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