export * from ~/components

This commit is contained in:
gawlk
2023-08-18 13:18:08 +02:00
committed by Paul Miller
parent 4d5c75fc84
commit 8fa30119e1
66 changed files with 320 additions and 1593 deletions

View File

@@ -1,183 +0,0 @@
import { NiceP } from "./layout";
import {
For,
Match,
Show,
Switch,
createEffect,
createResource,
createSignal
} from "solid-js";
import { useMegaStore } from "~/state/megaStore";
import { useI18n } from "~/i18n/context";
import { Contact } from "@mutinywallet/mutiny-wasm";
import { ActivityItem, HackActivityType } from "./ActivityItem";
import { DetailsIdModal } from "./DetailsModal";
import { A } from "solid-start";
import { LoadingShimmer } from "./BalanceBox";
import { createDeepSignal } from "~/utils/deepSignal";
export const THREE_COLUMNS =
"grid grid-cols-[auto,1fr,auto] gap-4 py-2 px-2 border-b border-neutral-800 last:border-b-0";
export const CENTER_COLUMN = "min-w-0 overflow-hidden max-w-full";
export const MISSING_LABEL =
"py-1 px-2 bg-white/10 rounded inline-block text-sm";
export const REDSHIFT_LABEL =
"py-1 px-2 bg-white text-m-red rounded inline-block text-sm";
export const RIGHT_COLUMN = "flex flex-col items-right text-right max-w-[8rem]";
export type OnChainTx = {
txid: string;
received: number;
sent: number;
fee?: number;
confirmation_time?: {
Confirmed?: {
height: number;
time: number;
};
};
labels: string[];
};
export type UtxoItem = {
outpoint: string;
txout: {
value: number;
script_pubkey: string;
};
keychain: string;
is_spent: boolean;
redshifted?: boolean;
};
export type ActivityItem = {
kind: HackActivityType;
id: string;
amount_sats: number;
inbound: boolean;
labels: string[];
contacts: Contact[];
last_updated: number;
};
function UnifiedActivityItem(props: {
item: ActivityItem;
onClick: (id: string, kind: HackActivityType) => void;
}) {
const click = () => {
props.onClick(
props.item.id,
props.item.kind as unknown as HackActivityType
);
};
return (
<ActivityItem
// This is actually the ActivityType enum but wasm is hard
kind={props.item.kind as unknown as HackActivityType}
labels={props.item.labels}
contacts={props.item.contacts}
// FIXME: is this something we can put into node logic?
amount={props.item.amount_sats || 0}
date={props.item.last_updated}
positive={props.item.inbound}
onClick={click}
/>
);
}
export function CombinedActivity(props: { limit?: number }) {
const [state, _actions] = useMegaStore();
const i18n = useI18n();
const [detailsOpen, setDetailsOpen] = createSignal(false);
const [detailsKind, setDetailsKind] = createSignal<HackActivityType>();
const [detailsId, setDetailsId] = createSignal("");
function openDetailsModal(id: string, kind: HackActivityType) {
console.log("Opening details modal: ", id, kind);
// Some old channels don't have a channel id in the activity list
if (!id) {
console.warn("No id provided to openDetailsModal");
return;
}
setDetailsId(id);
setDetailsKind(kind);
setDetailsOpen(true);
}
async function fetchActivity() {
return await state.mutiny_wallet?.get_activity();
}
const [activity, { refetch }] = createResource(fetchActivity, {
storage: createDeepSignal
});
createEffect(() => {
// Should re-run after every sync
if (!state.is_syncing) {
refetch();
}
});
return (
<Show
when={activity.state === "ready" || activity.state === "refreshing"}
fallback={<LoadingShimmer />}
>
<Show when={detailsId() && detailsKind()}>
<DetailsIdModal
open={detailsOpen()}
kind={detailsKind()}
id={detailsId()}
setOpen={setDetailsOpen}
/>
</Show>
<Switch>
<Match when={activity.latest.length === 0}>
<div class="w-full text-center pb-4">
<NiceP>
{i18n.t(
"activity.receive_some_sats_to_get_started"
)}
</NiceP>
</div>
</Match>
<Match
when={props.limit && activity.latest.length > props.limit}
>
<For each={activity.latest.slice(0, props.limit)}>
{(activityItem) => (
<UnifiedActivityItem
item={activityItem}
onClick={openDetailsModal}
/>
)}
</For>
</Match>
<Match when={activity.latest.length >= 0}>
<For each={activity.latest}>
{(activityItem) => (
<UnifiedActivityItem
item={activityItem}
onClick={openDetailsModal}
/>
)}
</For>
</Match>
</Switch>
<Show when={props.limit && activity.latest.length > 0}>
<A
href="/activity"
class="text-m-red active:text-m-red/80 font-semibold no-underline self-center"
>
{i18n.t("activity.view_all")}
</A>
</Show>
</Show>
);
}

View File

@@ -10,7 +10,7 @@ import { generateGradient } from "~/utils/gradientHash";
import { useMegaStore } from "~/state/megaStore";
import { Contact } from "@mutinywallet/mutiny-wasm";
import { useI18n } from "~/i18n/context";
import { AmountFiat, AmountSats } from "~/components/Amount";
import { AmountFiat, AmountSats } from "~/components";
export const ActivityAmount: ParentComponent<{
amount: string;

View File

@@ -1,8 +1,7 @@
import { Match, ParentComponent, Show, Switch, createMemo } from "solid-js";
import { Card, VStack } from "~/components/layout";
import { Card, VStack, AmountEditable } from "~/components";
import { useMegaStore } from "~/state/megaStore";
import { satsToUsd } from "~/utils/conversions";
import { AmountEditable } from "./AmountEditable";
import { useI18n } from "~/i18n/context";
const noop = () => {

View File

@@ -9,18 +9,15 @@ import {
Switch,
Match
} from "solid-js";
import { Button } from "~/components/layout";
import { Button, InlineAmount, InfoBox, FeesModal } from "~/components";
import { useMegaStore } from "~/state/megaStore";
import { satsToUsd, usdToSats } from "~/utils/conversions";
import { Dialog } from "@kobalte/core";
import close from "~/assets/icons/close.svg";
import pencil from "~/assets/icons/pencil.svg";
import currencySwap from "~/assets/icons/currency-swap.svg";
import { InlineAmount } from "./AmountCard";
import { DIALOG_CONTENT, DIALOG_POSITIONER } from "~/styles/dialogs";
import { InfoBox } from "./InfoBox";
import { Network } from "~/logic/mutinyWalletSetup";
import { FeesModal } from "./MoreInfoModal";
import { useI18n } from "~/i18n/context";
import { useNavigate } from "solid-start";

View File

@@ -1,23 +1,29 @@
import { DefaultMain, SafeArea, VStack, Card } from "~/components/layout";
import BalanceBox, { LoadingShimmer } from "~/components/BalanceBox";
import NavBar from "~/components/NavBar";
import ReloadPrompt from "~/components/Reload";
import {
DefaultMain,
SafeArea,
VStack,
Card,
LoadingShimmer,
BalanceBox,
ReloadPrompt,
NavBar,
OnboardWarning,
CombinedActivity,
BetaWarningModal,
PendingNwc,
DecryptDialog,
LoadingIndicator
} from "~/components";
import { A } from "solid-start";
import { OnboardWarning } from "~/components/OnboardWarning";
import { CombinedActivity } from "./Activity";
import { useMegaStore } from "~/state/megaStore";
import { Match, Show, Suspense, Switch } from "solid-js";
import { BetaWarningModal } from "~/components/BetaWarningModal";
import settings from "~/assets/icons/settings.svg";
import pixelLogo from "~/assets/mutiny-pixel-logo.png";
import plusLogo from "~/assets/mutiny-plus-logo.png";
import { PendingNwc } from "./PendingNwc";
import { DecryptDialog } from "./DecryptDialog";
import { LoadingIndicator } from "./LoadingIndicator";
import { FeedbackLink } from "~/routes/Feedback";
import { useI18n } from "~/i18n/context";
export default function App() {
export function App() {
const i18n = useI18n();
const [state, _actions] = useMegaStore();

View File

@@ -1,11 +1,16 @@
import { Match, Show, Switch } from "solid-js";
import { Button, FancyCard, Indicator } from "~/components/layout";
import {
Button,
FancyCard,
Indicator,
AmountSats,
AmountFiat,
InfoBox
} from "~/components";
import { useMegaStore } from "~/state/megaStore";
import { AmountSats, AmountFiat } from "./Amount";
import { A, useNavigate } from "solid-start";
import shuffle from "~/assets/icons/shuffle.svg";
import { useI18n } from "~/i18n/context";
import { InfoBox } from "./InfoBox";
export function LoadingShimmer() {
return (
@@ -23,7 +28,7 @@ export function LoadingShimmer() {
const STYLE =
"px-2 py-1 rounded-xl text-sm flex gap-2 items-center font-semibold";
export default function BalanceBox(props: { loading?: boolean }) {
export function BalanceBox(props: { loading?: boolean }) {
const [state, _actions] = useMegaStore();
const i18n = useI18n();

View File

@@ -1,7 +1,12 @@
import { Dialog } from "@kobalte/core";
import { ParentComponent, createSignal } from "solid-js";
import { DIALOG_CONTENT, DIALOG_POSITIONER, OVERLAY } from "./DetailsModal";
import { ModalCloseButton, SmallHeader } from "./layout";
import {
DIALOG_CONTENT,
DIALOG_POSITIONER,
OVERLAY,
ModalCloseButton,
SmallHeader
} from "~/components";
import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore";
import { ExternalLink } from "@mutinywallet/ui";

View File

@@ -1,10 +1,13 @@
import { Match, Switch, createSignal } from "solid-js";
import { SmallHeader, TinyButton } from "~/components/layout";
import { Dialog } from "@kobalte/core";
import close from "~/assets/icons/close.svg";
import { SubmitHandler } from "@modular-forms/solid";
import { ContactForm } from "./ContactForm";
import { ContactFormValues } from "./ContactViewer";
import {
ContactFormValues,
ContactForm,
SmallHeader,
TinyButton
} from "~/components";
import { DIALOG_CONTENT, DIALOG_POSITIONER } from "~/styles/dialogs";
import { useI18n } from "~/i18n/context";

View File

@@ -1,7 +1,11 @@
import { SubmitHandler, createForm, required } from "@modular-forms/solid";
import { Button, LargeHeader, VStack } from "~/components/layout";
import { TextField } from "~/components/layout/TextField";
import { ContactFormValues } from "./ContactViewer";
import {
Button,
LargeHeader,
VStack,
TextField,
ContactFormValues
} from "~/components";
import { useI18n } from "~/i18n/context";
export function ContactForm(props: {

View File

@@ -1,10 +1,15 @@
import { Match, Switch, createSignal } from "solid-js";
import { Button, Card, NiceP, SmallHeader } from "~/components/layout";
import {
Button,
Card,
NiceP,
SmallHeader,
ContactForm,
showToast
} from "~/components";
import { Dialog } from "@kobalte/core";
import close from "~/assets/icons/close.svg";
import { SubmitHandler } from "@modular-forms/solid";
import { ContactForm } from "./ContactForm";
import { showToast } from "./Toaster";
import { Contact } from "@mutinywallet/mutiny-wasm";
import { DIALOG_CONTENT, DIALOG_POSITIONER } from "~/styles/dialogs";
import { useI18n } from "~/i18n/context";

View File

@@ -1,7 +1,5 @@
import { Show, createSignal } from "solid-js";
import { Button, SimpleDialog } from "~/components/layout";
import { TextField } from "~/components/layout/TextField";
import { InfoBox } from "~/components/InfoBox";
import { Button, SimpleDialog, TextField, InfoBox } from "~/components";
import { useMegaStore } from "~/state/megaStore";
import eify from "~/utils/eify";
import { A } from "solid-start";

View File

@@ -1,8 +1,6 @@
import initMutinyWallet, { MutinyWallet } from "@mutinywallet/mutiny-wasm";
import { createSignal } from "solid-js";
import { ConfirmDialog } from "~/components/Dialog";
import { Button } from "~/components/layout";
import { showToast } from "~/components/Toaster";
import { ConfirmDialog, Button, showToast } from "~/components";
import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore";
import eify from "~/utils/eify";

View File

@@ -1,510 +0,0 @@
import { Dialog } from "@kobalte/core";
import {
For,
Match,
ParentComponent,
Show,
Suspense,
Switch,
createEffect,
createMemo,
createResource
} from "solid-js";
import { Hr, ModalCloseButton, TinyButton, VStack } from "~/components/layout";
import { MutinyChannel, MutinyInvoice } from "@mutinywallet/mutiny-wasm";
import { OnChainTx } from "./Activity";
import bolt from "~/assets/icons/bolt-black.svg";
import chain from "~/assets/icons/chain-black.svg";
import copyIcon from "~/assets/icons/copy.svg";
import shuffle from "~/assets/icons/shuffle-black.svg";
import { ActivityAmount, HackActivityType } from "./ActivityItem";
import { CopyButton, TruncateMiddle } from "./ShareCard";
import { prettyPrintTime } from "~/utils/prettyPrintTime";
import { useMegaStore } from "~/state/megaStore";
import { MutinyTagItem, tagToMutinyTag } from "~/utils/tags";
import { useCopy } from "~/utils/useCopy";
import mempoolTxUrl from "~/utils/mempoolTxUrl";
import { Network } from "~/logic/mutinyWalletSetup";
import { AmountSmall } from "./Amount";
import { ExternalLink } from "@mutinywallet/ui";
import { InfoBox } from "./InfoBox";
import { useI18n } from "~/i18n/context";
type ChannelClosure = {
channel_id: string;
node_id: string;
reason: string;
timestamp: number;
};
export const OVERLAY = "fixed inset-0 z-50 bg-black/50 backdrop-blur-sm";
export const DIALOG_POSITIONER =
"fixed inset-0 z-50 flex items-center justify-center";
export const DIALOG_CONTENT =
"max-w-[500px] w-[90vw] max-h-[100dvh] overflow-y-scroll disable-scrollbars mx-4 p-4 bg-neutral-800/80 backdrop-blur-md shadow-xl rounded-xl border border-white/10";
function LightningHeader(props: {
info: MutinyInvoice;
tags: MutinyTagItem[];
}) {
const i18n = useI18n();
const [state, _actions] = useMegaStore();
return (
<div class="flex flex-col items-center gap-4">
<div class="p-4 bg-neutral-100 rounded-full">
<img src={bolt} alt="lightning bolt" class="w-8 h-8" />
</div>
<h1 class="uppercase font-semibold">
{props.info.inbound
? i18n.t("modals.transaction_details.lightning_receive")
: i18n.t("modals.transaction_details.lightning_send")}
</h1>
<ActivityAmount
center
amount={props.info.amount_sats?.toString() ?? "0"}
price={state.price}
positive={props.info.inbound}
/>
<For each={props.tags}>
{(tag) => (
<TinyButton
tag={tag}
onClick={() => {
// noop
}}
>
{tag.name}
</TinyButton>
)}
</For>
</div>
);
}
function OnchainHeader(props: {
info: OnChainTx;
tags: MutinyTagItem[];
kind?: HackActivityType;
}) {
const i18n = useI18n();
const [state, _actions] = useMegaStore();
const isSend = () => {
return props.info.sent > props.info.received;
};
const amount = () => {
if (isSend()) {
return (props.info.sent - props.info.received).toString();
} else {
return (props.info.received - props.info.sent).toString();
}
};
return (
<div class="flex flex-col items-center gap-4">
<div class="p-4 bg-neutral-100 rounded-full">
<Switch>
<Match
when={
props.kind === "ChannelOpen" ||
props.kind === "ChannelClose"
}
>
<img src={shuffle} alt="swap" class="w-8 h-8" />
</Match>
<Match when={true}>
<img src={chain} alt="blockchain" class="w-8 h-8" />
</Match>
</Switch>
</div>
<h1 class="uppercase font-semibold">
{props.kind === "ChannelOpen"
? i18n.t("modals.transaction_details.channel_open")
: props.kind === "ChannelClose"
? i18n.t("modals.transaction_details.channel_close")
: isSend()
? i18n.t("modals.transaction_details.onchain_send")
: i18n.t("modals.transaction_details.onchain_receive")}
</h1>
<Show when={props.kind !== "ChannelClose"}>
<ActivityAmount
center
amount={amount() ?? "0"}
price={state.price}
positive={!isSend()}
/>
</Show>
<For each={props.tags}>
{(tag) => (
<TinyButton
tag={tag}
onClick={() => {
// noop
}}
>
{tag.name}
</TinyButton>
)}
</For>
</div>
);
}
export const KeyValue: ParentComponent<{ key: string }> = (props) => {
return (
<li class="flex justify-between items-center gap-4">
<span class="uppercase font-semibold whitespace-nowrap text-sm">
{props.key}
</span>
<span class="font-light">{props.children}</span>
</li>
);
};
export function MiniStringShower(props: { text: string }) {
const [copy, copied] = useCopy({ copiedTimeout: 1000 });
return (
<div class="w-full grid gap-1 grid-cols-[minmax(0,_1fr)_auto]">
<TruncateMiddle text={props.text} />
{/* <pre class="truncate text-neutral-300 font-light">{props.text}</pre> */}
<button
class="w-[1.5rem] p-1"
classList={{ "bg-m-green rounded": copied() }}
onClick={() => copy(props.text)}
>
<img src={copyIcon} alt="copy" class="w-4 h-4" />
</button>
</div>
);
}
function LightningDetails(props: { info: MutinyInvoice }) {
const i18n = useI18n();
return (
<VStack>
<ul class="flex flex-col gap-4">
<KeyValue key={i18n.t("modals.transaction_details.status")}>
<span class="text-neutral-300">
{props.info.paid
? i18n.t("modals.transaction_details.paid")
: i18n.t("modals.transaction_details.unpaid")}
</span>
</KeyValue>
<KeyValue key={i18n.t("modals.transaction_details.when")}>
<span class="text-neutral-300">
{prettyPrintTime(Number(props.info.last_updated))}
</span>
</KeyValue>
<Show when={props.info.description}>
<KeyValue
key={i18n.t("modals.transaction_details.description")}
>
<span class="text-neutral-300 truncate">
{props.info.description}
</span>
</KeyValue>
</Show>
<KeyValue key={i18n.t("modals.transaction_details.fees")}>
<span class="text-neutral-300">
<AmountSmall amountSats={props.info.fees_paid} />
</span>
</KeyValue>
<KeyValue key={i18n.t("modals.transaction_details.bolt11")}>
<MiniStringShower text={props.info.bolt11 ?? ""} />
</KeyValue>
<KeyValue
key={i18n.t("modals.transaction_details.payment_hash")}
>
<MiniStringShower text={props.info.payment_hash ?? ""} />
</KeyValue>
<KeyValue key={i18n.t("modals.transaction_details.preimage")}>
<MiniStringShower text={props.info.preimage ?? ""} />
</KeyValue>
</ul>
</VStack>
);
}
function OnchainDetails(props: { info: OnChainTx; kind?: HackActivityType }) {
const i18n = useI18n();
const [state, _actions] = useMegaStore();
const confirmationTime = () => {
return props.info.confirmation_time?.Confirmed?.time;
};
const network = state.mutiny_wallet?.get_network() as Network;
// Can return nothing if the channel is already closed
const [channelInfo] = createResource(async () => {
if (props.kind === "ChannelOpen") {
try {
const channels =
await (state.mutiny_wallet?.list_channels() as Promise<
MutinyChannel[]
>);
const channel = channels.find((channel) =>
channel.outpoint?.startsWith(props.info.txid)
);
return channel;
} catch (e) {
console.error(e);
}
} else {
return undefined;
}
});
return (
<VStack>
{/* <pre>{JSON.stringify(channelInfo() || "", null, 2)}</pre> */}
<ul class="flex flex-col gap-4">
<KeyValue key={i18n.t("modals.transaction_details.status")}>
<span class="text-neutral-300">
{confirmationTime()
? i18n.t("modals.transaction_details.confirmed")
: i18n.t("modals.transaction_details.unconfirmed")}
</span>
</KeyValue>
<Show when={confirmationTime()}>
<KeyValue key={i18n.t("modals.transaction_details.when")}>
<span class="text-neutral-300">
{confirmationTime()
? prettyPrintTime(Number(confirmationTime()))
: "Pending"}
</span>
</KeyValue>
</Show>
<Show when={props.info.fee && props.info.fee > 0}>
<KeyValue key={i18n.t("modals.transaction_details.fee")}>
<span class="text-neutral-300">
<AmountSmall amountSats={props.info.fee} />
</span>
</KeyValue>
</Show>
<KeyValue key={i18n.t("modals.transaction_details.txid")}>
<MiniStringShower text={props.info.txid ?? ""} />
</KeyValue>
<Switch>
<Match when={props.kind === "ChannelOpen" && channelInfo()}>
<KeyValue
key={i18n.t("modals.transaction_details.balance")}
>
<span class="text-neutral-300">
<AmountSmall
amountSats={channelInfo()?.balance}
/>
</span>
</KeyValue>
<KeyValue
key={i18n.t("modals.transaction_details.reserve")}
>
<span class="text-neutral-300">
<AmountSmall
amountSats={channelInfo()?.reserve}
/>
</span>
</KeyValue>
<KeyValue
key={i18n.t("modals.transaction_details.peer")}
>
<span class="text-neutral-300">
<MiniStringShower
text={channelInfo()?.peer ?? ""}
/>
</span>
</KeyValue>
</Match>
<Match when={props.kind === "ChannelOpen"}>
<InfoBox accent="blue">
{i18n.t("modals.transaction_details.no_details")}
</InfoBox>
</Match>
</Switch>
</ul>
<div class="text-center">
<ExternalLink href={mempoolTxUrl(props.info.txid, network)}>
{i18n.t("common.view_transaction")}
</ExternalLink>
</div>
</VStack>
);
}
function ChannelCloseDetails(props: { info: ChannelClosure }) {
const i18n = useI18n();
return (
<VStack>
{/* <pre>{JSON.stringify(props.info.value, null, 2)}</pre> */}
<ul class="flex flex-col gap-4">
<KeyValue key={i18n.t("modals.transaction_details.channel_id")}>
<MiniStringShower text={props.info.channel_id ?? ""} />
</KeyValue>
<Show when={props.info.timestamp}>
<KeyValue key={i18n.t("modals.transaction_details.when")}>
<span class="text-neutral-300">
{props.info.timestamp
? prettyPrintTime(Number(props.info.timestamp))
: i18n.t("common.pending")}
</span>
</KeyValue>
</Show>
<KeyValue key={i18n.t("modals.transaction_details.reason")}>
<p class="text-neutral-300 text-right">
{props.info.reason ?? ""}
</p>
</KeyValue>
</ul>
</VStack>
);
}
export function DetailsIdModal(props: {
open: boolean;
kind?: HackActivityType;
id: string;
setOpen: (open: boolean) => void;
}) {
const i18n = useI18n();
const [state, _actions] = useMegaStore();
const id = () => props.id;
const kind = () => props.kind;
// TODO: is there a cleaner way to do refetch when id changes?
const [data, { refetch }] = createResource(async () => {
try {
if (kind() === "Lightning") {
console.debug("reading invoice: ", id());
const invoice = await state.mutiny_wallet?.get_invoice_by_hash(
id()
);
return invoice;
} else if (kind() === "ChannelClose") {
console.debug("reading channel close: ", id());
const closeItem =
await state.mutiny_wallet?.get_channel_closure(id());
return closeItem;
} else {
console.debug("reading tx: ", id());
const tx = await state.mutiny_wallet?.get_transaction(id());
return tx;
}
} catch (e) {
console.error(e);
return undefined;
}
});
const tags = createMemo(() => {
if (data() && data().labels && data().labels.length > 0) {
try {
const contact = state.mutiny_wallet?.get_contact(
data().labels[0]
);
if (contact) {
return [tagToMutinyTag(contact)];
} else {
return [];
}
} catch (e) {
console.error(e);
return [];
}
} else {
return [];
}
});
createEffect(() => {
if (props.id && props.kind && props.open) {
refetch();
}
});
const json = createMemo(() => JSON.stringify(data() || "", null, 2));
return (
<Dialog.Root open={props.open} onOpenChange={props.setOpen}>
<Dialog.Portal>
<Dialog.Overlay class={OVERLAY} />
<div class={DIALOG_POSITIONER}>
<Dialog.Content class={DIALOG_CONTENT}>
<Suspense>
<div class="flex justify-between mb-2">
<div />
<Dialog.CloseButton>
<ModalCloseButton />
</Dialog.CloseButton>
</div>
<Dialog.Title>
<Switch>
<Match when={props.kind === "Lightning"}>
<LightningHeader
info={data() as MutinyInvoice}
tags={tags()}
/>
</Match>
<Match
when={
props.kind === "OnChain" ||
props.kind === "ChannelOpen" ||
props.kind === "ChannelClose"
}
>
<OnchainHeader
info={data() as OnChainTx}
tags={tags()}
kind={props.kind}
/>
</Match>
</Switch>
</Dialog.Title>
<Hr />
<Dialog.Description class="flex flex-col gap-4">
<Switch>
<Match when={props.kind === "Lightning"}>
<LightningDetails
info={data() as MutinyInvoice}
/>
</Match>
<Match
when={
props.kind === "OnChain" ||
props.kind === "ChannelOpen"
}
>
<OnchainDetails
info={data() as OnChainTx}
kind={props.kind}
/>
</Match>
<Match when={props.kind === "ChannelClose"}>
<ChannelCloseDetails
info={data() as ChannelClosure}
/>
</Match>
</Switch>
<Show when={props.kind !== "ChannelClose"}>
<div class="flex justify-center">
<CopyButton
title={i18n.t("common.copy")}
text={json()}
/>
</div>
</Show>
</Dialog.Description>
</Suspense>
</Dialog.Content>
</div>
</Dialog.Portal>
</Dialog.Root>
);
}

View File

@@ -1,6 +1,6 @@
import { Dialog } from "@kobalte/core";
import { ParentComponent } from "solid-js";
import { Button, SmallHeader } from "./layout";
import { Button, SmallHeader } from "~/components";
import { useI18n } from "~/i18n/context";
const OVERLAY = "fixed inset-0 z-50 bg-black/50 backdrop-blur-sm";

View File

@@ -10,7 +10,7 @@ import {
import { ExternalLink } from "@mutinywallet/ui";
import { useI18n } from "~/i18n/context";
export default function ErrorDisplay(props: { error: Error }) {
export function ErrorDisplay(props: { error: Error }) {
const i18n = useI18n();
return (
<SafeArea>

View File

@@ -1,6 +1,5 @@
import { useI18n } from "~/i18n/context";
import { AmountFiat, AmountSats } from "~/components/Amount";
import { FeesModal } from "~/components/MoreInfoModal";
import { AmountFiat, AmountSats, FeesModal } from "~/components";
export function Fee(props: { amountSats?: bigint | number }) {
const i18n = useI18n();

View File

@@ -1,5 +1,5 @@
import { ParentComponent, Show, createResource } from "solid-js";
import { I18nContext } from "../i18n/context";
import { I18nContext } from "~/i18n/context";
import i18next from "i18next";
import i18nConfig from "~/i18n/config";

View File

@@ -4,17 +4,17 @@ import {
InnerCard,
NiceP,
SimpleDialog,
VStack
} from "~/components/layout";
VStack,
ConfirmDialog,
showToast,
InfoBox,
TextField
} from "~/components";
import { Show, createSignal } from "solid-js";
import eify from "~/utils/eify";
import { showToast } from "./Toaster";
import { downloadTextFile } from "~/utils/download";
import { createFileUploader } from "@solid-primitives/upload";
import { ConfirmDialog } from "./Dialog";
import initMutinyWallet, { MutinyWallet } from "@mutinywallet/mutiny-wasm";
import { InfoBox } from "./InfoBox";
import { TextField } from "./layout/TextField";
import { useI18n } from "~/i18n/context";
export function ImportExport(props: { emergency?: boolean }) {

View File

@@ -2,8 +2,7 @@ import { Match, Show, Switch } from "solid-js";
import { QRCodeSVG } from "solid-qr-code";
import { ReceiveFlavor } from "~/routes/Receive";
import { useCopy } from "~/utils/useCopy";
import { AmountSats, AmountFiat } from "./Amount";
import { TruncateMiddle } from "./ShareCard";
import { AmountSats, AmountFiat, TruncateMiddle } from "~/components";
import copyBlack from "~/assets/icons/copy-black.svg";
import shareBlack from "~/assets/icons/share-black.svg";
import chainBlack from "~/assets/icons/chain-black.svg";

View File

@@ -1,12 +1,13 @@
import { Dialog } from "@kobalte/core";
import { JSX, createMemo } from "solid-js";
import { ModalCloseButton, SmallHeader } from "~/components/layout";
import {
ModalCloseButton,
SmallHeader,
DIALOG_CONTENT,
DIALOG_POSITIONER,
OVERLAY
} from "~/components/DetailsModal";
import { CopyButton } from "./ShareCard";
OVERLAY,
CopyButton
} from "~/components";
import { useI18n } from "~/i18n/context";
export function JsonModal(props: {

View File

@@ -1,5 +1,4 @@
import { useMegaStore } from "~/state/megaStore";
import { Hr, Button, InnerCard, VStack } from "~/components/layout";
import {
For,
Match,
@@ -13,14 +12,20 @@ import { MutinyChannel, MutinyPeer } from "@mutinywallet/mutiny-wasm";
import { Collapsible, TextField } from "@kobalte/core";
import mempoolTxUrl from "~/utils/mempoolTxUrl";
import eify from "~/utils/eify";
import { ConfirmDialog } from "~/components/Dialog";
import { showToast } from "~/components/Toaster";
import {
ConfirmDialog,
Hr,
Button,
InnerCard,
VStack,
showToast,
Restart,
ResyncOnchain,
ResetRouter,
MiniStringShower
} from "~/components";
import { Network } from "~/logic/mutinyWalletSetup";
import { ExternalLink } from "@mutinywallet/ui";
import { Restart } from "./Restart";
import { ResyncOnchain } from "./ResyncOnchain";
import { ResetRouter } from "./ResetRouter";
import { MiniStringShower } from "./DetailsModal";
import { useI18n } from "~/i18n/context";
// TODO: hopefully I don't have to maintain this type forever but I don't know how to pass it around otherwise
@@ -451,7 +456,7 @@ function ListNodes() {
);
}
export default function KitchenSink() {
export function KitchenSink() {
return (
<>
<ListNodes />

View File

@@ -1,5 +1,5 @@
import { MutinyWallet } from "@mutinywallet/mutiny-wasm";
import { InnerCard, NiceP, VStack, Button } from "~/components/layout";
import { Button, InnerCard, NiceP, VStack } from "~/components";
import { useI18n } from "~/i18n/context";
import { downloadTextFile } from "~/utils/download";

View File

@@ -1,7 +1,12 @@
import { Dialog } from "@kobalte/core";
import { ParentComponent, createSignal, JSXElement } from "solid-js";
import { DIALOG_CONTENT, DIALOG_POSITIONER, OVERLAY } from "./DetailsModal";
import { ModalCloseButton, SmallHeader } from "./layout";
import {
DIALOG_CONTENT,
DIALOG_POSITIONER,
OVERLAY,
ModalCloseButton,
SmallHeader
} from "~/components";
import { ExternalLink } from "@mutinywallet/ui";
import help from "~/assets/icons/help.svg";
import { useI18n } from "~/i18n/context";

View File

@@ -39,7 +39,7 @@ function NavBarItem(props: {
);
}
export default function NavBar(props: { activeTab: ActiveTab }) {
export function NavBar(props: { activeTab: ActiveTab }) {
return (
<nav class="hidden md:block fixed shadow-none z-40 safe-bottom top-0 bottom-auto left-0 h-full">
<ul class="flex flex-col justify-start gap-4 px-4 mt-4">

View File

@@ -1,5 +1,5 @@
import { Show } from "solid-js";
import { ButtonLink, SmallHeader } from "./layout";
import { ButtonLink, SmallHeader } from "~/components";
import { useMegaStore } from "~/state/megaStore";
import save from "~/assets/icons/save.svg";
import { useI18n } from "~/i18n/context";

View File

@@ -1,6 +1,6 @@
import { NwcProfile } from "@mutinywallet/mutiny-wasm";
import { formatExpiration } from "~/utils/prettyPrintTime";
import { Card, VStack } from "./layout";
import { Card, VStack, ActivityAmount, InfoBox } from "~/components";
import { LoadingSpinner } from "@mutinywallet/ui";
import bolt from "~/assets/icons/bolt.svg";
import {
@@ -16,8 +16,6 @@ import { useMegaStore } from "~/state/megaStore";
import greenCheck from "~/assets/icons/green-check.svg";
import redClose from "~/assets/icons/red-close.svg";
import { ActivityAmount } from "./ActivityItem";
import { InfoBox } from "./InfoBox";
import eify from "~/utils/eify";
import { A } from "solid-start";
import { createDeepSignal } from "~/utils/deepSignal";

View File

@@ -8,7 +8,7 @@ import {
import QrScanner from "qr-scanner";
import { Capacitor } from "@capacitor/core";
export default function Scanner(props: { onResult: (result: string) => void }) {
export function Scanner(props: { onResult: (result: string) => void }) {
let container: HTMLVideoElement | undefined;
let scanner: QrScanner | undefined;

View File

@@ -1,9 +1,8 @@
import type { Component } from "solid-js";
import { Show } from "solid-js";
// eslint-disable-next-line import/no-unresolved
import { useRegisterSW } from "virtual:pwa-register/solid";
const ReloadPrompt: Component = () => {
export function ReloadPrompt() {
const {
offlineReady: [offlineReady, _setOfflineReady],
needRefresh: [needRefresh, _setNeedRefresh],
@@ -41,6 +40,4 @@ const ReloadPrompt: Component = () => {
</Card> */}
</Show>
);
};
export default ReloadPrompt;
}

View File

@@ -1,4 +1,4 @@
import { Button, InnerCard, NiceP, VStack } from "~/components/layout";
import { Button, InnerCard, NiceP, VStack } from "~/components";
import { useMegaStore } from "~/state/megaStore";
import { useI18n } from "~/i18n/context";

View File

@@ -1,5 +1,5 @@
import { createSignal } from "solid-js";
import { Button, InnerCard, NiceP, VStack } from "~/components/layout";
import { Button, InnerCard, NiceP, VStack } from "~/components";
import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore";

View File

@@ -1,4 +1,4 @@
import { Button, InnerCard, NiceP, VStack } from "~/components/layout";
import { Button, InnerCard, NiceP, VStack } from "~/components";
import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore";

View File

@@ -4,13 +4,13 @@ import {
LargeHeader,
NiceP,
SafeArea,
SmallHeader
} from "~/components/layout";
SmallHeader,
ImportExport,
Logs,
DeleteEverything
} from "~/components";
import { ExternalLink } from "@mutinywallet/ui";
import { Match, Switch } from "solid-js";
import { ImportExport } from "./ImportExport";
import { Logs } from "./Logs";
import { DeleteEverything } from "./DeleteEverything";
import { FeedbackLink } from "~/routes/Feedback";
import { useI18n } from "~/i18n/context";
@@ -25,7 +25,7 @@ function ErrorFooter() {
);
}
export default function SetupErrorDisplay(props: { initialError: Error }) {
export function SetupErrorDisplay(props: { initialError: Error }) {
// Error shouldn't be reactive, so we assign to it so it just gets rendered with the first value
const i18n = useI18n();
const error = props.initialError;

View File

@@ -1,4 +1,4 @@
import { Card, VStack } from "~/components/layout";
import { Card, VStack, JsonModal } from "~/components";
import { useCopy } from "~/utils/useCopy";
import copyIcon from "~/assets/icons/copy.svg";
import copyBlack from "~/assets/icons/copy-black.svg";
@@ -6,7 +6,6 @@ import shareIcon from "~/assets/icons/share.svg";
import shareBlack from "~/assets/icons/share-black.svg";
import eyeIcon from "~/assets/icons/eye.svg";
import { Show, createSignal } from "solid-js";
import { JsonModal } from "./JsonModal";
import { useI18n } from "~/i18n/context";
const STYLE =

View File

@@ -1,7 +1,7 @@
import { Select, createOptions } from "@thisbeyond/solid-select";
import "~/styles/solid-select.css";
import { For, Show, createMemo, createSignal, onMount } from "solid-js";
import { TinyButton } from "./layout";
import { TinyButton } from "~/components";
import { MutinyTagItem, sortByLastUsed } from "~/utils/tags";
import { useMegaStore } from "~/state/megaStore";

View File

@@ -1,7 +1,7 @@
import { Toast, toaster } from "@kobalte/core";
import { Portal } from "solid-js/web";
import close from "~/assets/icons/close.svg";
import { SmallHeader } from "./layout";
import { SmallHeader } from "~/components";
export function Toaster() {
return (

43
src/components/index.ts Normal file
View File

@@ -0,0 +1,43 @@
export * from "./layout";
export * from "./successfail";
export * from "./Activity";
export * from "./ActivityItem";
export * from "./Amount";
export * from "./AmountCard";
export * from "./AmountEditable";
export * from "./App";
export * from "./BalanceBox";
export * from "./BetaWarningModal";
export * from "./ContactEditor";
export * from "./ContactForm";
export * from "./ContactViewer";
export * from "./CopyableQR";
export * from "./DecryptDialog";
export * from "./DeleteEverything";
export * from "./DetailsModal";
export * from "./Dialog";
export * from "./ErrorDisplay";
export * from "./Fee";
export * from "./I18nProvider";
export * from "./ImportExport";
export * from "./InfoBox";
export * from "./IntegratedQR";
export * from "./JsonModal";
export * from "./KitchenSink";
export * from "./LoadingIndicator";
export * from "./Logs";
export * from "./MoreInfoModal";
export * from "./NavBar";
export * from "./OnboardWarning";
export * from "./PendingNwc";
export * from "./Reader";
export * from "./Reload";
export * from "./ResetRouter";
export * from "./Restart";
export * from "./ResyncOnchain";
export * from "./SeedWords";
export * from "./SetupErrorDisplay";
export * from "./ShareCard";
export * from "./TagEditor";
export * from "./Toaster";

View File

@@ -1,5 +1,5 @@
import { useLocation, useNavigate } from "solid-start";
import { BackButton } from "./BackButton";
import { BackButton } from "~/components";
import { useI18n } from "~/i18n/context";
type StateWithPrevious = {

View File

@@ -4,7 +4,7 @@ interface LinkifyProps {
initialText: string;
}
export default function Linkify(props: LinkifyProps): JSX.Element {
export function Linkify(props: LinkifyProps): JSX.Element {
// By naming this "initialText" we can prove to eslint that the props won't change
const text = props.initialText;
const links: (string | JSX.Element)[] = [];

View File

@@ -8,8 +8,6 @@ import {
createResource,
createSignal
} from "solid-js";
import Linkify from "./Linkify";
import { Button, ButtonLink } from "./Button";
import {
Collapsible,
Checkbox as KCheckbox,
@@ -23,13 +21,10 @@ import { generateGradient } from "~/utils/gradientHash";
import close from "~/assets/icons/close.svg";
import { A } from "solid-start";
import down from "~/assets/icons/down.svg";
import { DecryptDialog } from "../DecryptDialog";
import { LoadingIndicator } from "~/components/LoadingIndicator";
import { LoadingIndicator, DecryptDialog } from "~/components";
import { LoadingSpinner } from "@mutinywallet/ui";
import { useI18n } from "~/i18n/context";
export { Button, ButtonLink, Linkify };
export const SmallHeader: ParentComponent<{ class?: string }> = (props) => {
return (
<header class={`text-sm font-semibold uppercase ${props.class}`}>

View File

@@ -1,8 +1,8 @@
import { Progress } from "@kobalte/core";
import { SmallHeader } from ".";
import { SmallHeader } from "~/components";
import { useI18n } from "~/i18n/context";
export default function formatNumber(num: number) {
export function formatNumber(num: number) {
const map = [
{ suffix: "T", threshold: 1e12 },
{ suffix: "B", threshold: 1e9 },

View File

@@ -1,6 +1,6 @@
import { TextField as KTextField } from "@kobalte/core";
import { type JSX, Show, splitProps } from "solid-js";
import { TinyText } from ".";
import { TinyText } from "~/components";
export type TextFieldProps = {
name: string;

View File

@@ -0,0 +1,9 @@
export * from "./BackButton";
export * from "./BackLink";
export * from "./BackPop";
export * from "./Button";
export * from "./Linkify";
export * from "./Misc";
export * from "./ProgressBar";
export * from "./Radio";
export * from "./TextField";

View File

@@ -1,6 +1,6 @@
import { Dialog } from "@kobalte/core";
import { JSX } from "solid-js";
import { Button } from "~/components/layout";
import { Button } from "~/components";
import { DIALOG_CONTENT, DIALOG_POSITIONER } from "~/styles/dialogs";
import { useI18n } from "~/i18n/context";

View File

@@ -0,0 +1,3 @@
export * from "./MegaCheck";
export * from "./MegaEx";
export * from "./SuccessModal";