put federations in megastore

This commit is contained in:
Paul Miller
2023-12-19 16:30:53 -06:00
committed by Tony Giorgio
parent 6dcdbae4bc
commit e1cfae2f7f
8 changed files with 147 additions and 149 deletions

View File

@@ -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
);

View File

@@ -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());

View File

@@ -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>
);
}

View File

@@ -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",

View File

@@ -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>

View File

@@ -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>

View File

@@ -153,7 +153,7 @@ export function Settings() {
text: "Sync Nostr Contacts"
},
{
href: "/settings/managefederations",
href: "/settings/federations",
text: "Manage Federations"
}
]}

View File

@@ -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 });
}
};