diff --git a/e2e/routes.spec.ts b/e2e/routes.spec.ts index dc4ba7d..e9a9bbe 100644 --- a/e2e/routes.spec.ts +++ b/e2e/routes.spec.ts @@ -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 ); diff --git a/src/components/AmountEditable.tsx b/src/components/AmountEditable.tsx index 2d3b255..d5d0b8f 100644 --- a/src/components/AmountEditable.tsx +++ b/src/components/AmountEditable.tsx @@ -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()); diff --git a/src/components/BalanceBox.tsx b/src/components/BalanceBox.tsx index 98fefec..5ad7678 100644 --- a/src/components/BalanceBox.tsx +++ b/src/components/BalanceBox.tsx @@ -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 }) { - - - - + + }> +
+
+
+ +
+
+ +
+
+

}> @@ -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 ( - -
- - -
-
- -
-
- -
-
-
-
-
- ); -} diff --git a/src/i18n/en/translations.ts b/src/i18n/en/translations.ts index c95507a..dc88887 100644 --- a/src/i18n/en/translations.ts +++ b/src/i18n/en/translations.ts @@ -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", diff --git a/src/router.tsx b/src/router.tsx index ca2fa5e..9c997e9 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -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} /> - + diff --git a/src/routes/settings/ManageFederations.tsx b/src/routes/settings/ManageFederations.tsx index e96022b..ce1658e 100644 --- a/src/routes/settings/ManageFederations.tsx +++ b/src/routes/settings/ManageFederations.tsx @@ -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(); 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 }) { /> )} - - {error()?.message} - - - {success()} - + + {error()?.message} + + + {success()} + ); } -function ListAndRemoveFederations(props: { - federations?: MutinyFederationIdentity[]; - refetch: () => void; -}) { +function FederationListItem(props: { fed: MutinyFederationIdentity }) { const i18n = useI18n(); - const [state, _actions] = useMegaStore(); - const [error, setError] = createSignal(); + 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 ( - - - {(fed) => ( - - -
- {fed.federation_name} -
-
- -

{fed.welcome_message}

-
- - - - - - - - - -
+ <> + + +
+ {props.fed.federation_name} +
+
+ +

{props.fed.welcome_message}

+
+ + + + + + + + + +
+ setConfirmOpen(false)} + > + {i18n.t( + "settings.manage_federations.federation_remove_confirm" )} -
- -
No federations available.
-
- - {error()?.message} - -
+ + ); } @@ -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 ( @@ -204,11 +199,18 @@ export function ManageFederations() { {i18n.t("settings.manage_federations.title")} - - + + {i18n.t("settings.manage_federations.description")}{" "} + + {i18n.t("settings.manage_federations.learn_more")} + + + + + + {(fed) => } + + diff --git a/src/routes/settings/Root.tsx b/src/routes/settings/Root.tsx index c45c37a..ee14177 100644 --- a/src/routes/settings/Root.tsx +++ b/src/routes/settings/Root.tsx @@ -153,7 +153,7 @@ export function Settings() { text: "Sync Nostr Contacts" }, { - href: "/settings/managefederations", + href: "/settings/federations", text: "Manage Federations" } ]} diff --git a/src/state/megaStore.tsx b/src/state/megaStore.tsx index 5275d20..429d3ea 100644 --- a/src/state/megaStore.tsx +++ b/src/state/megaStore.tsx @@ -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; @@ -94,6 +96,7 @@ export type MegaStore = [ setTestFlightPromptDismissed(): void; toggleHodl(): void; dropMutinyWallet(): void; + refreshFederations(): Promise; } ]; @@ -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 }); } };