mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2025-12-20 15:54:22 +01:00
add transfer funds screen
This commit is contained in:
@@ -265,7 +265,8 @@
|
|||||||
"back_home": "back home"
|
"back_home": "back home"
|
||||||
},
|
},
|
||||||
"start_a_chat": "Start a chat?",
|
"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": {
|
"scanner": {
|
||||||
"paste": "Paste Something",
|
"paste": "Paste Something",
|
||||||
@@ -566,7 +567,8 @@
|
|||||||
"descriptionpart2": "Each one is run by a group of different individuals or companies. Discover one that you or your friends might trust below.",
|
"descriptionpart2": "Each one is run by a group of different individuals or companies. Discover one that you or your friends might trust below.",
|
||||||
"join_me": "Join me",
|
"join_me": "Join me",
|
||||||
"recommend": "Recommend federation",
|
"recommend": "Recommend federation",
|
||||||
"recommended_by_you": "Recommended by you"
|
"recommended_by_you": "Recommended by you",
|
||||||
|
"transfer_funds": "Transfer funds"
|
||||||
},
|
},
|
||||||
"gift": {
|
"gift": {
|
||||||
"give_sats_link": "Give sats as a gift",
|
"give_sats_link": "Give sats as a gift",
|
||||||
@@ -787,5 +789,11 @@
|
|||||||
"nowish": "Nowish",
|
"nowish": "Nowish",
|
||||||
"seconds_future": "Seconds from now",
|
"seconds_future": "Seconds from now",
|
||||||
"seconds_past": "Just 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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
createSignal,
|
createSignal,
|
||||||
For,
|
For,
|
||||||
Match,
|
Match,
|
||||||
|
onMount,
|
||||||
Show,
|
Show,
|
||||||
Suspense,
|
Suspense,
|
||||||
Switch
|
Switch
|
||||||
@@ -364,7 +365,7 @@ function NewContactModal(props: { profile: PseudoContact; close: () => void }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function CombinedActivity() {
|
export function CombinedActivity() {
|
||||||
const [state, _actions, sw] = useMegaStore();
|
const [state, actions, sw] = useMegaStore();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
const [detailsOpen, setDetailsOpen] = createSignal(false);
|
const [detailsOpen, setDetailsOpen] = createSignal(false);
|
||||||
@@ -407,6 +408,17 @@ export function CombinedActivity() {
|
|||||||
|
|
||||||
const [newContact, setNewContact] = createSignal<PseudoContact>();
|
const [newContact, setNewContact] = createSignal<PseudoContact>();
|
||||||
|
|
||||||
|
const [
|
||||||
|
showFederationExpirationWarning,
|
||||||
|
setShowFederationExpirationWarning
|
||||||
|
] = createSignal(false);
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (state.expiration_warning) {
|
||||||
|
setShowFederationExpirationWarning(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Show when={detailsId() && detailsKind()}>
|
<Show when={detailsId() && detailsKind()}>
|
||||||
@@ -424,6 +436,32 @@ export function CombinedActivity() {
|
|||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<Suspense fallback={<LoadingShimmer />}>
|
<Suspense fallback={<LoadingShimmer />}>
|
||||||
|
<Show when={state.expiration_warning}>
|
||||||
|
<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={() => 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>
|
||||||
|
</Show>
|
||||||
<Show when={!state.has_backed_up}>
|
<Show when={!state.has_backed_up}>
|
||||||
<ButtonCard
|
<ButtonCard
|
||||||
red
|
red
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
} from "~/utils";
|
} from "~/utils";
|
||||||
|
|
||||||
export type MethodChoice = {
|
export type MethodChoice = {
|
||||||
method: "lightning" | "onchain";
|
method: "lightning" | "onchain" | "fedimint";
|
||||||
maxAmountSats?: bigint;
|
maxAmountSats?: bigint;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -29,6 +29,8 @@ function methodToIcon(method: MethodChoice["method"]) {
|
|||||||
return "lightning";
|
return "lightning";
|
||||||
} else if (method === "onchain") {
|
} else if (method === "onchain") {
|
||||||
return "chain";
|
return "chain";
|
||||||
|
} else if (method === "fedimint") {
|
||||||
|
return "community";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ import {
|
|||||||
Search,
|
Search,
|
||||||
Send,
|
Send,
|
||||||
Swap,
|
Swap,
|
||||||
SwapLightning
|
SwapLightning,
|
||||||
|
Transfer
|
||||||
} from "~/routes";
|
} from "~/routes";
|
||||||
import {
|
import {
|
||||||
Admin,
|
Admin,
|
||||||
@@ -179,6 +180,7 @@ export function Router() {
|
|||||||
<Route path="/send" component={Send} />
|
<Route path="/send" component={Send} />
|
||||||
<Route path="/swap" component={Swap} />
|
<Route path="/swap" component={Swap} />
|
||||||
<Route path="/swaplightning" component={SwapLightning} />
|
<Route path="/swaplightning" component={SwapLightning} />
|
||||||
|
<Route path="/transfer" component={Transfer} />
|
||||||
<Route path="/search" component={Search} />
|
<Route path="/search" component={Search} />
|
||||||
<Route path="/settings">
|
<Route path="/settings">
|
||||||
<Route path="/" component={Settings} />
|
<Route path="/" component={Settings} />
|
||||||
|
|||||||
216
src/routes/Transfer.tsx
Normal file
216
src/routes/Transfer.tsx
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
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 canTransfer = createMemo(() => {
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
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 () => {
|
||||||
|
return federationBalances()?.find(
|
||||||
|
(f) => f.identity_federation_id === fromFed()?.federation_id
|
||||||
|
)?.balance;
|
||||||
|
});
|
||||||
|
|
||||||
|
const toBalance = createAsync(async () => {
|
||||||
|
return federationBalances()?.find(
|
||||||
|
(f) => f.identity_federation_id === toFed()?.federation_id
|
||||||
|
)?.balance;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isMax = createMemo(() => {
|
||||||
|
return 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const fromFederatationId = params.from;
|
||||||
|
|
||||||
|
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 href="/settings/federations" />
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -12,3 +12,4 @@ export * from "./Request";
|
|||||||
export * from "./EditProfile";
|
export * from "./EditProfile";
|
||||||
export * from "./Swap";
|
export * from "./Swap";
|
||||||
export * from "./SwapLightning";
|
export * from "./SwapLightning";
|
||||||
|
export * from "./Transfer";
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import {
|
|||||||
} from "@modular-forms/solid";
|
} from "@modular-forms/solid";
|
||||||
import { FederationBalance, TagItem } from "@mutinywallet/mutiny-wasm";
|
import { FederationBalance, TagItem } from "@mutinywallet/mutiny-wasm";
|
||||||
import { A, useNavigate, useSearchParams } from "@solidjs/router";
|
import { A, useNavigate, useSearchParams } from "@solidjs/router";
|
||||||
import { BadgeCheck, LogOut, Scan, Trash } from "lucide-solid";
|
import { ArrowLeftRight, BadgeCheck, LogOut, Scan, Trash } from "lucide-solid";
|
||||||
import {
|
import {
|
||||||
|
createMemo,
|
||||||
createResource,
|
createResource,
|
||||||
createSignal,
|
createSignal,
|
||||||
For,
|
For,
|
||||||
@@ -57,6 +58,7 @@ export type MutinyFederationIdentity = {
|
|||||||
welcome_message: string;
|
welcome_message: string;
|
||||||
federation_expiry_timestamp: number;
|
federation_expiry_timestamp: number;
|
||||||
invite_code: string;
|
invite_code: string;
|
||||||
|
meta_external_url?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Metadata = {
|
export type Metadata = {
|
||||||
@@ -240,25 +242,52 @@ export function AddFederationForm(props: {
|
|||||||
<Match when={federations.latest}>
|
<Match when={federations.latest}>
|
||||||
<For each={federations()}>
|
<For each={federations()}>
|
||||||
{(fed) => (
|
{(fed) => (
|
||||||
|
<FederationFormItem
|
||||||
|
fed={fed}
|
||||||
|
onSelect={onSelect}
|
||||||
|
loadingFederation={loadingFederation()}
|
||||||
|
setup={!!props.setup}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FederationFormItem(props: {
|
||||||
|
fed: DiscoveredFederation;
|
||||||
|
onSelect: (invite_codes: string[]) => void;
|
||||||
|
loadingFederation: string;
|
||||||
|
setup: boolean;
|
||||||
|
}) {
|
||||||
|
const [state, _actions, _sw] = useMegaStore();
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
|
const alreadyAdded = createMemo(() => {
|
||||||
|
const matches = state.federations?.find((f) =>
|
||||||
|
props.fed.invite_codes.includes(f.invite_code)
|
||||||
|
);
|
||||||
|
return matches !== undefined;
|
||||||
|
});
|
||||||
|
return (
|
||||||
<FancyCard>
|
<FancyCard>
|
||||||
<VStack>
|
<VStack>
|
||||||
<div class="flex items-center gap-2 md:gap-4">
|
<div class="flex items-center gap-2 md:gap-4">
|
||||||
<LabelCircle
|
<LabelCircle
|
||||||
name={fed.metadata?.name}
|
name={props.fed.metadata?.name}
|
||||||
image_url={
|
image_url={props.fed.metadata?.picture}
|
||||||
fed.metadata?.picture
|
|
||||||
}
|
|
||||||
contact={false}
|
contact={false}
|
||||||
label={false}
|
label={false}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<header class={`font-semibold`}>
|
<header class={`font-semibold`}>
|
||||||
{fed.metadata?.name}
|
{props.fed.metadata?.name}
|
||||||
</header>
|
</header>
|
||||||
<Show
|
<Show when={props.fed.metadata?.about}>
|
||||||
when={fed.metadata?.about}
|
<p>{props.fed.metadata?.about}</p>
|
||||||
>
|
|
||||||
<p>{fed.metadata?.about}</p>
|
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -268,46 +297,28 @@ export function AddFederationForm(props: {
|
|||||||
"settings.manage_federations.federation_id"
|
"settings.manage_federations.federation_id"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<MiniStringShower
|
<MiniStringShower text={props.fed.id} />
|
||||||
text={fed.id}
|
|
||||||
/>
|
|
||||||
</KeyValue>
|
</KeyValue>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={fed.created_at}>
|
<Show when={props.fed.created_at}>
|
||||||
<KeyValue
|
<KeyValue
|
||||||
key={i18n.t(
|
key={i18n.t("settings.manage_federations.created_at")}
|
||||||
"settings.manage_federations.created_at"
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<time>
|
<time>{timeAgo(props.fed.created_at)}</time>
|
||||||
{timeAgo(fed.created_at)}
|
|
||||||
</time>
|
|
||||||
</KeyValue>
|
</KeyValue>
|
||||||
</Show>
|
</Show>
|
||||||
<Show
|
<Show when={props.fed.recommendations.length > 0}>
|
||||||
when={
|
|
||||||
fed.recommendations.length > 0
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<KeyValue
|
<KeyValue
|
||||||
key={i18n.t(
|
key={i18n.t(
|
||||||
"settings.manage_federations.recommended_by"
|
"settings.manage_federations.recommended_by"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2 overflow-scroll md:gap-4">
|
<div class="flex items-center gap-2 overflow-scroll md:gap-4">
|
||||||
<For
|
<For each={props.fed.recommendations}>
|
||||||
each={
|
|
||||||
fed.recommendations
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{(contact) => (
|
{(contact) => (
|
||||||
<LabelCircle
|
<LabelCircle
|
||||||
name={
|
name={contact.name}
|
||||||
contact.name
|
image_url={contact.image_url}
|
||||||
}
|
|
||||||
image_url={
|
|
||||||
contact.image_url
|
|
||||||
}
|
|
||||||
contact={true}
|
contact={true}
|
||||||
label={false}
|
label={false}
|
||||||
/>
|
/>
|
||||||
@@ -316,29 +327,21 @@ export function AddFederationForm(props: {
|
|||||||
</div>
|
</div>
|
||||||
</KeyValue>
|
</KeyValue>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={!props.browseOnly}>
|
<Show
|
||||||
|
when={!alreadyAdded() && !(state.federations?.length === 2)}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
intent="blue"
|
intent="blue"
|
||||||
onClick={() =>
|
onClick={() => props.onSelect(props.fed.invite_codes)}
|
||||||
onSelect(fed.invite_codes)
|
loading={props.fed.invite_codes.includes(
|
||||||
}
|
props.loadingFederation
|
||||||
loading={fed.invite_codes.includes(
|
|
||||||
loadingFederation()
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{i18n.t(
|
{i18n.t("settings.manage_federations.add")}
|
||||||
"settings.manage_federations.add"
|
|
||||||
)}
|
|
||||||
</Button>
|
</Button>
|
||||||
</Show>
|
</Show>
|
||||||
</VStack>
|
</VStack>
|
||||||
</FancyCard>
|
</FancyCard>
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
</Suspense>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,7 +428,8 @@ function FederationListItem(props: {
|
|||||||
balance?: bigint;
|
balance?: bigint;
|
||||||
}) {
|
}) {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const [_state, actions, sw] = useMegaStore();
|
const [state, actions, sw] = useMegaStore();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
async function removeFederation() {
|
async function removeFederation() {
|
||||||
setConfirmLoading(true);
|
setConfirmLoading(true);
|
||||||
@@ -442,6 +446,10 @@ function FederationListItem(props: {
|
|||||||
setConfirmOpen(true);
|
setConfirmOpen(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function transferFunds() {
|
||||||
|
navigate("/transfer?from=" + props.fed.federation_id);
|
||||||
|
}
|
||||||
|
|
||||||
const [confirmOpen, setConfirmOpen] = createSignal(false);
|
const [confirmOpen, setConfirmOpen] = createSignal(false);
|
||||||
const [confirmLoading, setConfirmLoading] = createSignal(false);
|
const [confirmLoading, setConfirmLoading] = createSignal(false);
|
||||||
|
|
||||||
@@ -449,6 +457,7 @@ function FederationListItem(props: {
|
|||||||
<>
|
<>
|
||||||
<FancyCard>
|
<FancyCard>
|
||||||
<VStack>
|
<VStack>
|
||||||
|
{/* <pre>{JSON.stringify(props.fed, null, 2)}</pre> */}
|
||||||
<Show when={props.fed.federation_name}>
|
<Show when={props.fed.federation_name}>
|
||||||
<header class={`font-semibold`}>
|
<header class={`font-semibold`}>
|
||||||
{props.fed.federation_name}
|
{props.fed.federation_name}
|
||||||
@@ -490,6 +499,19 @@ function FederationListItem(props: {
|
|||||||
inviteCode={props.fed.invite_code}
|
inviteCode={props.fed.invite_code}
|
||||||
/>
|
/>
|
||||||
</KeyValue>
|
</KeyValue>
|
||||||
|
<Show
|
||||||
|
when={
|
||||||
|
state.federations?.length &&
|
||||||
|
state.federations.length === 2
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SubtleButton onClick={transferFunds}>
|
||||||
|
<ArrowLeftRight class="h-4 w-4" />
|
||||||
|
{i18n.t(
|
||||||
|
"settings.manage_federations.transfer_funds"
|
||||||
|
)}
|
||||||
|
</SubtleButton>
|
||||||
|
</Show>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<RecommendButton fed={props.fed} />
|
<RecommendButton fed={props.fed} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
@@ -606,7 +628,7 @@ export function ManageFederations() {
|
|||||||
</VStack>
|
</VStack>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<Show when={state.federations?.length}>
|
<Show when={state.federations?.length}>
|
||||||
<AddFederationForm refetch={refetch} browseOnly />
|
<AddFederationForm refetch={refetch} />
|
||||||
</Show>
|
</Show>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</DefaultMain>
|
</DefaultMain>
|
||||||
|
|||||||
@@ -87,7 +87,10 @@ export const makeMegaStoreContext = () => {
|
|||||||
testflightPromptDismissed:
|
testflightPromptDismissed:
|
||||||
localStorage.getItem("testflightPromptDismissed") === "true",
|
localStorage.getItem("testflightPromptDismissed") === "true",
|
||||||
federations: undefined as MutinyFederationIdentity[] | undefined,
|
federations: undefined as MutinyFederationIdentity[] | undefined,
|
||||||
balanceView: localStorage.getItem("balanceView") || "sats"
|
balanceView: localStorage.getItem("balanceView") || "sats",
|
||||||
|
expiration_warning: undefined as
|
||||||
|
| { expiresTimestamp: number; expiresMessage: string }
|
||||||
|
| undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
@@ -226,15 +229,59 @@ export const makeMegaStoreContext = () => {
|
|||||||
const balance = await sw.get_balance();
|
const balance = await sw.get_balance();
|
||||||
|
|
||||||
// Get federations
|
// Get federations
|
||||||
const federations =
|
const federations = await sw.list_federations();
|
||||||
(await sw.list_federations()) as MutinyFederationIdentity[];
|
|
||||||
|
let expiration_warning:
|
||||||
|
| { expiresTimestamp: number; expiresMessage: string }
|
||||||
|
| undefined = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (federations.length) {
|
||||||
|
const activeFederation = federations[0];
|
||||||
|
const metadataUrl = activeFederation.meta_external_url;
|
||||||
|
console.log("federation metadata url", metadataUrl);
|
||||||
|
if (metadataUrl) {
|
||||||
|
const response = await fetch(metadataUrl);
|
||||||
|
if (response.ok) {
|
||||||
|
const metadata = await response.json();
|
||||||
|
console.log(
|
||||||
|
"all federation metadata",
|
||||||
|
metadata
|
||||||
|
);
|
||||||
|
const specificFederation =
|
||||||
|
metadata[activeFederation.federation_id];
|
||||||
|
console.log(
|
||||||
|
"specific federation metadata",
|
||||||
|
specificFederation
|
||||||
|
);
|
||||||
|
const expiresTimestamp =
|
||||||
|
specificFederation.popup_end_timestamp;
|
||||||
|
console.log(
|
||||||
|
"federation expires",
|
||||||
|
expiresTimestamp
|
||||||
|
);
|
||||||
|
const expiresMessage =
|
||||||
|
specificFederation.popup_countdown_message;
|
||||||
|
expiration_warning = {
|
||||||
|
expiresTimestamp,
|
||||||
|
expiresMessage
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error getting federation metadata", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("expiration_warning", expiration_warning);
|
||||||
|
|
||||||
setState({
|
setState({
|
||||||
wallet_loading: false,
|
wallet_loading: false,
|
||||||
load_stage: "done",
|
load_stage: "done",
|
||||||
balance,
|
balance,
|
||||||
federations,
|
federations,
|
||||||
network: network as Network
|
network: network as Network,
|
||||||
|
expiration_warning
|
||||||
});
|
});
|
||||||
|
|
||||||
// Timestamp our initialization for double init defense
|
// Timestamp our initialization for double init defense
|
||||||
@@ -506,6 +553,10 @@ export const makeMegaStoreContext = () => {
|
|||||||
channel.postMessage({ type: "EXISTING_TAB" });
|
channel.postMessage({ type: "EXISTING_TAB" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
// Only show the expiration warning once per session
|
||||||
|
clearExpirationWarning() {
|
||||||
|
setState({ expiration_warning: undefined });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -217,6 +217,7 @@ export async function get_balance(): Promise<MutinyBalance> {
|
|||||||
*/
|
*/
|
||||||
export async function list_federations(): Promise<MutinyFederationIdentity[]> {
|
export async function list_federations(): Promise<MutinyFederationIdentity[]> {
|
||||||
const federations = await wallet!.list_federations();
|
const federations = await wallet!.list_federations();
|
||||||
|
console.log("list_federations", federations);
|
||||||
return federations as MutinyFederationIdentity[];
|
return federations as MutinyFederationIdentity[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1548,9 +1549,15 @@ export async function estimate_sweep_channel_open_fee(
|
|||||||
* @returns {Promise<FedimintSweepResult>}
|
* @returns {Promise<FedimintSweepResult>}
|
||||||
*/
|
*/
|
||||||
export async function sweep_federation_balance(
|
export async function sweep_federation_balance(
|
||||||
amount?: bigint
|
amount?: bigint,
|
||||||
|
from_federation_id?: string,
|
||||||
|
to_federation_id?: string
|
||||||
): Promise<FedimintSweepResult> {
|
): Promise<FedimintSweepResult> {
|
||||||
const result = await wallet!.sweep_federation_balance(amount);
|
const result = await wallet!.sweep_federation_balance(
|
||||||
|
amount,
|
||||||
|
from_federation_id,
|
||||||
|
to_federation_id
|
||||||
|
);
|
||||||
return { ...result.value } as FedimintSweepResult;
|
return { ...result.value } as FedimintSweepResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user