Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

show federation expiration metadata #1129

Merged
merged 3 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
13 changes: 11 additions & 2 deletions public/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,8 @@
"back_home": "back home"
},
"start_a_chat": "Start a chat?",
"start_a_chat_are_you_sure": "This user isn't in your contact list."
"start_a_chat_are_you_sure": "This user isn't in your contact list.",
"federation_message": "Federation Message"
},
"scanner": {
"paste": "Paste Something",
Expand Down Expand Up @@ -561,7 +562,9 @@
"descriptionpart2": "Each one is run by a group of different inviduals or companies. Discover one that you or your friends might trust below.",
"join_me": "Join me",
"recommend": "Recommend federation",
"recommended_by_you": "Recommended by you"
"recommended_by_you": "Recommended by you",
"transfer_funds": "Transfer funds",
"transfer_funds_message": "Add a second federation to enable transfers."
},
"gift": {
"give_sats_link": "Give sats as a gift",
Expand Down Expand Up @@ -782,5 +785,11 @@
"nowish": "Nowish",
"seconds_future": "Seconds from now",
"seconds_past": "Just now"
},
"transfer": {
"completed": "Transfer Completed",
"sats_moved": "+{{amount}} sats have been moved to {{federation_name}}",
"confirm": "Confirm Transfer",
"title": "Transfer funds"
}
}
4 changes: 4 additions & 0 deletions src/components/Activity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
Button,
ButtonCard,
ContactButton,
FederationPopup,
LoadingShimmer,
NiceP,
SimpleDialog
Expand Down Expand Up @@ -424,6 +425,9 @@ export function CombinedActivity() {
/>
</Show>
<Suspense fallback={<LoadingShimmer />}>
<Show when={state.expiration_warning}>
<FederationPopup />
</Show>
<Show when={!state.has_backed_up}>
<ButtonCard
red
Expand Down
14 changes: 8 additions & 6 deletions src/components/AmountEditable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,18 @@ import {
} from "~/utils";

export type MethodChoice = {
method: "lightning" | "onchain";
method: "lightning" | "onchain" | "fedimint";
maxAmountSats?: bigint;
};

// Make sure to update this when we get the fedi option in here!
function methodToIcon(method: MethodChoice["method"]) {
if (method === "lightning") {
return "lightning";
} else if (method === "onchain") {
return "chain";
switch (method) {
case "lightning":
return "lightning";
case "onchain":
return "chain";
case "fedimint":
return "community";
}
}

Expand Down
45 changes: 45 additions & 0 deletions src/components/FederationPopup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useNavigate } from "@solidjs/router";
import { Users } from "lucide-solid";
import { createSignal } from "solid-js";

import { ButtonCard, NiceP, SimpleDialog } from "~/components/layout";
import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore";

export function FederationPopup() {
const [state, actions, _sw] = useMegaStore();
const [
showFederationExpirationWarning,
setShowFederationExpirationWarning
] = createSignal(!state.expiration_warning_seen);

const i18n = useI18n();
const navigate = useNavigate();

return (
<SimpleDialog
title={i18n.t("activity.federation_message")}
open={showFederationExpirationWarning()}
setOpen={(open: boolean) => {
if (!open) {
setShowFederationExpirationWarning(false);
actions.clearExpirationWarning();
}
}}
>
<NiceP>{state.expiration_warning?.expiresMessage}</NiceP>
<ButtonCard
onClick={() => {
actions.clearExpirationWarning();
setShowFederationExpirationWarning(false);
navigate("/settings/federations");
}}
>
<div class="flex items-center gap-2">
<Users class="inline-block text-m-red" />
<NiceP>{i18n.t("profile.manage_federation")}</NiceP>
</div>
</ButtonCard>
</SimpleDialog>
);
}
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,4 @@ export * from "./EditProfileForm";
export * from "./ImportNsecForm";
export * from "./LightningAddressShower";
export * from "./FederationInviteShower";
export * from "./FederationPopup";
4 changes: 3 additions & 1 deletion src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ import {
Search,
Send,
Swap,
SwapLightning
SwapLightning,
Transfer
} from "~/routes";
import {
Admin,
Expand Down Expand Up @@ -179,6 +180,7 @@ export function Router() {
<Route path="/send" component={Send} />
<Route path="/swap" component={Swap} />
<Route path="/swaplightning" component={SwapLightning} />
<Route path="/transfer" component={Transfer} />
<Route path="/search" component={Search} />
<Route path="/settings">
<Route path="/" component={Settings} />
Expand Down
7 changes: 1 addition & 6 deletions src/routes/Send.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { MutinyInvoice, TagItem } from "@mutinywallet/mutiny-wasm";
import {
createAsync,
useLocation,
useNavigate,
useSearchParams
} from "@solidjs/router";
import { useLocation, useNavigate, useSearchParams } from "@solidjs/router";
import { Eye, EyeOff, Link, X, Zap } from "lucide-solid";
import {
createEffect,
Expand Down
2 changes: 1 addition & 1 deletion src/routes/Swap.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createForm, required } from "@modular-forms/solid";
import { MutinyChannel } from "@mutinywallet/mutiny-wasm";
import { createAsync, useNavigate } from "@solidjs/router";
import { useNavigate } from "@solidjs/router";
import {
createEffect,
createMemo,
Expand Down
220 changes: 220 additions & 0 deletions src/routes/Transfer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import { FedimintSweepResult } from "@mutinywallet/mutiny-wasm";
import { createAsync, useNavigate, useSearchParams } from "@solidjs/router";
import { ArrowDown, Users } from "lucide-solid";
import { createMemo, createSignal, Match, Suspense, Switch } from "solid-js";

import {
AmountEditable,
AmountFiat,
AmountSats,
BackLink,
Button,
DefaultMain,
Failure,
Fee,
LargeHeader,
MegaCheck,
MutinyWalletGuard,
SharpButton,
SuccessModal,
VStack
} from "~/components";
import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore";
import { eify, vibrateSuccess } from "~/utils";

type TransferResultDetails = {
result?: FedimintSweepResult;
failure_reason?: string;
};

export function Transfer() {
const [state, _actions, sw] = useMegaStore();
const i18n = useI18n();
const navigate = useNavigate();
const [amountSats, setAmountSats] = createSignal(0n);
const [loading, setLoading] = createSignal(false);
const [params] = useSearchParams();

const [transferResult, setTransferResult] =
createSignal<TransferResultDetails>();

const fromFed = () => {
return state.federations?.find((f) => f.federation_id === params.from);
};

const toFed = () => {
return state.federations?.find((f) => f.federation_id !== params.from);
};

const federationBalances = createAsync(async () => {
try {
const balances = await sw.get_federation_balances();
return balances?.balances || [];
} catch (e) {
console.error(e);
return [];
}
});

const calculateMaxFederation = createAsync(async () => {
const balance = federationBalances()?.find(
(f) => f.identity_federation_id === fromFed()?.federation_id
)?.balance;
return balance || 0n;
});

const toBalance = createAsync(async () => {
return federationBalances()?.find(
(f) => f.identity_federation_id === toFed()?.federation_id
)?.balance;
});

const isMax = createMemo(() => {
return amountSats() === calculateMaxFederation();
});

const canTransfer = createMemo(() => {
if (!calculateMaxFederation()) return false;
return amountSats() > 0n && amountSats() <= calculateMaxFederation()!;
});

async function handleTransfer() {
try {
setLoading(true);
if (!fromFed()) throw new Error("No from federation");
if (!toFed()) throw new Error("No to federation");

if (isMax()) {
const result = await sw.sweep_federation_balance(
undefined,
fromFed()?.federation_id,
toFed()?.federation_id
);

setTransferResult({ result: result });
} else {
const result = await sw.sweep_federation_balance(
amountSats(),
fromFed()?.federation_id,
toFed()?.federation_id
);

setTransferResult({ result: result });
}

await vibrateSuccess();
} catch (e) {
const error = eify(e);
setTransferResult({ failure_reason: error.message });
console.error(e);
} finally {
setLoading(false);
}
}

return (
<MutinyWalletGuard>
<DefaultMain>
<SuccessModal
confirmText={
transferResult()?.result
? i18n.t("common.nice")
: i18n.t("common.home")
}
open={!!transferResult()}
setOpen={(open: boolean) => {
if (!open) setTransferResult(undefined);
}}
onConfirm={() => {
setTransferResult(undefined);
navigate("/");
}}
>
<Switch>
<Match when={transferResult()?.failure_reason}>
<Failure
reason={transferResult()!.failure_reason!}
/>
</Match>
<Match when={transferResult()?.result}>
<MegaCheck />
<div class="flex flex-col justify-center">
<h1 class="mb-2 mt-4 w-full justify-center text-center text-2xl font-semibold md:text-3xl">
{i18n.t("transfer.completed")}
</h1>
<p class="text-center text-xl">
{i18n.t("transfer.sats_moved", {
amount: Number(
transferResult()?.result?.amount
).toLocaleString(),
federation_name:
toFed()?.federation_name
})}
</p>
<div class="text-center text-sm text-white/70">
<Suspense>
<AmountFiat
amountSats={Number(
transferResult()?.result?.amount
)}
/>
</Suspense>
</div>
</div>
<hr class="w-16 bg-m-grey-400" />
<Fee
amountSats={Number(
transferResult()?.result?.fees
)}
/>
</Match>
</Switch>
</SuccessModal>
<BackLink
title={i18n.t("common.back")}
href="/settings/federations"
showOnDesktop
/>
<LargeHeader>{i18n.t("transfer.title")}</LargeHeader>
<div class="flex flex-1 flex-col justify-between gap-2">
<div class="flex-1" />
<div class="flex flex-col items-center">
<AmountEditable
initialAmountSats={amountSats()}
setAmountSats={setAmountSats}
/>
<SharpButton disabled onClick={() => {}}>
<Users class="w-[18px]" />
{fromFed()?.federation_name}
<AmountSats
amountSats={calculateMaxFederation()}
denominationSize="sm"
/>
</SharpButton>
<ArrowDown class="h-4 w-4" />
<SharpButton disabled onClick={() => {}}>
<Users class="w-[18px]" />
{toFed()?.federation_name}
<AmountSats
amountSats={toBalance()}
denominationSize="sm"
/>
</SharpButton>
</div>
<div class="flex-1" />
<VStack>
<Button
disabled={!canTransfer()}
intent="blue"
onClick={handleTransfer}
loading={loading()}
>
{i18n.t("transfer.confirm")}
</Button>
</VStack>
</div>
</DefaultMain>
</MutinyWalletGuard>
);
}
1 change: 1 addition & 0 deletions src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export * from "./Request";
export * from "./EditProfile";
export * from "./Swap";
export * from "./SwapLightning";
export * from "./Transfer";
Loading
Loading