mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2025-12-23 09:04:26 +01:00
translation pass 2
This commit is contained in:
@@ -37,14 +37,14 @@
|
|||||||
"workbox-window": "^6.6.0"
|
"workbox-window": "^6.6.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mutinywallet/barcode-scanner": "5.0.0-beta.3",
|
|
||||||
"@capacitor/android": "^5.2.1",
|
"@capacitor/android": "^5.2.1",
|
||||||
"@capacitor/clipboard": "^5.0.6",
|
"@capacitor/clipboard": "^5.0.6",
|
||||||
"@capacitor/core": "^5.2.1",
|
"@capacitor/core": "^5.2.1",
|
||||||
"@kobalte/core": "^0.9.8",
|
"@kobalte/core": "^0.9.8",
|
||||||
"@kobalte/tailwindcss": "^0.5.0",
|
"@kobalte/tailwindcss": "^0.5.0",
|
||||||
"@mutinywallet/mutiny-wasm": "0.4.4",
|
|
||||||
"@modular-forms/solid": "^0.18.0",
|
"@modular-forms/solid": "^0.18.0",
|
||||||
|
"@mutinywallet/barcode-scanner": "5.0.0-beta.3",
|
||||||
|
"@mutinywallet/mutiny-wasm": "0.4.4",
|
||||||
"@mutinywallet/waila-wasm": "^0.2.1",
|
"@mutinywallet/waila-wasm": "^0.2.1",
|
||||||
"@solid-primitives/upload": "^0.0.111",
|
"@solid-primitives/upload": "^0.0.111",
|
||||||
"@solidjs/meta": "^0.28.5",
|
"@solidjs/meta": "^0.28.5",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Card, VStack } from "~/components/layout";
|
|||||||
import { useMegaStore } from "~/state/megaStore";
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
import { satsToUsd } from "~/utils/conversions";
|
import { satsToUsd } from "~/utils/conversions";
|
||||||
import { AmountEditable } from "./AmountEditable";
|
import { AmountEditable } from "./AmountEditable";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
const noop = () => {
|
const noop = () => {
|
||||||
// do nothing
|
// do nothing
|
||||||
@@ -25,6 +26,7 @@ export const InlineAmount: ParentComponent<{
|
|||||||
sign?: string;
|
sign?: string;
|
||||||
fiat?: boolean;
|
fiat?: boolean;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
|
const i18n = useI18n();
|
||||||
const prettyPrint = createMemo(() => {
|
const prettyPrint = createMemo(() => {
|
||||||
const parsed = Number(props.amount);
|
const parsed = Number(props.amount);
|
||||||
if (isNaN(parsed)) {
|
if (isNaN(parsed)) {
|
||||||
@@ -39,12 +41,15 @@ export const InlineAmount: ParentComponent<{
|
|||||||
{props.sign ? `${props.sign} ` : ""}
|
{props.sign ? `${props.sign} ` : ""}
|
||||||
{props.fiat ? "$" : ""}
|
{props.fiat ? "$" : ""}
|
||||||
{prettyPrint()}{" "}
|
{prettyPrint()}{" "}
|
||||||
<span class="text-sm">{props.fiat ? "USD" : "SATS"}</span>
|
<span class="text-sm">
|
||||||
|
{props.fiat ? i18n.t("common.usd") : i18n.t("common.sats")}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function USDShower(props: { amountSats: string; fee?: string }) {
|
function USDShower(props: { amountSats: string; fee?: string }) {
|
||||||
|
const i18n = useI18n();
|
||||||
const [state, _] = useMegaStore();
|
const [state, _] = useMegaStore();
|
||||||
const amountInUsd = () =>
|
const amountInUsd = () =>
|
||||||
satsToUsd(state.price, add(props.amountSats, props.fee), true);
|
satsToUsd(state.price, add(props.amountSats, props.fee), true);
|
||||||
@@ -54,7 +59,7 @@ function USDShower(props: { amountSats: string; fee?: string }) {
|
|||||||
<KeyValue gray key="">
|
<KeyValue gray key="">
|
||||||
<div class="self-end">
|
<div class="self-end">
|
||||||
~{amountInUsd()}
|
~{amountInUsd()}
|
||||||
<span class="text-sm">USD</span>
|
<span class="text-sm">{i18n.t("common.usd")}</span>
|
||||||
</div>
|
</div>
|
||||||
</KeyValue>
|
</KeyValue>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -69,14 +69,20 @@ function SingleDigitButton(props: {
|
|||||||
onClear: () => void;
|
onClear: () => void;
|
||||||
fiat: boolean;
|
fiat: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
const i18n = useI18n();
|
||||||
let holdTimer: number;
|
let holdTimer: number;
|
||||||
const holdThreshold = 500;
|
const holdThreshold = 500;
|
||||||
|
|
||||||
function onHold() {
|
function onHold() {
|
||||||
|
if (
|
||||||
|
props.character === "DEL" ||
|
||||||
|
props.character === i18n.t("char.del")
|
||||||
|
) {
|
||||||
holdTimer = setTimeout(() => {
|
holdTimer = setTimeout(() => {
|
||||||
props.onClear();
|
props.onClear();
|
||||||
}, holdThreshold);
|
}, holdThreshold);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function endHold() {
|
function endHold() {
|
||||||
clearTimeout(holdTimer);
|
clearTimeout(holdTimer);
|
||||||
@@ -130,7 +136,7 @@ function BigScalingText(props: { text: string; fiat: boolean }) {
|
|||||||
>
|
>
|
||||||
{props.text}
|
{props.text}
|
||||||
<span class="text-xl">
|
<span class="text-xl">
|
||||||
{props.fiat ? "USD" : `${i18n.t("common.sats")}`}
|
{props.fiat ? i18n.t("common.usd") : i18n.t("common.sats")}
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
);
|
);
|
||||||
@@ -142,7 +148,7 @@ function SmallSubtleAmount(props: { text: string; fiat: boolean }) {
|
|||||||
<h2 class="flex flex-row items-end text-xl font-light text-neutral-400">
|
<h2 class="flex flex-row items-end text-xl font-light text-neutral-400">
|
||||||
~{props.text}
|
~{props.text}
|
||||||
<span class="text-base">
|
<span class="text-base">
|
||||||
{props.fiat ? "USD" : `${i18n.t("common.sats")}`}
|
{props.fiat ? i18n.t("common.usd") : `${i18n.t("common.sats")}`}
|
||||||
</span>
|
</span>
|
||||||
<img
|
<img
|
||||||
class={"pl-[4px] pb-[4px] hover:cursor-pointer"}
|
class={"pl-[4px] pb-[4px] hover:cursor-pointer"}
|
||||||
@@ -214,7 +220,7 @@ export const AmountEditable: ParentComponent<{
|
|||||||
"9",
|
"9",
|
||||||
".",
|
".",
|
||||||
"0",
|
"0",
|
||||||
`${i18n.t("char.del")}`
|
i18n.t("char.del")
|
||||||
];
|
];
|
||||||
|
|
||||||
const displaySats = () => toDisplayHandleNaN(localSats(), false);
|
const displaySats = () => toDisplayHandleNaN(localSats(), false);
|
||||||
@@ -243,9 +249,13 @@ export const AmountEditable: ParentComponent<{
|
|||||||
if ((state.balance?.lightning || 0n) === 0n) {
|
if ((state.balance?.lightning || 0n) === 0n) {
|
||||||
const network = state.mutiny_wallet?.get_network() as Network;
|
const network = state.mutiny_wallet?.get_network() as Network;
|
||||||
if (network === "bitcoin") {
|
if (network === "bitcoin") {
|
||||||
return "Your first lightning receive needs to be 50,000 sats or greater. A setup fee will be deducted from the requested amount.";
|
return i18n.t("receive.amount_editable.receive_too_small", {
|
||||||
|
amount: "50,000"
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
return i18n.t("amount_editable_first_payment_10k_or_greater");
|
return i18n.t("receive.amount_editable.receive_too_small", {
|
||||||
|
amount: "10,000"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +265,7 @@ export const AmountEditable: ParentComponent<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (parsed > (inboundCapacity() || 0)) {
|
if (parsed > (inboundCapacity() || 0)) {
|
||||||
return "A lightning setup fee will be charged if paid over lightning.";
|
return i18n.t("receive.amount_editable.setup_fee_lightning");
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -269,10 +279,10 @@ export const AmountEditable: ParentComponent<{
|
|||||||
|
|
||||||
if (parsed >= 2099999997690000) {
|
if (parsed >= 2099999997690000) {
|
||||||
// If over 21 million bitcoin, warn that too much
|
// If over 21 million bitcoin, warn that too much
|
||||||
return i18n.t("more_than_21m");
|
return i18n.t("receive.amount_editable.more_than_21m");
|
||||||
} else if (parsed >= 4000000) {
|
} else if (parsed >= 4000000) {
|
||||||
// If over 4 million sats, warn that it's a beta bro
|
// If over 4 million sats, warn that it's a beta bro
|
||||||
return i18n.t("too_big_for_beta");
|
return i18n.t("receive.amount_editable.too_big_for_beta");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -285,7 +295,7 @@ export const AmountEditable: ParentComponent<{
|
|||||||
|
|
||||||
let sane;
|
let sane;
|
||||||
|
|
||||||
if (character === "DEL") {
|
if (character === "DEL" || character === i18n.t("char.del")) {
|
||||||
if (localValue().length <= 1) {
|
if (localValue().length <= 1) {
|
||||||
sane = "0";
|
sane = "0";
|
||||||
} else {
|
} else {
|
||||||
@@ -424,7 +434,7 @@ export const AmountEditable: ParentComponent<{
|
|||||||
when={localSats() !== "0"}
|
when={localSats() !== "0"}
|
||||||
fallback={
|
fallback={
|
||||||
<div class="inline-block font-semibold">
|
<div class="inline-block font-semibold">
|
||||||
{i18n.t("set_amount")}
|
{i18n.t("receive.amount_editable.set_amount")}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -540,7 +550,7 @@ export const AmountEditable: ParentComponent<{
|
|||||||
}}
|
}}
|
||||||
class="py-2 px-4 rounded-lg bg-white/10"
|
class="py-2 px-4 rounded-lg bg-white/10"
|
||||||
>
|
>
|
||||||
MAX
|
{i18n.t("receive.amount_editable.max")}
|
||||||
</button>
|
</button>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
@@ -561,7 +571,7 @@ export const AmountEditable: ParentComponent<{
|
|||||||
class="w-full flex-none"
|
class="w-full flex-none"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
>
|
>
|
||||||
{i18n.t("set_amount")}
|
{i18n.t("receive.amount_editable.set_amount")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import { createSignal } from "solid-js";
|
|||||||
import { ConfirmDialog } from "~/components/Dialog";
|
import { ConfirmDialog } from "~/components/Dialog";
|
||||||
import { Button } from "~/components/layout";
|
import { Button } from "~/components/layout";
|
||||||
import { showToast } from "~/components/Toaster";
|
import { showToast } from "~/components/Toaster";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
import { useMegaStore } from "~/state/megaStore";
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
import eify from "~/utils/eify";
|
import eify from "~/utils/eify";
|
||||||
|
|
||||||
export function DeleteEverything(props: { emergency?: boolean }) {
|
export function DeleteEverything(props: { emergency?: boolean }) {
|
||||||
|
const i18n = useI18n();
|
||||||
const [state, actions] = useMegaStore();
|
const [state, actions] = useMegaStore();
|
||||||
|
|
||||||
async function confirmReset() {
|
async function confirmReset() {
|
||||||
@@ -34,7 +36,14 @@ export function DeleteEverything(props: { emergency?: boolean }) {
|
|||||||
await MutinyWallet.import_json("{}");
|
await MutinyWallet.import_json("{}");
|
||||||
}
|
}
|
||||||
|
|
||||||
showToast({ title: "Deleted", description: `Deleted all data` });
|
showToast({
|
||||||
|
title: i18n.t(
|
||||||
|
"settings.emergency_kit.delete_everything.deleted"
|
||||||
|
),
|
||||||
|
description: i18n.t(
|
||||||
|
"settings.emergency_kit.delete_everything.deleted_description"
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
@@ -50,14 +59,16 @@ export function DeleteEverything(props: { emergency?: boolean }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={confirmReset}>Delete Everything</Button>
|
<Button onClick={confirmReset}>
|
||||||
|
{i18n.t("settings.emergency_kit.delete_everything.delete")}
|
||||||
|
</Button>
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
loading={confirmLoading()}
|
loading={confirmLoading()}
|
||||||
open={confirmOpen()}
|
open={confirmOpen()}
|
||||||
onConfirm={resetNode}
|
onConfirm={resetNode}
|
||||||
onCancel={() => setConfirmOpen(false)}
|
onCancel={() => setConfirmOpen(false)}
|
||||||
>
|
>
|
||||||
This will delete your node's state. This can't be undone!
|
{i18n.t("settings.emergency_kit.delete_everything.confirm")}
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ import { ConfirmDialog } from "./Dialog";
|
|||||||
import initMutinyWallet, { MutinyWallet } from "@mutinywallet/mutiny-wasm";
|
import initMutinyWallet, { MutinyWallet } from "@mutinywallet/mutiny-wasm";
|
||||||
import { InfoBox } from "./InfoBox";
|
import { InfoBox } from "./InfoBox";
|
||||||
import { TextField } from "./layout/TextField";
|
import { TextField } from "./layout/TextField";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
export function ImportExport(props: { emergency?: boolean }) {
|
export function ImportExport(props: { emergency?: boolean }) {
|
||||||
|
const i18n = useI18n();
|
||||||
const [state, _] = useMegaStore();
|
const [state, _] = useMegaStore();
|
||||||
|
|
||||||
const [error, setError] = createSignal<Error>();
|
const [error, setError] = createSignal<Error>();
|
||||||
@@ -44,7 +46,11 @@ export function ImportExport(props: { emergency?: boolean }) {
|
|||||||
try {
|
try {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
if (!password()) {
|
if (!password()) {
|
||||||
throw new Error("Password is required");
|
throw new Error(
|
||||||
|
i18n.t(
|
||||||
|
"settings.emergency_kit.import_export.error_password"
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const json = await MutinyWallet.export_json(password());
|
const json = await MutinyWallet.export_json(password());
|
||||||
downloadTextFile(json || "", "mutiny-state.json");
|
downloadTextFile(json || "", "mutiny-state.json");
|
||||||
@@ -77,11 +83,23 @@ export function ImportExport(props: { emergency?: boolean }) {
|
|||||||
if (result) {
|
if (result) {
|
||||||
resolve(result);
|
resolve(result);
|
||||||
} else {
|
} else {
|
||||||
reject(new Error("No text found in file"));
|
reject(
|
||||||
|
new Error(
|
||||||
|
i18n.t(
|
||||||
|
"settings.emergency_kit.import_export.error_no_text"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fileReader.onerror = (_e) =>
|
fileReader.onerror = (_e) =>
|
||||||
reject(new Error("File read error"));
|
reject(
|
||||||
|
new Error(
|
||||||
|
i18n.t(
|
||||||
|
"settings.emergency_kit.import_export.error_read_file"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
fileReader.readAsText(file, "UTF-8");
|
fileReader.readAsText(file, "UTF-8");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -130,25 +148,35 @@ export function ImportExport(props: { emergency?: boolean }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<InnerCard title="Export wallet state">
|
<InnerCard
|
||||||
|
title={i18n.t("settings.emergency_kit.import_export.title")}
|
||||||
|
>
|
||||||
<NiceP>
|
<NiceP>
|
||||||
You can export your entire Mutiny Wallet state to a file and
|
{i18n.t("settings.emergency_kit.import_export.tip")}
|
||||||
import it into a new browser. It usually works!
|
|
||||||
</NiceP>
|
</NiceP>
|
||||||
<NiceP>
|
<NiceP>
|
||||||
<strong>Important caveats:</strong> after exporting don't do
|
<strong>
|
||||||
any operations in the original browser. If you do, you'll
|
{i18n.t(
|
||||||
need to export again. After a successful import, a best
|
"settings.emergency_kit.import_export.caveat_header"
|
||||||
practice is to clear the state of the original browser just
|
)}
|
||||||
to make sure you don't create conflicts.
|
</strong>{" "}
|
||||||
|
{i18n.t("settings.emergency_kit.import_export.caveat")}
|
||||||
</NiceP>
|
</NiceP>
|
||||||
<div />
|
<div />
|
||||||
<Show when={error()}>
|
<Show when={error()}>
|
||||||
<InfoBox accent="red">{error()?.message}</InfoBox>
|
<InfoBox accent="red">{error()?.message}</InfoBox>
|
||||||
</Show>
|
</Show>
|
||||||
<VStack>
|
<VStack>
|
||||||
<Button onClick={handleSave}>Save State As File</Button>
|
<Button onClick={handleSave}>
|
||||||
<Button onClick={uploadFile}>Import State From File</Button>
|
{i18n.t(
|
||||||
|
"settings.emergency_kit.import_export.save_state"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={uploadFile}>
|
||||||
|
{i18n.t(
|
||||||
|
"settings.emergency_kit.import_export.import_state"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
</VStack>
|
</VStack>
|
||||||
</InnerCard>
|
</InnerCard>
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
@@ -157,11 +185,14 @@ export function ImportExport(props: { emergency?: boolean }) {
|
|||||||
onConfirm={importJson}
|
onConfirm={importJson}
|
||||||
onCancel={() => setConfirmOpen(false)}
|
onCancel={() => setConfirmOpen(false)}
|
||||||
>
|
>
|
||||||
Do you want to replace your state with {files()[0].name}?
|
{i18n.t("settings.emergency_kit.import_export.confirm_replace")}{" "}
|
||||||
|
{files()[0].name}?
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
{/* TODO: this is pretty redundant with the DecryptDialog, could make a shared component */}
|
{/* TODO: this is pretty redundant with the DecryptDialog, could make a shared component */}
|
||||||
<SimpleDialog
|
<SimpleDialog
|
||||||
title="Enter your password to decrypt"
|
title={i18n.t(
|
||||||
|
"settings.emergency_kit.import_export.confirm_replace"
|
||||||
|
)}
|
||||||
open={exportDecrypt()}
|
open={exportDecrypt()}
|
||||||
>
|
>
|
||||||
<form onSubmit={savePassword}>
|
<form onSubmit={savePassword}>
|
||||||
@@ -180,7 +211,9 @@ export function ImportExport(props: { emergency?: boolean }) {
|
|||||||
<InfoBox accent="red">{error()?.message}</InfoBox>
|
<InfoBox accent="red">{error()?.message}</InfoBox>
|
||||||
</Show>
|
</Show>
|
||||||
<Button intent="blue" onClick={savePassword}>
|
<Button intent="blue" onClick={savePassword}>
|
||||||
Decrypt Wallet
|
{i18n.t(
|
||||||
|
"settings.emergency_kit.import_export.decrypt_wallet"
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { MutinyWallet } from "@mutinywallet/mutiny-wasm";
|
import { MutinyWallet } from "@mutinywallet/mutiny-wasm";
|
||||||
import { Button, InnerCard, NiceP, VStack } from "~/components/layout";
|
import { Button, InnerCard, NiceP, VStack } from "~/components/layout";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
import { downloadTextFile } from "~/utils/download";
|
import { downloadTextFile } from "~/utils/download";
|
||||||
|
|
||||||
export function Logs() {
|
export function Logs() {
|
||||||
|
const i18n = useI18n();
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
try {
|
try {
|
||||||
const logs = await MutinyWallet.get_logs();
|
const logs = await MutinyWallet.get_logs();
|
||||||
@@ -18,11 +20,13 @@ export function Logs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InnerCard title="Download debug logs">
|
<InnerCard title={i18n.t("settings.emergency_kit.logs.title")}>
|
||||||
<VStack>
|
<VStack>
|
||||||
<NiceP>Something screwy going on? Check out the logs!</NiceP>
|
<NiceP>
|
||||||
|
{i18n.t("settings.emergency_kit.logs.something_screwy")}
|
||||||
|
</NiceP>
|
||||||
<Button intent="green" onClick={handleSave}>
|
<Button intent="green" onClick={handleSave}>
|
||||||
Download Logs
|
{i18n.t("settings.emergency_kit.logs.download_logs")}
|
||||||
</Button>
|
</Button>
|
||||||
</VStack>
|
</VStack>
|
||||||
</InnerCard>
|
</InnerCard>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export function FeesModal(props: { icon?: boolean }) {
|
|||||||
props.icon ? (
|
props.icon ? (
|
||||||
<img src={help} alt="help" class="w-4 h-4 cursor-pointer" />
|
<img src={help} alt="help" class="w-4 h-4 cursor-pointer" />
|
||||||
) : (
|
) : (
|
||||||
i18n.t("why?")
|
i18n.t("why")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { onCleanup, onMount } from "solid-js";
|
import { onCleanup, onMount } from "solid-js";
|
||||||
import { BarcodeScanner, BarcodeFormat, CameraPermissionState, CameraPermissionType, CameraPluginPermissions, PermissionStates } from '@mutinywallet/barcode-scanner';
|
import {
|
||||||
|
BarcodeScanner,
|
||||||
|
BarcodeFormat,
|
||||||
|
CameraPermissionState,
|
||||||
|
CameraPermissionType,
|
||||||
|
CameraPluginPermissions,
|
||||||
|
PermissionStates
|
||||||
|
} from "@mutinywallet/barcode-scanner";
|
||||||
import QrScanner from "qr-scanner";
|
import QrScanner from "qr-scanner";
|
||||||
|
|
||||||
export default function Scanner(props: { onResult: (result: string) => void }) {
|
export default function Scanner(props: { onResult: (result: string) => void }) {
|
||||||
@@ -12,7 +19,8 @@ export default function Scanner(props: { onResult: (result: string) => void }) {
|
|||||||
|
|
||||||
const startScan = async () => {
|
const startScan = async () => {
|
||||||
// Check camera permission
|
// Check camera permission
|
||||||
const permissions: PermissionStates = await BarcodeScanner.checkPermissions();
|
const permissions: PermissionStates =
|
||||||
|
await BarcodeScanner.checkPermissions();
|
||||||
if (permissions.camera === "granted") {
|
if (permissions.camera === "granted") {
|
||||||
const callback = (result: ScanResult, err?: any) => {
|
const callback = (result: ScanResult, err?: any) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -24,10 +32,14 @@ export default function Scanner(props: { onResult: (result: string) => void }) {
|
|||||||
handleResult({ data: result.content }); // pass the raw scanned content
|
handleResult({ data: result.content }); // pass the raw scanned content
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
await BarcodeScanner.start({ targetedFormats: [BarcodeFormat.QR_CODE] }, callback);
|
await BarcodeScanner.start(
|
||||||
|
{ targetedFormats: [BarcodeFormat.QR_CODE] },
|
||||||
|
callback
|
||||||
|
);
|
||||||
} else if (permissions.camera === "prompt") {
|
} else if (permissions.camera === "prompt") {
|
||||||
// Request permission if it has not been asked before
|
// Request permission if it has not been asked before
|
||||||
const requestedPermissions: PermissionStates = await BarcodeScanner.requestPermissions();
|
const requestedPermissions: PermissionStates =
|
||||||
|
await BarcodeScanner.requestPermissions();
|
||||||
if (requestedPermissions.camera === "granted") {
|
if (requestedPermissions.camera === "granted") {
|
||||||
// If user grants permission, start the scan
|
// If user grants permission, start the scan
|
||||||
await startScan();
|
await startScan();
|
||||||
@@ -35,7 +47,7 @@ export default function Scanner(props: { onResult: (result: string) => void }) {
|
|||||||
} else if (permissions.camera === "denied") {
|
} else if (permissions.camera === "denied") {
|
||||||
// Handle the scenario when user denies the permission
|
// Handle the scenario when user denies the permission
|
||||||
// Maybe show a user friendly message here
|
// Maybe show a user friendly message here
|
||||||
console.log('Camera permission was denied');
|
console.log("Camera permission was denied");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { For, Match, Switch, createMemo, createSignal } from "solid-js";
|
import { For, Match, Switch, createMemo, createSignal } from "solid-js";
|
||||||
import { useCopy } from "~/utils/useCopy";
|
import { useCopy } from "~/utils/useCopy";
|
||||||
import copyIcon from "~/assets/icons/copy.svg";
|
import copyIcon from "~/assets/icons/copy.svg";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
export function SeedWords(props: {
|
export function SeedWords(props: {
|
||||||
words: string;
|
words: string;
|
||||||
setHasSeen?: (hasSeen: boolean) => void;
|
setHasSeen?: (hasSeen: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
|
const i18n = useI18n();
|
||||||
const [shouldShow, setShouldShow] = createSignal(false);
|
const [shouldShow, setShouldShow] = createSignal(false);
|
||||||
const [copy, copied] = useCopy({ copiedTimeout: 1000 });
|
const [copy, copied] = useCopy({ copiedTimeout: 1000 });
|
||||||
|
|
||||||
@@ -30,7 +32,9 @@ export function SeedWords(props: {
|
|||||||
class="cursor-pointer flex w-full justify-center"
|
class="cursor-pointer flex w-full justify-center"
|
||||||
onClick={toggleShow}
|
onClick={toggleShow}
|
||||||
>
|
>
|
||||||
<code class="text-red">TAP TO REVEAL SEED WORDS</code>
|
<code class="text-red">
|
||||||
|
{i18n.t("settings.backup.seed_words.reveal")}
|
||||||
|
</code>
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
|
|
||||||
@@ -40,7 +44,9 @@ export function SeedWords(props: {
|
|||||||
class="cursor-pointer flex w-full justify-center"
|
class="cursor-pointer flex w-full justify-center"
|
||||||
onClick={toggleShow}
|
onClick={toggleShow}
|
||||||
>
|
>
|
||||||
<code class="text-red">HIDE</code>
|
<code class="text-red">
|
||||||
|
{i18n.t("settings.backup.seed_words.hide")}
|
||||||
|
</code>
|
||||||
</div>
|
</div>
|
||||||
<ol class="overflow-hidden columns-2 w-full list-decimal list-inside">
|
<ol class="overflow-hidden columns-2 w-full list-decimal list-inside">
|
||||||
<For each={splitWords()}>
|
<For each={splitWords()}>
|
||||||
@@ -59,8 +65,12 @@ export function SeedWords(props: {
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span>
|
<span>
|
||||||
{copied()
|
{copied()
|
||||||
? "Copied!"
|
? i18n.t(
|
||||||
: "Dangerously Copy to Clipboard"}
|
"settings.backup.seed_words.copied"
|
||||||
|
)
|
||||||
|
: i18n.t(
|
||||||
|
"settings.backup.seed_words.copy"
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
<img
|
<img
|
||||||
src={copyIcon}
|
src={copyIcon}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { Back } from "~/assets/svg/Back";
|
import { Back } from "~/assets/svg/Back";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
export function BackButton(props: {
|
export function BackButton(props: {
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
title?: string;
|
title?: string;
|
||||||
showOnDesktop?: boolean;
|
showOnDesktop?: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
const i18n = useI18n();
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={() => props.onClick()}
|
onClick={() => props.onClick()}
|
||||||
@@ -12,7 +14,7 @@ export function BackButton(props: {
|
|||||||
classList={{ "md:!flex": props.showOnDesktop }}
|
classList={{ "md:!flex": props.showOnDesktop }}
|
||||||
>
|
>
|
||||||
<Back />
|
<Back />
|
||||||
{props.title ? props.title : "Home"}
|
{props.title ? props.title : i18n.t("common.home")}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { A } from "solid-start";
|
import { A } from "solid-start";
|
||||||
import { Back } from "~/assets/svg/Back";
|
import { Back } from "~/assets/svg/Back";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
export function BackLink(props: { href?: string; title?: string }) {
|
export function BackLink(props: { href?: string; title?: string }) {
|
||||||
|
const i18n = useI18n();
|
||||||
return (
|
return (
|
||||||
<A
|
<A
|
||||||
href={props.href ? props.href : "/"}
|
href={props.href ? props.href : "/"}
|
||||||
class="text-m-red active:text-m-red/80 text-xl font-semibold no-underline md:hidden flex items-center"
|
class="text-m-red active:text-m-red/80 text-xl font-semibold no-underline md:hidden flex items-center"
|
||||||
>
|
>
|
||||||
<Back />
|
<Back />
|
||||||
{props.title ? props.title : "Home"}
|
{props.title ? props.title : i18n.t("common.home")}
|
||||||
</A>
|
</A>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { useLocation, useNavigate } from "solid-start";
|
import { useLocation, useNavigate } from "solid-start";
|
||||||
import { BackButton } from "./BackButton";
|
import { BackButton } from "./BackButton";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
type StateWithPrevious = {
|
type StateWithPrevious = {
|
||||||
previous?: string;
|
previous?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function BackPop() {
|
export function BackPop() {
|
||||||
|
const i18n = useI18n();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
@@ -15,7 +17,7 @@ export function BackPop() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BackButton
|
<BackButton
|
||||||
title="Back"
|
title={i18n.t("common.back")}
|
||||||
onClick={() => navigate(backPath())}
|
onClick={() => navigate(backPath())}
|
||||||
showOnDesktop
|
showOnDesktop
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Progress } from "@kobalte/core";
|
import { Progress } from "@kobalte/core";
|
||||||
import { SmallHeader } from ".";
|
import { SmallHeader } from ".";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
export default function formatNumber(num: number) {
|
export default function formatNumber(num: number) {
|
||||||
const map = [
|
const map = [
|
||||||
@@ -21,19 +22,24 @@ export default function formatNumber(num: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ProgressBar(props: { value: number; max: number }) {
|
export function ProgressBar(props: { value: number; max: number }) {
|
||||||
|
const i18n = useI18n();
|
||||||
return (
|
return (
|
||||||
<Progress.Root
|
<Progress.Root
|
||||||
value={props.value}
|
value={props.value}
|
||||||
minValue={0}
|
minValue={0}
|
||||||
maxValue={props.max}
|
maxValue={props.max}
|
||||||
getValueLabel={({ value, max }) =>
|
getValueLabel={({ value, max }) =>
|
||||||
`${formatNumber(value)} of ${formatNumber(max)} sats sent`
|
`${formatNumber(value)} ${i18n.t(
|
||||||
|
"send.progress_bar.of"
|
||||||
|
)} ${formatNumber(max)} ${i18n.t(
|
||||||
|
"send.progress_bar.sats_sent"
|
||||||
|
)}`
|
||||||
}
|
}
|
||||||
class="w-full flex flex-col gap-2"
|
class="w-full flex flex-col gap-2"
|
||||||
>
|
>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<Progress.Label>
|
<Progress.Label>
|
||||||
<SmallHeader>Sending...</SmallHeader>
|
<SmallHeader>{i18n.t("send.sending")}</SmallHeader>
|
||||||
</Progress.Label>
|
</Progress.Label>
|
||||||
<Progress.ValueLabel class="text-sm font-semibold uppercase" />
|
<Progress.ValueLabel class="text-sm font-semibold uppercase" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { A } from "solid-start";
|
|||||||
import down from "~/assets/icons/down.svg";
|
import down from "~/assets/icons/down.svg";
|
||||||
import { DecryptDialog } from "../DecryptDialog";
|
import { DecryptDialog } from "../DecryptDialog";
|
||||||
import { LoadingIndicator } from "~/components/LoadingIndicator";
|
import { LoadingIndicator } from "~/components/LoadingIndicator";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
export { Button, ButtonLink, Linkify };
|
export { Button, ButtonLink, Linkify };
|
||||||
|
|
||||||
@@ -138,6 +139,7 @@ export const DefaultMain: ParentComponent = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const FullscreenLoader = () => {
|
export const FullscreenLoader = () => {
|
||||||
|
const i18n = useI18n();
|
||||||
const [waitedTooLong, setWaitedTooLong] = createSignal(false);
|
const [waitedTooLong, setWaitedTooLong] = createSignal(false);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -149,10 +151,9 @@ export const FullscreenLoader = () => {
|
|||||||
<LoadingSpinner wide />
|
<LoadingSpinner wide />
|
||||||
<Show when={waitedTooLong()}>
|
<Show when={waitedTooLong()}>
|
||||||
<p class="max-w-[20rem] text-neutral-400">
|
<p class="max-w-[20rem] text-neutral-400">
|
||||||
Stuck on this screen? Try reloading. If that doesn't work,
|
{i18n.t("error.load_time.stuck")}{" "}
|
||||||
check out the{" "}
|
|
||||||
<A class="text-white" href="/emergencykit">
|
<A class="text-white" href="/emergencykit">
|
||||||
emergency kit.
|
{i18n.t("error.load_time.emergency_link")}
|
||||||
</A>
|
</A>
|
||||||
</p>
|
</p>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -5,27 +5,32 @@ import LanguageDetector from "i18next-browser-languagedetector";
|
|||||||
import en from "~/i18n/en/translations";
|
import en from "~/i18n/en/translations";
|
||||||
import pt from "~/i18n/pt/translations";
|
import pt from "~/i18n/pt/translations";
|
||||||
|
|
||||||
const i18n = use(LanguageDetector).init(
|
export const resources = {
|
||||||
{
|
|
||||||
fallbackLng: "en",
|
|
||||||
preload: ["en"],
|
|
||||||
load: "languageOnly",
|
|
||||||
ns: ["translations"],
|
|
||||||
defaultNS: "translations",
|
|
||||||
fallbackNS: false,
|
|
||||||
debug: true,
|
|
||||||
detection: {
|
|
||||||
order: ["querystring", "navigator", "htmlTag"],
|
|
||||||
lookupQuerystring: "lang"
|
|
||||||
},
|
|
||||||
resources: {
|
|
||||||
en: {
|
en: {
|
||||||
translations: en
|
translations: en
|
||||||
},
|
},
|
||||||
pt: {
|
pt: {
|
||||||
translations: pt
|
translations: pt
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export const defaultNS = "translations";
|
||||||
|
|
||||||
|
const i18n = use(LanguageDetector).init(
|
||||||
|
{
|
||||||
|
returnNull: false,
|
||||||
|
fallbackLng: "en",
|
||||||
|
preload: ["en"],
|
||||||
|
load: "languageOnly",
|
||||||
|
ns: ["translations"],
|
||||||
|
defaultNS: defaultNS,
|
||||||
|
fallbackNS: false,
|
||||||
|
debug: true,
|
||||||
|
detection: {
|
||||||
|
order: ["querystring", "navigator", "htmlTag"],
|
||||||
|
lookupQuerystring: "lang"
|
||||||
|
},
|
||||||
|
resources: resources
|
||||||
// FIXME: this doesn't work when deployed
|
// FIXME: this doesn't work when deployed
|
||||||
// backend: {
|
// backend: {
|
||||||
// loadPath: 'src/i18n/{{lng}}/{{ns}}.json',
|
// loadPath: 'src/i18n/{{lng}}/{{ns}}.json',
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ export default {
|
|||||||
usd: "USD",
|
usd: "USD",
|
||||||
fee: "Fee",
|
fee: "Fee",
|
||||||
send: "Send",
|
send: "Send",
|
||||||
receive: "Receive"
|
receive: "Receive",
|
||||||
|
dangit: "Dangit",
|
||||||
|
back: "Back"
|
||||||
},
|
},
|
||||||
char: {
|
char: {
|
||||||
del: "DEL"
|
del: "DEL"
|
||||||
@@ -19,13 +21,55 @@ export default {
|
|||||||
choose_format: "Choose format",
|
choose_format: "Choose format",
|
||||||
payment_received: "Payment Received",
|
payment_received: "Payment Received",
|
||||||
payment_initiated: "Payment Initiated",
|
payment_initiated: "Payment Initiated",
|
||||||
receive_add_the_sender: "Add the sender for your records"
|
receive_add_the_sender: "Add the sender for your records",
|
||||||
|
amount_editable: {
|
||||||
|
receive_too_small:
|
||||||
|
"Your first lightning receive needs to be {{amount}} sats or greater. A setup fee will be deducted from the requested amount.",
|
||||||
|
setup_fee_lightning:
|
||||||
|
"A lightning setup fee will be charged if paid over lightning.",
|
||||||
|
too_big_for_beta:
|
||||||
|
"That's a lot of sats. You do know Mutiny Wallet is still in beta, yeah?",
|
||||||
|
more_than_21m:
|
||||||
|
"There are only 21 million bitcoin.",
|
||||||
|
set_amount: "Set amount",
|
||||||
|
max: "MAX"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
send: {
|
send: {
|
||||||
sending: "Sending...",
|
sending: "Sending...",
|
||||||
confirm_send: "Confirm Send",
|
confirm_send: "Confirm Send",
|
||||||
contact_placeholder: "Add the receiver for your records",
|
contact_placeholder: "Add the receiver for your records",
|
||||||
start_over: "Start Over"
|
start_over: "Start Over",
|
||||||
|
progress_bar: {
|
||||||
|
of: "of",
|
||||||
|
sats_sent: "sats sent"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
feedback: {
|
||||||
|
header: "Give us feedback!",
|
||||||
|
received: "Feedback received!",
|
||||||
|
thanks: "Thank you for letting us know what's going on.",
|
||||||
|
more: "Got more to say?",
|
||||||
|
tracking:
|
||||||
|
"Mutiny doesn't track or spy on your behavior, so your feedback is incredibly helpful.",
|
||||||
|
github_one: "If you're comfortable with GitHub you can also",
|
||||||
|
github_two: ".",
|
||||||
|
create_issue: "create an issue",
|
||||||
|
link: "Feedback?",
|
||||||
|
feedback_placeholder: "Bugs, feature requests, feedback, etc.",
|
||||||
|
info_label: "Include contact info",
|
||||||
|
info_caption: "If you need us to follow-up on this issue",
|
||||||
|
email: "Email",
|
||||||
|
email_caption: "Burners welcome",
|
||||||
|
nostr: "Nostr",
|
||||||
|
nostr_caption: "Your freshest npub",
|
||||||
|
nostr_label: "Nostr npub or NIP-05",
|
||||||
|
send_feedback: "Send Feedback",
|
||||||
|
invalid_feedback: "Please say something!",
|
||||||
|
need_contact: "We need some way to contact you",
|
||||||
|
invalid_email: "That doesn't look like an email address to me",
|
||||||
|
error: "Error submitting feedback",
|
||||||
|
try_again: "Please try again later."
|
||||||
},
|
},
|
||||||
activity: {
|
activity: {
|
||||||
view_all: "View all",
|
view_all: "View all",
|
||||||
@@ -34,25 +78,159 @@ export default {
|
|||||||
channel_close: "Channel Close",
|
channel_close: "Channel Close",
|
||||||
unknown: "Unknown"
|
unknown: "Unknown"
|
||||||
},
|
},
|
||||||
|
redshift: {},
|
||||||
|
scanner: {
|
||||||
|
paste: "Paste Something",
|
||||||
|
cancel: "Cancel"
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
header: "Settings",
|
||||||
|
mutiny_plus: "MUTINY+",
|
||||||
|
support: "Learn how to support Mutiny",
|
||||||
|
general: "GENERAL",
|
||||||
|
beta_features: "BETA FEATURES",
|
||||||
|
debug_tools: "DEBUG TOOLS",
|
||||||
|
danger_zone: "Danger zone",
|
||||||
|
admin: {
|
||||||
|
title: "Admin Page",
|
||||||
|
caption: "Our internal debug tools. Use wisely!",
|
||||||
|
header: "Secret Debug Tools",
|
||||||
|
warning_one:
|
||||||
|
"If you know what you're doing you're in the right place.",
|
||||||
|
warning_two:
|
||||||
|
"These are internal tools we use to debug and test the app. Please be careful!"
|
||||||
|
},
|
||||||
|
backup: {
|
||||||
|
title: "Backup",
|
||||||
|
secure_funds: "Let's get these funds secured.",
|
||||||
|
twelve_words_tip:
|
||||||
|
"We'll show you 12 words. You write down the 12 words.",
|
||||||
|
warning_one:
|
||||||
|
"If you clear your browser history, or lose your device, these 12 words are the only way you can restore your wallet.",
|
||||||
|
warning_two: "Mutiny is self-custodial. It's all up to you...",
|
||||||
|
confirm: "I wrote down the words",
|
||||||
|
responsibility: "I understand that my funds are my responsibility",
|
||||||
|
liar: "I'm not lying just to get this over with",
|
||||||
|
seed_words: {
|
||||||
|
reveal: "TAP TO REVEAL SEED WORDS",
|
||||||
|
hide: "HIDE",
|
||||||
|
copy: "Dangerously Copy to Clipboard",
|
||||||
|
copied: "Copied!"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
channels: {
|
||||||
|
title: "Lightning Channels",
|
||||||
|
outbound: "Outbound",
|
||||||
|
inbound: "Inbound",
|
||||||
|
have_channels: "You have",
|
||||||
|
have_channels_one: "lightning channel.",
|
||||||
|
have_channels_many: "lightning channels.",
|
||||||
|
inbound_outbound_tip:
|
||||||
|
"Outbound is the amount of money you can spend on lightning. Inbound is the amount you can receive without incurring a lightning service fee.",
|
||||||
|
no_channels:
|
||||||
|
"It looks like you don't have any channels yet. To get started, receive some sats over lightning, or swap some on-chain funds into a channel. Get your hands dirty!"
|
||||||
|
},
|
||||||
|
connections: {
|
||||||
|
title: "Wallet Connections",
|
||||||
|
error_name: "Name cannot be empty",
|
||||||
|
error_connection: "Failed to create Wallet Connection",
|
||||||
|
add_connection: "Add Connection",
|
||||||
|
manage_connections: "Manage Connections",
|
||||||
|
disable_connection: "Disable",
|
||||||
|
enable_connection: "Enable",
|
||||||
|
new_connection: "New Connection",
|
||||||
|
new_connection_label: "Name",
|
||||||
|
new_connection_placeholder: "My favorite nostr client...",
|
||||||
|
create_connection: "Create Connection",
|
||||||
|
authorize:
|
||||||
|
"Authorize external services to request payments from your wallet. Pairs great with Nostr clients."
|
||||||
|
},
|
||||||
|
emergency_kit: {
|
||||||
|
title: "Emergency Kit",
|
||||||
|
caption: "Diagnose and solve problems with your wallet.",
|
||||||
|
emergency_tip:
|
||||||
|
"If your wallet seems broken, here are some tools to try to debug and repair it.",
|
||||||
|
questions:
|
||||||
|
"If you have any questions on what these buttons do, please",
|
||||||
|
link: "reach out to us for support.",
|
||||||
|
import_export: {
|
||||||
|
title: "Export wallet state",
|
||||||
|
error_password: "Password is required",
|
||||||
|
error_read_file: "File read error",
|
||||||
|
error_no_text: "No text found in file",
|
||||||
|
tip: "You can export your entire Mutiny Wallet state to a file and import it into a new browser. It usually works!",
|
||||||
|
caveat_header: "Important caveats:",
|
||||||
|
caveat: "after exporting don't do any operations in the original browser. If you do, you'll need to export again. After a successful import, a best practice is to clear the state of the original browser just to make sure you don't create conflicts.",
|
||||||
|
save_state: "Save State As File",
|
||||||
|
import_state: "Import State From File",
|
||||||
|
confirm_replace: "Do you want to replace your state with",
|
||||||
|
password: "Enter your password to decrypt",
|
||||||
|
decrypt_wallet: "Decrypt Wallet"
|
||||||
|
},
|
||||||
|
logs: {
|
||||||
|
title: "Download debug logs",
|
||||||
|
something_screwy:
|
||||||
|
"Something screwy going on? Check out the logs!",
|
||||||
|
download_logs: "Download Logs"
|
||||||
|
},
|
||||||
|
delete_everything: {
|
||||||
|
delete: "Delete Everything",
|
||||||
|
confirm:
|
||||||
|
"This will delete your node's state. This can't be undone!",
|
||||||
|
deleted: "Deleted",
|
||||||
|
deleted_description: "Deleted all data"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
encrypt: {},
|
||||||
|
lnurl_auth: {
|
||||||
|
title: "LNURL Auth"
|
||||||
|
},
|
||||||
|
plus: {},
|
||||||
|
restore: {
|
||||||
|
title: "Restore"
|
||||||
|
},
|
||||||
|
servers: {
|
||||||
|
title: "Servers",
|
||||||
|
caption: "Don't trust us! Use your own servers to back Mutiny."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
swap: {
|
||||||
|
peer_not_found: "Peer not found",
|
||||||
|
channel_too_small:
|
||||||
|
"It's just silly to make a channel smaller than {{amount}} sats",
|
||||||
|
insufficient_funds: "You don't have enough funds to make this channel",
|
||||||
|
header: "Swap to Lightning",
|
||||||
|
initiated: "Swap Initiated",
|
||||||
|
sats_added: "sats will be added to your Lightning balance",
|
||||||
|
use_existing: "Use existing peer",
|
||||||
|
choose_peer: "Choose a peer",
|
||||||
|
peer_connect_label: "Connect to new peer",
|
||||||
|
peer_connect_placeholder: "Peer connect string",
|
||||||
|
connect: "Connect",
|
||||||
|
connecting: "Connecting...",
|
||||||
|
confirm_swap: "Confirm Swap"
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
load_time: {
|
||||||
|
stuck: "Stuck on this screen? Try reloading. If that doesn't work, check out the",
|
||||||
|
emergency_link: "emergency kit."
|
||||||
|
},
|
||||||
|
not_found: {
|
||||||
|
title: "Not Found",
|
||||||
|
wtf_paul: "This is probably Paul's fault."
|
||||||
|
}
|
||||||
|
},
|
||||||
create_an_issue: "Create an issue",
|
create_an_issue: "Create an issue",
|
||||||
feedback: "Bugs? Feedback?",
|
|
||||||
send_bitcoin: "Send Bitcoin",
|
send_bitcoin: "Send Bitcoin",
|
||||||
view_transaction: "View Transaction",
|
view_transaction: "View Transaction",
|
||||||
amount_editable_first_payment_10k_or_greater:
|
why: "Why?",
|
||||||
"Your first lightning receive needs to be 10,000 sats or greater. A setup fee will be deducted from the requested amount.",
|
|
||||||
"why?": "Why?",
|
|
||||||
more_info_modal_p1:
|
more_info_modal_p1:
|
||||||
"Mutiny is a self-custodial wallet. To initiate a lightning payment we must open a lightning channel, which requires a minimum amount and a setup fee.",
|
"Mutiny is a self-custodial wallet. To initiate a lightning payment we must open a lightning channel, which requires a minimum amount and a setup fee.",
|
||||||
more_info_modal_p2:
|
more_info_modal_p2:
|
||||||
"Future payments, both send and recieve, will only incur normal network fees and a nominal service fee unless your channel runs out of inbound capacity.",
|
"Future payments, both send and recieve, will only incur normal network fees and a nominal service fee unless your channel runs out of inbound capacity.",
|
||||||
learn_more_about_liquidity: "Learn more about liquidity",
|
learn_more_about_liquidity: "Learn more about liquidity",
|
||||||
set_amount: "Set amount",
|
|
||||||
whats_with_the_fees: "What's with the fees?",
|
whats_with_the_fees: "What's with the fees?",
|
||||||
private_tags: "Private tags",
|
private_tags: "Private tags",
|
||||||
continue: "Continue",
|
continue: "Continue",
|
||||||
keep_mutiny_open: "Keep Mutiny open to complete the payment.",
|
keep_mutiny_open: "Keep Mutiny open to complete the payment."
|
||||||
too_big_for_beta:
|
|
||||||
"That's a lot of sats. You do know Mutiny Wallet is still in beta, yeah?",
|
|
||||||
more_than_21m:
|
|
||||||
"There are only 21 million bitcoin.",
|
|
||||||
};
|
};
|
||||||
|
|||||||
9
src/i18n/i18next.d.ts
vendored
Normal file
9
src/i18n/i18next.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { resources, defaultNS } from "~/i18n/config";
|
||||||
|
|
||||||
|
declare module "i18next" {
|
||||||
|
interface CustomTypeOptions {
|
||||||
|
defaultNS: typeof defaultNS;
|
||||||
|
resources: (typeof resources)["en"];
|
||||||
|
returnNull: false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,10 +27,12 @@ import feedback from "~/assets/icons/feedback.svg";
|
|||||||
import { InfoBox } from "~/components/InfoBox";
|
import { InfoBox } from "~/components/InfoBox";
|
||||||
import eify from "~/utils/eify";
|
import eify from "~/utils/eify";
|
||||||
import { MegaCheck } from "~/components/successfail/MegaCheck";
|
import { MegaCheck } from "~/components/successfail/MegaCheck";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
const FEEDBACK_API = import.meta.env.VITE_FEEDBACK;
|
const FEEDBACK_API = import.meta.env.VITE_FEEDBACK;
|
||||||
|
|
||||||
export function FeedbackLink(props: { setupError?: boolean }) {
|
export function FeedbackLink(props: { setupError?: boolean }) {
|
||||||
|
const i18n = useI18n();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
return (
|
return (
|
||||||
<A
|
<A
|
||||||
@@ -43,7 +45,7 @@ export function FeedbackLink(props: { setupError?: boolean }) {
|
|||||||
}}
|
}}
|
||||||
href="/feedback"
|
href="/feedback"
|
||||||
>
|
>
|
||||||
Feedback?
|
{i18n.t("feedback.link")}
|
||||||
<img src={feedback} class="h-5 w-5" alt="Feedback" />
|
<img src={feedback} class="h-5 w-5" alt="Feedback" />
|
||||||
</A>
|
</A>
|
||||||
);
|
);
|
||||||
@@ -58,11 +60,6 @@ type FeedbackForm = {
|
|||||||
images: File[];
|
images: File[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const COMMUNICATION_METHODS = [
|
|
||||||
{ value: "nostr", label: "Nostr", caption: "Your freshest npub" },
|
|
||||||
{ value: "email", label: "Email", caption: "Burners welcome" }
|
|
||||||
];
|
|
||||||
|
|
||||||
async function formDataFromFeedbackForm(f: FeedbackForm) {
|
async function formDataFromFeedbackForm(f: FeedbackForm) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
||||||
@@ -107,9 +104,23 @@ async function formDataFromFeedbackForm(f: FeedbackForm) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function FeedbackForm(props: { onSubmitted: () => void }) {
|
function FeedbackForm(props: { onSubmitted: () => void }) {
|
||||||
|
const i18n = useI18n();
|
||||||
const [loading, setLoading] = createSignal(false);
|
const [loading, setLoading] = createSignal(false);
|
||||||
const [error, setError] = createSignal<Error>();
|
const [error, setError] = createSignal<Error>();
|
||||||
|
|
||||||
|
const COMMUNICATION_METHODS = [
|
||||||
|
{
|
||||||
|
value: "nostr",
|
||||||
|
label: i18n.t("feedback.nostr"),
|
||||||
|
caption: i18n.t("feedback.nostr_caption")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "email",
|
||||||
|
label: i18n.t("feedback.email"),
|
||||||
|
caption: i18n.t("feedback.email_caption")
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const [feedbackForm, { Form, Field }] = createForm<FeedbackForm>({
|
const [feedbackForm, { Form, Field }] = createForm<FeedbackForm>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
user_type: "nostr",
|
user_type: "nostr",
|
||||||
@@ -133,7 +144,9 @@ function FeedbackForm(props: { onSubmitted: () => void }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error(`Error submitting feedback: ${res.statusText}`);
|
throw new Error(
|
||||||
|
`${i18n.t("feedback.error")}: ${res.statusText}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
@@ -142,7 +155,9 @@ function FeedbackForm(props: { onSubmitted: () => void }) {
|
|||||||
props.onSubmitted();
|
props.onSubmitted();
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Error submitting feedback. Please try again later."
|
`${i18n.t("feedback.error")}. ${i18n.t(
|
||||||
|
"feedback.try_again"
|
||||||
|
)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -158,7 +173,7 @@ function FeedbackForm(props: { onSubmitted: () => void }) {
|
|||||||
<VStack>
|
<VStack>
|
||||||
<Field
|
<Field
|
||||||
name="feedback"
|
name="feedback"
|
||||||
validate={[required("Please say something!")]}
|
validate={[required(i18n.t("feedback.invalid_feedback"))]}
|
||||||
>
|
>
|
||||||
{(field, props) => (
|
{(field, props) => (
|
||||||
<TextField
|
<TextField
|
||||||
@@ -166,7 +181,9 @@ function FeedbackForm(props: { onSubmitted: () => void }) {
|
|||||||
{...props}
|
{...props}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
error={field.error}
|
error={field.error}
|
||||||
placeholder="Bugs, feature requests, feedback, etc."
|
placeholder={i18n.t(
|
||||||
|
"feedback.feedback_placeholder"
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
@@ -174,8 +191,8 @@ function FeedbackForm(props: { onSubmitted: () => void }) {
|
|||||||
{(field, _props) => (
|
{(field, _props) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={field.value || false}
|
checked={field.value || false}
|
||||||
label="Include contact info"
|
label={i18n.t("feedback.info_label")}
|
||||||
caption="If you need us to follow-up on this issue"
|
caption={i18n.t("feedback.info_caption")}
|
||||||
onChange={(c) =>
|
onChange={(c) =>
|
||||||
setValue(feedbackForm, "include_contact", c)
|
setValue(feedbackForm, "include_contact", c)
|
||||||
}
|
}
|
||||||
@@ -210,7 +227,7 @@ function FeedbackForm(props: { onSubmitted: () => void }) {
|
|||||||
<Field
|
<Field
|
||||||
name="id"
|
name="id"
|
||||||
validate={[
|
validate={[
|
||||||
required("We need some way to contact you")
|
required(i18n.t("feedback.need_contact"))
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{(field, props) => (
|
{(field, props) => (
|
||||||
@@ -218,7 +235,7 @@ function FeedbackForm(props: { onSubmitted: () => void }) {
|
|||||||
{...props}
|
{...props}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
error={field.error}
|
error={field.error}
|
||||||
label="Nostr npub or NIP-05"
|
label={i18n.t("feedback.nostr_label")}
|
||||||
placeholder="npub..."
|
placeholder="npub..."
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -234,10 +251,8 @@ function FeedbackForm(props: { onSubmitted: () => void }) {
|
|||||||
<Field
|
<Field
|
||||||
name="id"
|
name="id"
|
||||||
validate={[
|
validate={[
|
||||||
required("We need some way to contact you"),
|
required(i18n.t("feedback.need_contact")),
|
||||||
email(
|
email(i18n.t("feedback.invalid_email"))
|
||||||
"That doesn't look like an email address to me"
|
|
||||||
)
|
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{(field, props) => (
|
{(field, props) => (
|
||||||
@@ -246,7 +261,7 @@ function FeedbackForm(props: { onSubmitted: () => void }) {
|
|||||||
value={field.value}
|
value={field.value}
|
||||||
error={field.error}
|
error={field.error}
|
||||||
type="email"
|
type="email"
|
||||||
label="Email"
|
label={i18n.t("feedback.email")}
|
||||||
placeholder="email@nokycemail.com"
|
placeholder="email@nokycemail.com"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -267,7 +282,7 @@ function FeedbackForm(props: { onSubmitted: () => void }) {
|
|||||||
intent="blue"
|
intent="blue"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
Send Feedback
|
{i18n.t("feedback.send_feedback")}
|
||||||
</Button>
|
</Button>
|
||||||
</VStack>
|
</VStack>
|
||||||
</Form>
|
</Form>
|
||||||
@@ -275,6 +290,7 @@ function FeedbackForm(props: { onSubmitted: () => void }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Feedback() {
|
export default function Feedback() {
|
||||||
|
const i18n = useI18n();
|
||||||
const [submitted, setSubmitted] = createSignal(false);
|
const [submitted, setSubmitted] = createSignal(false);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
@@ -292,35 +308,30 @@ export default function Feedback() {
|
|||||||
<div class="flex flex-col gap-4 items-center h-full">
|
<div class="flex flex-col gap-4 items-center h-full">
|
||||||
<MegaCheck />
|
<MegaCheck />
|
||||||
<LargeHeader centered>
|
<LargeHeader centered>
|
||||||
Feedback received!
|
{i18n.t("feedback.received")}
|
||||||
</LargeHeader>
|
</LargeHeader>
|
||||||
<NiceP>
|
<NiceP>{i18n.t("feedback.thanks")}</NiceP>
|
||||||
Thank you for letting us know what's going on.
|
|
||||||
</NiceP>
|
|
||||||
<ButtonLink intent="blue" href="/" layout="full">
|
<ButtonLink intent="blue" href="/" layout="full">
|
||||||
Go Home
|
{i18n.t("common.home")}
|
||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
<Button
|
<Button
|
||||||
intent="text"
|
intent="text"
|
||||||
layout="full"
|
layout="full"
|
||||||
onClick={() => setSubmitted(false)}
|
onClick={() => setSubmitted(false)}
|
||||||
>
|
>
|
||||||
Got more to say?
|
{i18n.t("feedback.more")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={true}>
|
<Match when={true}>
|
||||||
<LargeHeader>Give us feedback!</LargeHeader>
|
<LargeHeader>{i18n.t("feedback.header")}</LargeHeader>
|
||||||
|
<NiceP>{i18n.t("feedback.tracking")}</NiceP>
|
||||||
<NiceP>
|
<NiceP>
|
||||||
Mutiny doesn't track or spy on your behavior, so
|
{i18n.t("feedback.github_one")}{" "}
|
||||||
your feedback is incredibly helpful.
|
|
||||||
</NiceP>
|
|
||||||
<NiceP>
|
|
||||||
If you're comfortable with GitHub you can also{" "}
|
|
||||||
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/issues">
|
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/issues">
|
||||||
create an issue
|
{i18n.t("feedback.create_issue")}
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
.
|
{i18n.t("feedback.github_two")}
|
||||||
</NiceP>
|
</NiceP>
|
||||||
<FeedbackForm onSubmitted={() => setSubmitted(true)} />
|
<FeedbackForm onSubmitted={() => setSubmitted(true)} />
|
||||||
</Match>
|
</Match>
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import { useMegaStore } from "~/state/megaStore";
|
|||||||
import { toParsedParams } from "~/logic/waila";
|
import { toParsedParams } from "~/logic/waila";
|
||||||
import { Clipboard } from "@capacitor/clipboard";
|
import { Clipboard } from "@capacitor/clipboard";
|
||||||
import { Capacitor } from "@capacitor/core";
|
import { Capacitor } from "@capacitor/core";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
export default function Scanner() {
|
export default function Scanner() {
|
||||||
|
const i18n = useI18n();
|
||||||
const [state, actions] = useMegaStore();
|
const [state, actions] = useMegaStore();
|
||||||
const [scanResult, setScanResult] = createSignal<string>();
|
const [scanResult, setScanResult] = createSignal<string>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -70,9 +72,9 @@ export default function Scanner() {
|
|||||||
<div class="w-full flex flex-col items-center fixed bottom-[2rem] gap-8 px-8">
|
<div class="w-full flex flex-col items-center fixed bottom-[2rem] gap-8 px-8">
|
||||||
<div class="w-full max-w-[800px] flex flex-col gap-2">
|
<div class="w-full max-w-[800px] flex flex-col gap-2">
|
||||||
<Button intent="blue" onClick={handlePaste}>
|
<Button intent="blue" onClick={handlePaste}>
|
||||||
Paste Something
|
{i18n.t("scanner.paste")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={exit}>Cancel</Button>
|
<Button onClick={exit}>{i18n.t("scanner.cancel")}</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -574,15 +574,15 @@ export default function Send() {
|
|||||||
>
|
>
|
||||||
<BackButton
|
<BackButton
|
||||||
onClick={() => clearAll()}
|
onClick={() => clearAll()}
|
||||||
title={`${i18n.t("send.start_over")}`}
|
title={i18n.t("send.start_over")}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<LargeHeader>{i18n.t("send_bitcoin")}</LargeHeader>
|
<LargeHeader>{i18n.t("send_bitcoin")}</LargeHeader>
|
||||||
<SuccessModal
|
<SuccessModal
|
||||||
confirmText={
|
confirmText={
|
||||||
sentDetails()?.amount
|
sentDetails()?.amount
|
||||||
? `${i18n.t("send.nice")}`
|
? i18n.t("common.nice")
|
||||||
: `${i18n.t("send.home")}`
|
: i18n.t("common.home")
|
||||||
}
|
}
|
||||||
open={!!sentDetails()}
|
open={!!sentDetails()}
|
||||||
setOpen={(open: boolean) => {
|
setOpen={(open: boolean) => {
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export default function Swap() {
|
|||||||
if (peer) {
|
if (peer) {
|
||||||
setSelectedPeer(peer.pubkey);
|
setSelectedPeer(peer.pubkey);
|
||||||
} else {
|
} else {
|
||||||
showToast(new Error("Peer not found"));
|
showToast(new Error(i18n.t("swap.peer_not_found")));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast(eify(e));
|
showToast(eify(e));
|
||||||
@@ -198,11 +198,11 @@ export default function Swap() {
|
|||||||
const network = state.mutiny_wallet?.get_network() as Network;
|
const network = state.mutiny_wallet?.get_network() as Network;
|
||||||
|
|
||||||
if (network === "bitcoin" && amountSats() < 50000n) {
|
if (network === "bitcoin" && amountSats() < 50000n) {
|
||||||
return "It's just silly to make a channel smaller than 50,000 sats";
|
return i18n.t("swap.channel_too_small", { amount: "50,000" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (amountSats() < 10000n) {
|
if (amountSats() < 10000n) {
|
||||||
return "It's just silly to make a channel smaller than 10,000 sats";
|
return i18n.t("swap.channel_too_small", { amount: "10,000" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -211,7 +211,7 @@ export default function Swap() {
|
|||||||
(state.balance?.unconfirmed || 0n) ||
|
(state.balance?.unconfirmed || 0n) ||
|
||||||
!feeEstimate()
|
!feeEstimate()
|
||||||
) {
|
) {
|
||||||
return "You don't have enough funds to make this channel";
|
return i18n.t("swap.insufficient_funds");
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -266,10 +266,12 @@ export default function Swap() {
|
|||||||
<SafeArea>
|
<SafeArea>
|
||||||
<DefaultMain>
|
<DefaultMain>
|
||||||
<BackLink />
|
<BackLink />
|
||||||
<LargeHeader>Swap to Lightning</LargeHeader>
|
<LargeHeader>{i18n.t("swap.header")}</LargeHeader>
|
||||||
<SuccessModal
|
<SuccessModal
|
||||||
confirmText={
|
confirmText={
|
||||||
channelOpenResult()?.channel ? "Nice" : "Home"
|
channelOpenResult()?.channel
|
||||||
|
? i18n.t("common.nice")
|
||||||
|
: i18n.t("common.home")
|
||||||
}
|
}
|
||||||
open={!!channelOpenResult()}
|
open={!!channelOpenResult()}
|
||||||
setOpen={(open: boolean) => {
|
setOpen={(open: boolean) => {
|
||||||
@@ -295,14 +297,13 @@ export default function Swap() {
|
|||||||
<MegaCheck />
|
<MegaCheck />
|
||||||
<div class="flex flex-col justify-center">
|
<div class="flex flex-col justify-center">
|
||||||
<h1 class="w-full mt-4 mb-2 justify-center text-2xl font-semibold text-center md:text-3xl">
|
<h1 class="w-full mt-4 mb-2 justify-center text-2xl font-semibold text-center md:text-3xl">
|
||||||
Swap Initiated
|
{i18n.t("swap.initiated")}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-xl text-center">
|
<p class="text-xl text-center">
|
||||||
+
|
+
|
||||||
{channelOpenResult()?.channel?.balance.toLocaleString() ??
|
{channelOpenResult()?.channel?.balance.toLocaleString() ??
|
||||||
"0"}{" "}
|
"0"}{" "}
|
||||||
sats will be added to your Lightning
|
{i18n.t("swap.sats_added")}
|
||||||
balance
|
|
||||||
</p>
|
</p>
|
||||||
<AmountFiat
|
<AmountFiat
|
||||||
amountSats={
|
amountSats={
|
||||||
@@ -348,7 +349,7 @@ export default function Swap() {
|
|||||||
for="peerselect"
|
for="peerselect"
|
||||||
class="uppercase font-semibold text-sm"
|
class="uppercase font-semibold text-sm"
|
||||||
>
|
>
|
||||||
Use existing peer
|
{i18n.t("swap.use_existing")}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
name="peerselect"
|
name="peerselect"
|
||||||
@@ -361,7 +362,7 @@ export default function Swap() {
|
|||||||
class=""
|
class=""
|
||||||
selected
|
selected
|
||||||
>
|
>
|
||||||
Choose a peer
|
{i18n.t("swap.choose_peer")}
|
||||||
</option>
|
</option>
|
||||||
<For each={peers()}>
|
<For each={peers()}>
|
||||||
{(peer) => (
|
{(peer) => (
|
||||||
@@ -389,8 +390,12 @@ export default function Swap() {
|
|||||||
{...props}
|
{...props}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
error={field.error}
|
error={field.error}
|
||||||
label="Connect to new peer"
|
label={i18n.t(
|
||||||
placeholder="Peer connect string"
|
"swap.peer_connect_label"
|
||||||
|
)}
|
||||||
|
placeholder={i18n.t(
|
||||||
|
"swap.peer_connect_placeholder"
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
@@ -400,8 +405,12 @@ export default function Swap() {
|
|||||||
disabled={isConnecting()}
|
disabled={isConnecting()}
|
||||||
>
|
>
|
||||||
{isConnecting()
|
{isConnecting()
|
||||||
? "Connecting..."
|
? i18n.t(
|
||||||
: "Connect"}
|
"swap.connecting"
|
||||||
|
)
|
||||||
|
: i18n.t(
|
||||||
|
"swap.connect"
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
</Show>
|
</Show>
|
||||||
@@ -429,7 +438,7 @@ export default function Swap() {
|
|||||||
onClick={handleSwap}
|
onClick={handleSwap}
|
||||||
loading={loading()}
|
loading={loading()}
|
||||||
>
|
>
|
||||||
{"Confirm Swap"}
|
{i18n.t("swap.confirm_swap")}
|
||||||
</Button>
|
</Button>
|
||||||
</DefaultMain>
|
</DefaultMain>
|
||||||
<NavBar activeTab="none" />
|
<NavBar activeTab="none" />
|
||||||
|
|||||||
@@ -6,18 +6,20 @@ import {
|
|||||||
LargeHeader,
|
LargeHeader,
|
||||||
SafeArea
|
SafeArea
|
||||||
} from "~/components/layout";
|
} from "~/components/layout";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
export default function NotFound() {
|
export default function NotFound() {
|
||||||
|
const i18n = useI18n();
|
||||||
return (
|
return (
|
||||||
<SafeArea>
|
<SafeArea>
|
||||||
<Title>Not Found</Title>
|
<Title>{i18n.t("not_found.title")}</Title>
|
||||||
<HttpStatusCode code={404} />
|
<HttpStatusCode code={404} />
|
||||||
<DefaultMain>
|
<DefaultMain>
|
||||||
<LargeHeader>Not Found</LargeHeader>
|
<LargeHeader>{i18n.t("not_found.title")}</LargeHeader>
|
||||||
<p>This is probably Paul's fault.</p>
|
<p>{i18n.t("not_found.wtf_paul")}</p>
|
||||||
<div class="h-full" />
|
<div class="h-full" />
|
||||||
<ButtonLink href="/" intent="red">
|
<ButtonLink href="/" intent="red">
|
||||||
Dangit
|
{i18n.t("common.dangit")}
|
||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
</DefaultMain>
|
</DefaultMain>
|
||||||
</SafeArea>
|
</SafeArea>
|
||||||
|
|||||||
@@ -11,26 +11,27 @@ import {
|
|||||||
VStack
|
VStack
|
||||||
} from "~/components/layout";
|
} from "~/components/layout";
|
||||||
import { BackLink } from "~/components/layout/BackLink";
|
import { BackLink } from "~/components/layout/BackLink";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
export default function Admin() {
|
export default function Admin() {
|
||||||
|
const i18n = useI18n();
|
||||||
return (
|
return (
|
||||||
<MutinyWalletGuard>
|
<MutinyWalletGuard>
|
||||||
<SafeArea>
|
<SafeArea>
|
||||||
<DefaultMain>
|
<DefaultMain>
|
||||||
<BackLink href="/settings" title="Settings" />
|
<BackLink
|
||||||
<LargeHeader>Secret Debug Tools</LargeHeader>
|
href="/settings"
|
||||||
|
title={i18n.t("settings.header")}
|
||||||
|
/>
|
||||||
|
<LargeHeader>{i18n.t("settings.admin.header")}</LargeHeader>
|
||||||
<VStack>
|
<VStack>
|
||||||
<NiceP>
|
<NiceP>{i18n.t("settings.admin.warning_one")}</NiceP>
|
||||||
If you know what you're doing you're in the right
|
<NiceP>{i18n.t("settings.admin.warning_two")}</NiceP>
|
||||||
place.
|
|
||||||
</NiceP>
|
|
||||||
<NiceP>
|
|
||||||
These are internal tools we use to debug and test
|
|
||||||
the app. Please be careful!
|
|
||||||
</NiceP>
|
|
||||||
<KitchenSink />
|
<KitchenSink />
|
||||||
<div class="rounded-xl p-4 flex flex-col gap-2 bg-m-red overflow-x-hidden">
|
<div class="rounded-xl p-4 flex flex-col gap-2 bg-m-red overflow-x-hidden">
|
||||||
<SmallHeader>Danger zone</SmallHeader>
|
<SmallHeader>
|
||||||
|
{i18n.t("settings.danger_zone")}
|
||||||
|
</SmallHeader>
|
||||||
<DeleteEverything />
|
<DeleteEverything />
|
||||||
</div>
|
</div>
|
||||||
</VStack>
|
</VStack>
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ import { SeedWords } from "~/components/SeedWords";
|
|||||||
import { useMegaStore } from "~/state/megaStore";
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
import { Show, createEffect, createSignal } from "solid-js";
|
import { Show, createEffect, createSignal } from "solid-js";
|
||||||
import { BackLink } from "~/components/layout/BackLink";
|
import { BackLink } from "~/components/layout/BackLink";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
function Quiz(props: { setHasCheckedAll: (hasChecked: boolean) => void }) {
|
function Quiz(props: { setHasCheckedAll: (hasChecked: boolean) => void }) {
|
||||||
|
const i18n = useI18n();
|
||||||
const [one, setOne] = createSignal(false);
|
const [one, setOne] = createSignal(false);
|
||||||
const [two, setTwo] = createSignal(false);
|
const [two, setTwo] = createSignal(false);
|
||||||
const [three, setThree] = createSignal(false);
|
const [three, setThree] = createSignal(false);
|
||||||
@@ -33,23 +35,24 @@ function Quiz(props: { setHasCheckedAll: (hasChecked: boolean) => void }) {
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
checked={one()}
|
checked={one()}
|
||||||
onChange={setOne}
|
onChange={setOne}
|
||||||
label="I wrote down the words"
|
label={i18n.t("settings.backup.confirm")}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={two()}
|
checked={two()}
|
||||||
onChange={setTwo}
|
onChange={setTwo}
|
||||||
label="I understand that my funds are my responsibility"
|
label={i18n.t("settings.backup.responsibility")}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={three()}
|
checked={three()}
|
||||||
onChange={setThree}
|
onChange={setThree}
|
||||||
label="I'm not lying just to get this over with"
|
label={i18n.t("settings.backup.liar")}
|
||||||
/>
|
/>
|
||||||
</VStack>
|
</VStack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Backup() {
|
export default function Backup() {
|
||||||
|
const i18n = useI18n();
|
||||||
const [store, actions] = useMegaStore();
|
const [store, actions] = useMegaStore();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -69,23 +72,19 @@ export default function Backup() {
|
|||||||
<MutinyWalletGuard>
|
<MutinyWalletGuard>
|
||||||
<SafeArea>
|
<SafeArea>
|
||||||
<DefaultMain>
|
<DefaultMain>
|
||||||
<BackLink href="/settings" title="Settings" />
|
<BackLink
|
||||||
<LargeHeader>Backup</LargeHeader>
|
href="/settings"
|
||||||
|
title={i18n.t("settings.header")}
|
||||||
|
/>
|
||||||
|
<LargeHeader>{i18n.t("settings.backup.title")}</LargeHeader>
|
||||||
|
|
||||||
<VStack>
|
<VStack>
|
||||||
<NiceP>Let's get these funds secured.</NiceP>
|
<NiceP>{i18n.t("settings.backup.secure_funds")}</NiceP>
|
||||||
<NiceP>
|
<NiceP>
|
||||||
We'll show you 12 words. You write down the 12
|
{i18n.t("settings.backup.twelve_words_tip")}
|
||||||
words.
|
|
||||||
</NiceP>
|
|
||||||
<NiceP>
|
|
||||||
If you clear your browser history, or lose your
|
|
||||||
device, these 12 words are the only way you can
|
|
||||||
restore your wallet.
|
|
||||||
</NiceP>
|
|
||||||
<NiceP>
|
|
||||||
Mutiny is self-custodial. It's all up to you...
|
|
||||||
</NiceP>
|
</NiceP>
|
||||||
|
<NiceP>{i18n.t("settings.backup.warning_one")}</NiceP>
|
||||||
|
<NiceP>{i18n.t("settings.backup.warning_two")}</NiceP>
|
||||||
<SeedWords
|
<SeedWords
|
||||||
words={store.mutiny_wallet?.show_seed() || ""}
|
words={store.mutiny_wallet?.show_seed() || ""}
|
||||||
setHasSeen={setHasSeenBackup}
|
setHasSeen={setHasSeenBackup}
|
||||||
@@ -99,7 +98,7 @@ export default function Backup() {
|
|||||||
onClick={wroteDownTheWords}
|
onClick={wroteDownTheWords}
|
||||||
loading={loading()}
|
loading={loading()}
|
||||||
>
|
>
|
||||||
I wrote down the words
|
{i18n.t("settings.backup.confirm")}
|
||||||
</Button>
|
</Button>
|
||||||
</VStack>
|
</VStack>
|
||||||
</DefaultMain>
|
</DefaultMain>
|
||||||
|
|||||||
@@ -14,13 +14,17 @@ import {
|
|||||||
import { AmountSmall } from "~/components/Amount";
|
import { AmountSmall } from "~/components/Amount";
|
||||||
import { BackLink } from "~/components/layout/BackLink";
|
import { BackLink } from "~/components/layout/BackLink";
|
||||||
import NavBar from "~/components/NavBar";
|
import NavBar from "~/components/NavBar";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
function BalanceBar(props: { inbound: number; outbound: number }) {
|
function BalanceBar(props: { inbound: number; outbound: number }) {
|
||||||
|
const i18n = useI18n();
|
||||||
return (
|
return (
|
||||||
<VStack smallgap>
|
<VStack smallgap>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<SmallHeader>Outbound</SmallHeader>
|
<SmallHeader>
|
||||||
<SmallHeader>Inbound</SmallHeader>
|
{i18n.t("settings.channels.outbound")}
|
||||||
|
</SmallHeader>
|
||||||
|
<SmallHeader>{i18n.t("settings.channels.inbound")}</SmallHeader>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-1 w-full">
|
<div class="flex gap-1 w-full">
|
||||||
<div
|
<div
|
||||||
@@ -45,6 +49,7 @@ function BalanceBar(props: { inbound: number; outbound: number }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function LiquidityMonitor() {
|
export function LiquidityMonitor() {
|
||||||
|
const i18n = useI18n();
|
||||||
const [state, _actions] = useMegaStore();
|
const [state, _actions] = useMegaStore();
|
||||||
|
|
||||||
const [channelInfo] = createResource(async () => {
|
const [channelInfo] = createResource(async () => {
|
||||||
@@ -71,41 +76,41 @@ export function LiquidityMonitor() {
|
|||||||
<Match when={channelInfo()?.channelCount}>
|
<Match when={channelInfo()?.channelCount}>
|
||||||
<Card>
|
<Card>
|
||||||
<NiceP>
|
<NiceP>
|
||||||
You have {channelInfo()?.channelCount} lightning{" "}
|
{i18n.t("settings.channels.have_channels")}{" "}
|
||||||
|
{channelInfo()?.channelCount}{" "}
|
||||||
{channelInfo()?.channelCount === 1
|
{channelInfo()?.channelCount === 1
|
||||||
? "channel"
|
? i18n.t("settings.channels.have_channels_one")
|
||||||
: "channels"}
|
: i18n.t("settings.channels.have_channels_many")}
|
||||||
.
|
|
||||||
</NiceP>{" "}
|
</NiceP>{" "}
|
||||||
<BalanceBar
|
<BalanceBar
|
||||||
inbound={Number(channelInfo()?.inbound) || 0}
|
inbound={Number(channelInfo()?.inbound) || 0}
|
||||||
outbound={Number(state.balance?.lightning) || 0}
|
outbound={Number(state.balance?.lightning) || 0}
|
||||||
/>
|
/>
|
||||||
<TinyText>
|
<TinyText>
|
||||||
Outbound is the amount of money you can spend on
|
{i18n.t("settings.channels.inbound_outbound_tip")}
|
||||||
lightning. Inbound is the amount you can receive without
|
|
||||||
incurring a lightning service fee.
|
|
||||||
</TinyText>
|
</TinyText>
|
||||||
</Card>
|
</Card>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={true}>
|
<Match when={true}>
|
||||||
<NiceP>
|
<NiceP>{i18n.t("settings.channels.no_channels")}</NiceP>
|
||||||
It looks like you don't have any channels yet. To get
|
|
||||||
started, receive some sats over lightning, or swap some
|
|
||||||
on-chain funds into a channel. Get your hands dirty!
|
|
||||||
</NiceP>
|
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Channels() {
|
export default function Channels() {
|
||||||
|
const i18n = useI18n();
|
||||||
return (
|
return (
|
||||||
<MutinyWalletGuard>
|
<MutinyWalletGuard>
|
||||||
<SafeArea>
|
<SafeArea>
|
||||||
<DefaultMain>
|
<DefaultMain>
|
||||||
<BackLink href="/settings" title="Settings" />
|
<BackLink
|
||||||
<LargeHeader>Lightning Channels</LargeHeader>
|
href="/settings"
|
||||||
|
title={i18n.t("settings.header")}
|
||||||
|
/>
|
||||||
|
<LargeHeader>
|
||||||
|
{i18n.t("settings.channels.title")}
|
||||||
|
</LargeHeader>
|
||||||
<LiquidityMonitor />
|
<LiquidityMonitor />
|
||||||
</DefaultMain>
|
</DefaultMain>
|
||||||
<NavBar activeTab="settings" />
|
<NavBar activeTab="settings" />
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ import { BackLink } from "~/components/layout/BackLink";
|
|||||||
import { TextField } from "~/components/layout/TextField";
|
import { TextField } from "~/components/layout/TextField";
|
||||||
import { useMegaStore } from "~/state/megaStore";
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
import eify from "~/utils/eify";
|
import eify from "~/utils/eify";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
function Nwc() {
|
function Nwc() {
|
||||||
|
const i18n = useI18n();
|
||||||
const [state, _actions] = useMegaStore();
|
const [state, _actions] = useMegaStore();
|
||||||
|
|
||||||
const [nwcProfiles, { refetch }] = createResource(async () => {
|
const [nwcProfiles, { refetch }] = createResource(async () => {
|
||||||
@@ -47,7 +49,7 @@ function Nwc() {
|
|||||||
setCreateLoading(true);
|
setCreateLoading(true);
|
||||||
|
|
||||||
if (formName() === "") {
|
if (formName() === "") {
|
||||||
setError("Name cannot be empty");
|
setError(i18n.t("settings.connections.error_name"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const profile = await state.mutiny_wallet?.create_nwc_profile(
|
const profile = await state.mutiny_wallet?.create_nwc_profile(
|
||||||
@@ -56,7 +58,7 @@ function Nwc() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
setError("Failed to create Wallet Connection");
|
setError(i18n.t("settings.connections.error_connection"));
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
refetch();
|
refetch();
|
||||||
@@ -93,10 +95,12 @@ function Nwc() {
|
|||||||
return (
|
return (
|
||||||
<VStack biggap>
|
<VStack biggap>
|
||||||
<Button intent="blue" onClick={() => setDialogOpen(true)}>
|
<Button intent="blue" onClick={() => setDialogOpen(true)}>
|
||||||
Add Connection
|
{i18n.t("settings.connections.add_connection")}
|
||||||
</Button>
|
</Button>
|
||||||
<Show when={nwcProfiles() && nwcProfiles()!.length > 0}>
|
<Show when={nwcProfiles() && nwcProfiles()!.length > 0}>
|
||||||
<SettingsCard title="Manage Connections">
|
<SettingsCard
|
||||||
|
title={i18n.t("settings.connections.manage_connections")}
|
||||||
|
>
|
||||||
<For each={nwcProfiles()}>
|
<For each={nwcProfiles()}>
|
||||||
{(profile) => (
|
{(profile) => (
|
||||||
<Collapser
|
<Collapser
|
||||||
@@ -121,7 +125,13 @@ function Nwc() {
|
|||||||
layout="small"
|
layout="small"
|
||||||
onClick={() => toggleEnabled(profile)}
|
onClick={() => toggleEnabled(profile)}
|
||||||
>
|
>
|
||||||
{profile.enabled ? "Disable" : "Enable"}
|
{profile.enabled
|
||||||
|
? i18n.t(
|
||||||
|
"settings.connections.disable_connection"
|
||||||
|
)
|
||||||
|
: i18n.t(
|
||||||
|
"settings.connections.enable_connection"
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</VStack>
|
</VStack>
|
||||||
</Collapser>
|
</Collapser>
|
||||||
@@ -132,19 +142,23 @@ function Nwc() {
|
|||||||
<SimpleDialog
|
<SimpleDialog
|
||||||
open={dialogOpen()}
|
open={dialogOpen()}
|
||||||
setOpen={setDialogOpen}
|
setOpen={setDialogOpen}
|
||||||
title="New Connection"
|
title={i18n.t("settings.connections.new_connection")}
|
||||||
>
|
>
|
||||||
<div class="flex flex-col gap-4 py-4">
|
<div class="flex flex-col gap-4 py-4">
|
||||||
<TextField
|
<TextField
|
||||||
name="name"
|
name="name"
|
||||||
label="Name"
|
label={i18n.t(
|
||||||
|
"settings.connections.new_connection_label"
|
||||||
|
)}
|
||||||
ref={noop}
|
ref={noop}
|
||||||
value={formName()}
|
value={formName()}
|
||||||
onInput={(e) => setFormName(e.currentTarget.value)}
|
onInput={(e) => setFormName(e.currentTarget.value)}
|
||||||
error={""}
|
error={""}
|
||||||
onBlur={noop}
|
onBlur={noop}
|
||||||
onChange={noop}
|
onChange={noop}
|
||||||
placeholder="My favorite nostr client..."
|
placeholder={i18n.t(
|
||||||
|
"settings.connections.new_connection_placeholder"
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<Show when={error()}>
|
<Show when={error()}>
|
||||||
<InfoBox accent="red">{error()}</InfoBox>
|
<InfoBox accent="red">{error()}</InfoBox>
|
||||||
@@ -156,7 +170,7 @@ function Nwc() {
|
|||||||
loading={createLoading()}
|
loading={createLoading()}
|
||||||
onClick={createConnection}
|
onClick={createConnection}
|
||||||
>
|
>
|
||||||
Create Connection
|
{i18n.t("settings.connections.create_connection")}
|
||||||
</Button>
|
</Button>
|
||||||
</SimpleDialog>
|
</SimpleDialog>
|
||||||
</VStack>
|
</VStack>
|
||||||
@@ -164,16 +178,19 @@ function Nwc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Connections() {
|
export default function Connections() {
|
||||||
|
const i18n = useI18n();
|
||||||
return (
|
return (
|
||||||
<MutinyWalletGuard>
|
<MutinyWalletGuard>
|
||||||
<SafeArea>
|
<SafeArea>
|
||||||
<DefaultMain>
|
<DefaultMain>
|
||||||
<BackLink href="/settings" title="Settings" />
|
<BackLink
|
||||||
<LargeHeader>Wallet Connections</LargeHeader>
|
href="/settings"
|
||||||
<NiceP>
|
title={i18n.t("settings.header")}
|
||||||
Authorize external services to request payments from
|
/>
|
||||||
your wallet. Pairs great with Nostr clients.
|
<LargeHeader>
|
||||||
</NiceP>
|
{i18n.t("settings.connections.title")}
|
||||||
|
</LargeHeader>
|
||||||
|
<NiceP>{i18n.t("settings.connections.authorize")}</NiceP>
|
||||||
<Nwc />
|
<Nwc />
|
||||||
<div class="h-full" />
|
<div class="h-full" />
|
||||||
</DefaultMain>
|
</DefaultMain>
|
||||||
|
|||||||
@@ -13,14 +13,16 @@ import {
|
|||||||
} from "~/components/layout";
|
} from "~/components/layout";
|
||||||
import { BackLink } from "~/components/layout/BackLink";
|
import { BackLink } from "~/components/layout/BackLink";
|
||||||
import { ExternalLink } from "~/components/layout/ExternalLink";
|
import { ExternalLink } from "~/components/layout/ExternalLink";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
function EmergencyStack() {
|
function EmergencyStack() {
|
||||||
|
const i18n = useI18n();
|
||||||
return (
|
return (
|
||||||
<VStack>
|
<VStack>
|
||||||
<ImportExport emergency />
|
<ImportExport emergency />
|
||||||
<Logs />
|
<Logs />
|
||||||
<div class="rounded-xl p-4 flex flex-col gap-2 bg-m-red overflow-x-hidden">
|
<div class="rounded-xl p-4 flex flex-col gap-2 bg-m-red overflow-x-hidden">
|
||||||
<SmallHeader>Danger zone</SmallHeader>
|
<SmallHeader>{i18n.t("settings.danger_zone")}</SmallHeader>
|
||||||
<DeleteEverything emergency />
|
<DeleteEverything emergency />
|
||||||
</div>
|
</div>
|
||||||
</VStack>
|
</VStack>
|
||||||
@@ -28,22 +30,23 @@ function EmergencyStack() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function EmergencyKit() {
|
export default function EmergencyKit() {
|
||||||
|
const i18n = useI18n();
|
||||||
return (
|
return (
|
||||||
<SafeArea>
|
<SafeArea>
|
||||||
<DefaultMain>
|
<DefaultMain>
|
||||||
<BackLink href="/settings" title="Settings" />
|
<BackLink href="/settings" title={i18n.t("settings.header")} />
|
||||||
<LargeHeader>Emergency Kit</LargeHeader>
|
<LargeHeader>
|
||||||
|
{i18n.t("settings.emergency_kit.title")}
|
||||||
|
</LargeHeader>
|
||||||
<VStack>
|
<VStack>
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
<NiceP>
|
<NiceP>
|
||||||
If your wallet seems broken, here are some tools to try
|
{i18n.t("settings.emergency_kit.emergency_tip")}
|
||||||
to debug and repair it.
|
|
||||||
</NiceP>
|
</NiceP>
|
||||||
<NiceP>
|
<NiceP>
|
||||||
If you have any questions on what these buttons do,
|
{i18n.t("settings.emergency_kit.questions")}{" "}
|
||||||
please{" "}
|
|
||||||
<ExternalLink href="https://matrix.to/#/#mutiny-community:lightninghackers.com">
|
<ExternalLink href="https://matrix.to/#/#mutiny-community:lightninghackers.com">
|
||||||
reach out to us for support.
|
{i18n.t("settings.emergency_kit.link")}
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
</NiceP>
|
</NiceP>
|
||||||
<EmergencyStack />
|
<EmergencyStack />
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { TextField } from "~/components/layout/TextField";
|
|||||||
import { timeout } from "~/utils/timeout";
|
import { timeout } from "~/utils/timeout";
|
||||||
import eify from "~/utils/eify";
|
import eify from "~/utils/eify";
|
||||||
import { InfoBox } from "~/components/InfoBox";
|
import { InfoBox } from "~/components/InfoBox";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
type EncryptPasswordForm = {
|
type EncryptPasswordForm = {
|
||||||
existingPassword: string;
|
existingPassword: string;
|
||||||
@@ -25,6 +26,7 @@ type EncryptPasswordForm = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function Encrypt() {
|
export default function Encrypt() {
|
||||||
|
const i18n = useI18n();
|
||||||
const [store, _actions] = useMegaStore();
|
const [store, _actions] = useMegaStore();
|
||||||
const [error, setError] = createSignal<Error>();
|
const [error, setError] = createSignal<Error>();
|
||||||
const [loading, setLoading] = createSignal(false);
|
const [loading, setLoading] = createSignal(false);
|
||||||
@@ -66,7 +68,10 @@ export default function Encrypt() {
|
|||||||
<MutinyWalletGuard>
|
<MutinyWalletGuard>
|
||||||
<SafeArea>
|
<SafeArea>
|
||||||
<DefaultMain>
|
<DefaultMain>
|
||||||
<BackLink href="/settings" title="Settings" />
|
<BackLink
|
||||||
|
href="/settings"
|
||||||
|
title={i18n.t("settings.header")}
|
||||||
|
/>
|
||||||
<LargeHeader>
|
<LargeHeader>
|
||||||
Encrypt your seed words (optional)
|
Encrypt your seed words (optional)
|
||||||
</LargeHeader>
|
</LargeHeader>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import NavBar from "~/components/NavBar";
|
|||||||
import { A } from "solid-start";
|
import { A } from "solid-start";
|
||||||
import { For, Show } from "solid-js";
|
import { For, Show } from "solid-js";
|
||||||
import forward from "~/assets/icons/forward.svg";
|
import forward from "~/assets/icons/forward.svg";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
function SettingsLinkList(props: {
|
function SettingsLinkList(props: {
|
||||||
header: string;
|
header: string;
|
||||||
@@ -57,36 +58,37 @@ function SettingsLinkList(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
|
const i18n = useI18n();
|
||||||
return (
|
return (
|
||||||
<SafeArea>
|
<SafeArea>
|
||||||
<DefaultMain>
|
<DefaultMain>
|
||||||
<BackLink />
|
<BackLink />
|
||||||
<LargeHeader>Settings</LargeHeader>
|
<LargeHeader>{i18n.t("settings.header")}</LargeHeader>
|
||||||
<VStack biggap>
|
<VStack biggap>
|
||||||
<SettingsLinkList
|
<SettingsLinkList
|
||||||
header="Mutiny+"
|
header={i18n.t("settings.mutiny_plus")}
|
||||||
links={[
|
links={[
|
||||||
{
|
{
|
||||||
href: "/settings/plus",
|
href: "/settings/plus",
|
||||||
text: "Learn how to support Mutiny"
|
text: i18n.t("settings.support")
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<SettingsLinkList
|
<SettingsLinkList
|
||||||
header="General"
|
header={i18n.t("settings.general")}
|
||||||
links={[
|
links={[
|
||||||
{
|
{
|
||||||
href: "/settings/channels",
|
href: "/settings/channels",
|
||||||
text: "Lightning Channels"
|
text: i18n.t("settings.channels.title")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: "/settings/backup",
|
href: "/settings/backup",
|
||||||
text: "Backup",
|
text: i18n.t("settings.backup.title"),
|
||||||
accent: "green"
|
accent: "green"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: "/settings/restore",
|
href: "/settings/restore",
|
||||||
text: "Restore",
|
text: i18n.t("settings.restore.title"),
|
||||||
accent: "red"
|
accent: "red"
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
@@ -99,39 +101,38 @@ export default function Settings() {
|
|||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
href: "/settings/servers",
|
href: "/settings/servers",
|
||||||
text: "Servers",
|
text: i18n.t("settings.servers.title"),
|
||||||
caption:
|
caption: i18n.t("settings.servers.caption")
|
||||||
"Don't trust us! Use your own servers to back Mutiny."
|
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<SettingsLinkList
|
<SettingsLinkList
|
||||||
header="Beta Features"
|
header={i18n.t("settings.beta_features")}
|
||||||
links={[
|
links={[
|
||||||
{
|
{
|
||||||
href: "/settings/connections",
|
href: "/settings/connections",
|
||||||
text: "Wallet Connections"
|
text: i18n.t("settings.connections.title")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: "/settings/lnurlauth",
|
href: "/settings/lnurlauth",
|
||||||
text: "LNURL Auth"
|
text: i18n.t("settings.lnurl_auth.title")
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<SettingsLinkList
|
<SettingsLinkList
|
||||||
header="Debug Tools"
|
header={i18n.t("settings.debug_tools")}
|
||||||
links={[
|
links={[
|
||||||
{
|
{
|
||||||
href: "/settings/emergencykit",
|
href: "/settings/emergencykit",
|
||||||
text: "Emergency Kit",
|
text: i18n.t("settings.emergency_kit.title"),
|
||||||
caption:
|
caption: i18n.t(
|
||||||
"Diagnose and solve problems with your wallet."
|
"settings.emergency_kit.caption"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: "/settings/admin",
|
href: "/settings/admin",
|
||||||
text: "Admin Page",
|
text: i18n.t("settings.admin.title"),
|
||||||
caption:
|
caption: i18n.t("settings.admin.caption"),
|
||||||
"Our internal debug tools. Use wisely!",
|
|
||||||
accent: "red"
|
accent: "red"
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
|
|||||||
Reference in New Issue
Block a user