mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2025-12-18 23:04:25 +01:00
put federations in megastore
This commit is contained in:
committed by
Tony Giorgio
parent
6dcdbae4bc
commit
e1cfae2f7f
@@ -25,8 +25,8 @@ const settingsRoutes = [
|
||||
"/plus",
|
||||
"/restore",
|
||||
"/servers",
|
||||
"/syncnostrcontacts"
|
||||
"/managefederations"
|
||||
"/syncnostrcontacts",
|
||||
"/federations"
|
||||
];
|
||||
|
||||
const settingsRoutesPrefixed = settingsRoutes.map((route) => {
|
||||
@@ -128,7 +128,7 @@ test("visit each route", async ({ page }) => {
|
||||
// Manage Federations
|
||||
await checkRoute(
|
||||
page,
|
||||
"/settings/managefederations",
|
||||
"/settings/federations",
|
||||
"Manage Federations",
|
||||
checklist
|
||||
);
|
||||
|
||||
@@ -19,7 +19,6 @@ import currencySwap from "~/assets/icons/currency-swap.svg";
|
||||
import pencil from "~/assets/icons/pencil.svg";
|
||||
import { Button, FeesModal, InfoBox, InlineAmount, VStack } from "~/components";
|
||||
import { useI18n } from "~/i18n/context";
|
||||
import { Network } from "~/logic/mutinyWalletSetup";
|
||||
import { useMegaStore } from "~/state/megaStore";
|
||||
import { DIALOG_CONTENT, DIALOG_POSITIONER } from "~/styles/dialogs";
|
||||
import { Currency, fiatToSats, satsToFiat } from "~/utils";
|
||||
@@ -404,17 +403,13 @@ export const AmountEditable: ParentComponent<{
|
||||
});
|
||||
|
||||
const warningText = () => {
|
||||
if (state.federations?.length !== 0) {
|
||||
return undefined;
|
||||
}
|
||||
if ((state.balance?.lightning || 0n) === 0n) {
|
||||
const network = state.mutiny_wallet?.get_network() as Network;
|
||||
if (network === "bitcoin") {
|
||||
return i18n.t("receive.amount_editable.receive_too_small", {
|
||||
amount: "100,000"
|
||||
});
|
||||
} else {
|
||||
return i18n.t("receive.amount_editable.receive_too_small", {
|
||||
amount: "10,000"
|
||||
});
|
||||
}
|
||||
return i18n.t("receive.amount_editable.receive_too_small", {
|
||||
amount: "100,000"
|
||||
});
|
||||
}
|
||||
|
||||
const parsed = Number(localSats());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { A, useNavigate } from "@solidjs/router";
|
||||
import { createResource, Match, Show, Suspense, Switch } from "solid-js";
|
||||
import { Match, Show, Switch } from "solid-js";
|
||||
|
||||
import shuffle from "~/assets/icons/shuffle.svg";
|
||||
import {
|
||||
@@ -91,10 +91,25 @@ export function BalanceBox(props: { loading?: boolean }) {
|
||||
</Match>
|
||||
</Switch>
|
||||
</Show>
|
||||
<Show when={!props.loading && !state.safe_mode}>
|
||||
<Suspense>
|
||||
<FederationsBalance />
|
||||
</Suspense>
|
||||
<Show when={state.federations && state.federations.length}>
|
||||
<Show when={!props.loading} fallback={<LoadingShimmer />}>
|
||||
<hr class="my-2 border-m-grey-750" />
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="text-2xl">
|
||||
<AmountSats
|
||||
amountSats={state.balance?.federation || 0}
|
||||
icon="community"
|
||||
denominationSize="lg"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-lg text-white/70">
|
||||
<AmountFiat
|
||||
amountSats={state.balance?.federation || 0n}
|
||||
denominationSize="sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</Show>
|
||||
<hr class="my-2 border-m-grey-750" />
|
||||
<Show when={!props.loading} fallback={<LoadingShimmer />}>
|
||||
@@ -157,39 +172,3 @@ export function BalanceBox(props: { loading?: boolean }) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FederationsBalance() {
|
||||
const [state, _actions] = useMegaStore();
|
||||
|
||||
async function fetchFederations() {
|
||||
const result = await state.mutiny_wallet?.list_federations();
|
||||
return result ?? [];
|
||||
}
|
||||
|
||||
const [federations] = createResource(fetchFederations);
|
||||
|
||||
return (
|
||||
<Show when={federations() && federations().length}>
|
||||
<hr class="my-2 border-m-grey-750" />
|
||||
<Switch>
|
||||
<Match when={true}>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="text-2xl">
|
||||
<AmountSats
|
||||
amountSats={state.balance?.federation || 0}
|
||||
icon="community"
|
||||
denominationSize="lg"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-lg text-white/70">
|
||||
<AmountFiat
|
||||
amountSats={state.balance?.federation || 0n}
|
||||
denominationSize="sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Match>
|
||||
</Switch>
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -538,10 +538,15 @@ export default {
|
||||
federation_code_label: "Federation code",
|
||||
federation_code_required: "Federation code can't be blank",
|
||||
federation_added_success: "Federation added successfully",
|
||||
federation_remove_confirm:
|
||||
"Are you sure you want to remove this federation? Make sure any funds you have are transferred to your lightning balance or another wallet first.",
|
||||
add: "Add",
|
||||
remove: "Remove",
|
||||
expires: "Expires",
|
||||
federation_id: "Federation ID"
|
||||
federation_id: "Federation ID",
|
||||
description:
|
||||
"Mutiny has experimental support for the Fedimint protocol. You'll need a federation invite code to use this feature. These funds are currently not backed up remotely. Store funds in a federation at your own risk!",
|
||||
learn_more: "Learn more about Fedimint."
|
||||
},
|
||||
gift: {
|
||||
give_sats_link: "Give sats as a gift",
|
||||
|
||||
@@ -26,12 +26,12 @@ import {
|
||||
EmergencyKit,
|
||||
Encrypt,
|
||||
Gift,
|
||||
ManageFederations,
|
||||
Plus,
|
||||
Restore,
|
||||
Servers,
|
||||
Settings,
|
||||
SyncNostrContacts,
|
||||
ManageFederations
|
||||
SyncNostrContacts
|
||||
} from "~/routes/settings";
|
||||
|
||||
import { useMegaStore } from "./state/megaStore";
|
||||
@@ -119,7 +119,10 @@ export function Router() {
|
||||
path="/syncnostrcontacts"
|
||||
component={SyncNostrContacts}
|
||||
/>
|
||||
<Route path="/managefederations" component={ManageFederations} />
|
||||
<Route
|
||||
path="/federations"
|
||||
component={ManageFederations}
|
||||
/>
|
||||
</Route>
|
||||
<Route path="/*all" component={NotFound} />
|
||||
</Routes>
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import { createForm, required, SubmitHandler } from "@modular-forms/solid";
|
||||
import { createResource, createSignal, For, Show } from "solid-js";
|
||||
import {
|
||||
createForm,
|
||||
required,
|
||||
reset,
|
||||
SubmitHandler
|
||||
} from "@modular-forms/solid";
|
||||
import { createSignal, For, Show } from "solid-js";
|
||||
|
||||
import {
|
||||
BackLink,
|
||||
Button,
|
||||
ConfirmDialog,
|
||||
DefaultMain,
|
||||
ExternalLink,
|
||||
FancyCard,
|
||||
InfoBox,
|
||||
KeyValue,
|
||||
@@ -12,6 +19,7 @@ import {
|
||||
MiniStringShower,
|
||||
MutinyWalletGuard,
|
||||
NavBar,
|
||||
NiceP,
|
||||
SafeArea,
|
||||
TextField,
|
||||
VStack
|
||||
@@ -24,16 +32,16 @@ type FederationForm = {
|
||||
federation_code: string;
|
||||
};
|
||||
|
||||
type MutinyFederationIdentity = {
|
||||
export type MutinyFederationIdentity = {
|
||||
federation_id: string;
|
||||
federation_name: string;
|
||||
welcome_message: string;
|
||||
federation_expiry_timestamp: number;
|
||||
};
|
||||
|
||||
function AddFederationForm(props: { refetch: () => void }) {
|
||||
function AddFederationForm() {
|
||||
const i18n = useI18n();
|
||||
const [state, _actions] = useMegaStore();
|
||||
const [state, actions] = useMegaStore();
|
||||
const [error, setError] = createSignal<Error>();
|
||||
const [success, setSuccess] = createSignal("");
|
||||
|
||||
@@ -56,7 +64,8 @@ function AddFederationForm(props: { refetch: () => void }) {
|
||||
setSuccess(
|
||||
i18n.t("settings.manage_federations.federation_added_success")
|
||||
);
|
||||
await props.refetch();
|
||||
await actions.refreshFederations();
|
||||
reset(feedbackForm);
|
||||
} catch (e) {
|
||||
console.error("Error submitting federation:", e);
|
||||
setError(eify(e));
|
||||
@@ -89,90 +98,89 @@ function AddFederationForm(props: { refetch: () => void }) {
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
<Show when={error()}>
|
||||
<InfoBox accent="red">{error()?.message}</InfoBox>
|
||||
</Show>
|
||||
<Show when={success()}>
|
||||
<InfoBox accent="green">{success()}</InfoBox>
|
||||
</Show>
|
||||
<Button
|
||||
loading={false}
|
||||
loading={feedbackForm.submitting}
|
||||
disabled={!feedbackForm.touched || feedbackForm.invalid}
|
||||
intent="blue"
|
||||
type="submit"
|
||||
>
|
||||
{i18n.t("settings.manage_federations.add")}
|
||||
</Button>
|
||||
<Show when={error()}>
|
||||
<InfoBox accent="red">{error()?.message}</InfoBox>
|
||||
</Show>
|
||||
<Show when={success()}>
|
||||
<InfoBox accent="green">{success()}</InfoBox>
|
||||
</Show>
|
||||
</VStack>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
function ListAndRemoveFederations(props: {
|
||||
federations?: MutinyFederationIdentity[];
|
||||
refetch: () => void;
|
||||
}) {
|
||||
function FederationListItem(props: { fed: MutinyFederationIdentity }) {
|
||||
const i18n = useI18n();
|
||||
const [state, _actions] = useMegaStore();
|
||||
const [error, setError] = createSignal<Error>();
|
||||
const [state, actions] = useMegaStore();
|
||||
|
||||
const removeFederation = async (federationId: string) => {
|
||||
async function removeFederation() {
|
||||
setConfirmLoading(true);
|
||||
try {
|
||||
await state.mutiny_wallet?.remove_federation(federationId);
|
||||
props.refetch();
|
||||
await state.mutiny_wallet?.remove_federation(
|
||||
props.fed.federation_id
|
||||
);
|
||||
await actions.refreshFederations();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setError(eify(e));
|
||||
}
|
||||
};
|
||||
setConfirmLoading(false);
|
||||
}
|
||||
|
||||
async function confirmRemove() {
|
||||
setConfirmOpen(true);
|
||||
}
|
||||
|
||||
const [confirmOpen, setConfirmOpen] = createSignal(false);
|
||||
const [confirmLoading, setConfirmLoading] = createSignal(false);
|
||||
|
||||
return (
|
||||
<VStack>
|
||||
<For each={props.federations ?? []}>
|
||||
{(fed) => (
|
||||
<FancyCard>
|
||||
<Show when={fed.federation_name}>
|
||||
<header class={`font-semibold`}>
|
||||
{fed.federation_name}
|
||||
</header>
|
||||
</Show>
|
||||
<Show when={fed.welcome_message}>
|
||||
<p>{fed.welcome_message}</p>
|
||||
</Show>
|
||||
<Show when={fed.federation_expiry_timestamp}>
|
||||
<KeyValue
|
||||
key={i18n.t(
|
||||
"settings.manage_federations.expires"
|
||||
)}
|
||||
>
|
||||
<time>
|
||||
{timeAgo(fed.federation_expiry_timestamp)}
|
||||
</time>
|
||||
</KeyValue>
|
||||
</Show>
|
||||
<KeyValue
|
||||
key={i18n.t(
|
||||
"settings.manage_federations.federation_id"
|
||||
)}
|
||||
>
|
||||
<MiniStringShower text={fed.federation_id} />
|
||||
</KeyValue>
|
||||
<Button
|
||||
intent="red"
|
||||
onClick={() => removeFederation(fed.federation_id)}
|
||||
>
|
||||
{i18n.t("settings.manage_federations.remove")}
|
||||
</Button>
|
||||
</FancyCard>
|
||||
<>
|
||||
<FancyCard>
|
||||
<Show when={props.fed.federation_name}>
|
||||
<header class={`font-semibold`}>
|
||||
{props.fed.federation_name}
|
||||
</header>
|
||||
</Show>
|
||||
<Show when={props.fed.welcome_message}>
|
||||
<p>{props.fed.welcome_message}</p>
|
||||
</Show>
|
||||
<Show when={props.fed.federation_expiry_timestamp}>
|
||||
<KeyValue
|
||||
key={i18n.t("settings.manage_federations.expires")}
|
||||
>
|
||||
<time>
|
||||
{timeAgo(props.fed.federation_expiry_timestamp)}
|
||||
</time>
|
||||
</KeyValue>
|
||||
</Show>
|
||||
<KeyValue
|
||||
key={i18n.t("settings.manage_federations.federation_id")}
|
||||
>
|
||||
<MiniStringShower text={props.fed.federation_id} />
|
||||
</KeyValue>
|
||||
<Button intent="red" onClick={confirmRemove}>
|
||||
{i18n.t("settings.manage_federations.remove")}
|
||||
</Button>
|
||||
</FancyCard>
|
||||
<ConfirmDialog
|
||||
loading={confirmLoading()}
|
||||
open={confirmOpen()}
|
||||
onConfirm={removeFederation}
|
||||
onCancel={() => setConfirmOpen(false)}
|
||||
>
|
||||
{i18n.t(
|
||||
"settings.manage_federations.federation_remove_confirm"
|
||||
)}
|
||||
</For>
|
||||
<Show when={(props.federations ?? []).length === 0}>
|
||||
<div>No federations available.</div>
|
||||
</Show>
|
||||
<Show when={error()}>
|
||||
<InfoBox accent="red">{error()?.message}</InfoBox>
|
||||
</Show>
|
||||
</VStack>
|
||||
</ConfirmDialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -180,19 +188,6 @@ export function ManageFederations() {
|
||||
const i18n = useI18n();
|
||||
const [state, _actions] = useMegaStore();
|
||||
|
||||
async function fetchFederations() {
|
||||
try {
|
||||
const result =
|
||||
(await state.mutiny_wallet?.list_federations()) as MutinyFederationIdentity[];
|
||||
return result ?? [];
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const [federations, { refetch }] = createResource(fetchFederations);
|
||||
|
||||
return (
|
||||
<MutinyWalletGuard>
|
||||
<SafeArea>
|
||||
@@ -204,11 +199,18 @@ export function ManageFederations() {
|
||||
<LargeHeader>
|
||||
{i18n.t("settings.manage_federations.title")}
|
||||
</LargeHeader>
|
||||
<AddFederationForm refetch={refetch} />
|
||||
<ListAndRemoveFederations
|
||||
federations={federations.latest}
|
||||
refetch={refetch}
|
||||
/>
|
||||
<NiceP>
|
||||
{i18n.t("settings.manage_federations.description")}{" "}
|
||||
<ExternalLink href="https://fedimint.org/">
|
||||
{i18n.t("settings.manage_federations.learn_more")}
|
||||
</ExternalLink>
|
||||
</NiceP>
|
||||
<AddFederationForm />
|
||||
<VStack>
|
||||
<For each={state.federations ?? []}>
|
||||
{(fed) => <FederationListItem fed={fed} />}
|
||||
</For>
|
||||
</VStack>
|
||||
</DefaultMain>
|
||||
<NavBar activeTab="settings" />
|
||||
</SafeArea>
|
||||
|
||||
@@ -153,7 +153,7 @@ export function Settings() {
|
||||
text: "Sync Nostr Contacts"
|
||||
},
|
||||
{
|
||||
href: "/settings/managefederations",
|
||||
href: "/settings/federations",
|
||||
text: "Manage Federations"
|
||||
}
|
||||
]}
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
setupMutinyWallet
|
||||
} from "~/logic/mutinyWalletSetup";
|
||||
import { ParsedParams, toParsedParams } from "~/logic/waila";
|
||||
import { MutinyFederationIdentity } from "~/routes/settings";
|
||||
import {
|
||||
BTC_OPTION,
|
||||
Currency,
|
||||
@@ -70,6 +71,7 @@ export type MegaStore = [
|
||||
betaWarned: boolean;
|
||||
testflightPromptDismissed: boolean;
|
||||
should_zap_hodl: boolean;
|
||||
federations?: MutinyFederationIdentity[];
|
||||
},
|
||||
{
|
||||
setup(password?: string): Promise<void>;
|
||||
@@ -94,6 +96,7 @@ export type MegaStore = [
|
||||
setTestFlightPromptDismissed(): void;
|
||||
toggleHodl(): void;
|
||||
dropMutinyWallet(): void;
|
||||
refreshFederations(): Promise<void>;
|
||||
}
|
||||
];
|
||||
|
||||
@@ -134,7 +137,8 @@ export const Provider: ParentComponent = (props) => {
|
||||
betaWarned: localStorage.getItem("betaWarned") === "true",
|
||||
should_zap_hodl: localStorage.getItem("should_zap_hodl") === "true",
|
||||
testflightPromptDismissed:
|
||||
localStorage.getItem("testflightPromptDismissed") === "true"
|
||||
localStorage.getItem("testflightPromptDismissed") === "true",
|
||||
federations: undefined as MutinyFederationIdentity[] | undefined
|
||||
});
|
||||
|
||||
const actions = {
|
||||
@@ -211,11 +215,16 @@ export const Provider: ParentComponent = (props) => {
|
||||
// Get balance
|
||||
const balance = await mutinyWallet.get_balance();
|
||||
|
||||
// Get federations
|
||||
const federations =
|
||||
(await mutinyWallet.list_federations()) as MutinyFederationIdentity[];
|
||||
|
||||
setState({
|
||||
mutiny_wallet: mutinyWallet,
|
||||
wallet_loading: false,
|
||||
load_stage: "done",
|
||||
balance
|
||||
balance,
|
||||
federations
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -408,6 +417,11 @@ export const Provider: ParentComponent = (props) => {
|
||||
},
|
||||
dropMutinyWallet() {
|
||||
setState({ mutiny_wallet: undefined });
|
||||
},
|
||||
async refreshFederations() {
|
||||
const federations =
|
||||
(await state.mutiny_wallet?.list_federations()) as MutinyFederationIdentity[];
|
||||
setState({ federations });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user