mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2026-02-23 15:14:19 +01:00
bring back smug satisfaction
hermes domain formatting fixes confirm to add contact bump to rc4 send me sats modal hide button if no hermes env
This commit is contained in:
@@ -54,7 +54,7 @@
|
||||
"@capacitor/toast": "^5.0.6",
|
||||
"@kobalte/core": "^0.12.6",
|
||||
"@kobalte/tailwindcss": "^0.9.0",
|
||||
"@mutinywallet/mutiny-wasm": "0.6.2-rc3",
|
||||
"@mutinywallet/mutiny-wasm": "0.6.2-rc4",
|
||||
"@modular-forms/solid": "^0.20.0",
|
||||
"@solid-primitives/upload": "^0.0.117",
|
||||
"@solidjs/meta": "^0.29.3",
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -51,8 +51,8 @@ dependencies:
|
||||
specifier: ^0.20.0
|
||||
version: 0.20.0(solid-js@1.8.16)
|
||||
'@mutinywallet/mutiny-wasm':
|
||||
specifier: 0.6.2-rc3
|
||||
version: 0.6.2-rc3
|
||||
specifier: 0.6.2-rc4
|
||||
version: 0.6.2-rc4
|
||||
'@solid-primitives/upload':
|
||||
specifier: ^0.0.117
|
||||
version: 0.0.117(solid-js@1.8.16)
|
||||
@@ -2085,8 +2085,8 @@ packages:
|
||||
solid-js: 1.8.16
|
||||
dev: false
|
||||
|
||||
/@mutinywallet/mutiny-wasm@0.6.2-rc3:
|
||||
resolution: {integrity: sha512-/FxSLHuMxIuXYs2C6KbMNzBuGeSHri+BkIrtuJFI1JWG3+XcSiJF4HU2mqAwaZQH9jg25x+LvY71aZ7pM3tM/w==}
|
||||
/@mutinywallet/mutiny-wasm@0.6.2-rc4:
|
||||
resolution: {integrity: sha512-u9xzX6V6fAhOd1liOf6hsSzklTP/bkGBsqgN0DXfEu1p3alho3/5R0fqF64WbOQgwNBRS2b9WX0HDIZiXoz4Ow==}
|
||||
dev: false
|
||||
|
||||
/@nodelib/fs.scandir@2.1.5:
|
||||
|
||||
@@ -56,7 +56,8 @@
|
||||
"nym": "Nym"
|
||||
},
|
||||
"deleted": "Your nostr profile has been deleted.",
|
||||
"no_lightning_address": "No lightning address set"
|
||||
"no_lightning_address": "No lightning address set",
|
||||
"pay_me": "Send me sats"
|
||||
},
|
||||
"chat": {
|
||||
"prompt": "This is a new conversation. Try asking for money!",
|
||||
@@ -262,7 +263,9 @@
|
||||
"sweep_delay": "Funds may take a few days to be swept back into the wallet",
|
||||
"no_details": "No channel details found, which means this channel has likely been closed.",
|
||||
"back_home": "back home"
|
||||
}
|
||||
},
|
||||
"start_a_chat": "Start a chat?",
|
||||
"start_a_chat_are_you_sure": "This user isn't in your contact list."
|
||||
},
|
||||
"scanner": {
|
||||
"paste": "Paste Something",
|
||||
|
||||
@@ -3,7 +3,14 @@ import { cache, createAsync, revalidate, useNavigate } from "@solidjs/router";
|
||||
import { Plus, Save, Search, Shuffle, Users } from "lucide-solid";
|
||||
import { createEffect, createSignal, For, Match, Show, Switch } from "solid-js";
|
||||
|
||||
import { ActivityDetailsModal, ButtonCard, NiceP } from "~/components";
|
||||
import {
|
||||
ActivityDetailsModal,
|
||||
Button,
|
||||
ButtonCard,
|
||||
ContactButton,
|
||||
NiceP,
|
||||
SimpleDialog
|
||||
} from "~/components";
|
||||
import { useI18n } from "~/i18n/context";
|
||||
import { PrivacyLevel } from "~/routes";
|
||||
import { useMegaStore } from "~/state/megaStore";
|
||||
@@ -11,6 +18,7 @@ import {
|
||||
actuallyFetchNostrProfile,
|
||||
hexpubFromNpub,
|
||||
profileToPseudoContact,
|
||||
PseudoContact,
|
||||
timeAgo
|
||||
} from "~/utils";
|
||||
|
||||
@@ -36,6 +44,7 @@ export interface IActivityItem {
|
||||
export function UnifiedActivityItem(props: {
|
||||
item: IActivityItem;
|
||||
onClick: (id: string, kind: HackActivityType) => void;
|
||||
onNewContactClick: (profile: PseudoContact) => void;
|
||||
}) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -165,7 +174,9 @@ export function UnifiedActivityItem(props: {
|
||||
primaryOnClick={() =>
|
||||
primaryContact()?.id
|
||||
? navigate(`/chat/${primaryContact()?.id}`)
|
||||
: undefined
|
||||
: profileFromNostr()
|
||||
? props.onNewContactClick(profileFromNostr()!)
|
||||
: undefined
|
||||
}
|
||||
amountOnClick={click}
|
||||
primaryName={
|
||||
@@ -194,6 +205,74 @@ export function UnifiedActivityItem(props: {
|
||||
);
|
||||
}
|
||||
|
||||
function NewContactModal(props: { profile: PseudoContact; close: () => void }) {
|
||||
const i18n = useI18n();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [state, _actions] = useMegaStore();
|
||||
|
||||
async function createContact() {
|
||||
try {
|
||||
const existingContact =
|
||||
await state.mutiny_wallet?.get_contact_for_npub(
|
||||
props.profile.hexpub
|
||||
);
|
||||
|
||||
if (existingContact) {
|
||||
navigate(`/chat/${existingContact.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const contactId = await state.mutiny_wallet?.create_new_contact(
|
||||
props.profile.name,
|
||||
props.profile.hexpub,
|
||||
props.profile.ln_address,
|
||||
props.profile.lnurl,
|
||||
props.profile.image_url
|
||||
);
|
||||
|
||||
if (!contactId) {
|
||||
throw new Error("no contact id returned");
|
||||
}
|
||||
|
||||
const tagItem = await state.mutiny_wallet?.get_tag_item(contactId);
|
||||
|
||||
if (!tagItem) {
|
||||
throw new Error("no contact returned");
|
||||
}
|
||||
|
||||
navigate(`/chat/${contactId}`);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<SimpleDialog
|
||||
title={i18n.t("activity.start_a_chat")}
|
||||
open
|
||||
setOpen={() => {
|
||||
props.close();
|
||||
}}
|
||||
>
|
||||
<NiceP>{i18n.t("activity.start_a_chat_are_you_sure")}</NiceP>
|
||||
<ContactButton contact={props.profile} onClick={() => {}} />
|
||||
<div class="flex-end flex w-full justify-end gap-2">
|
||||
<Button
|
||||
layout="small"
|
||||
intent="red"
|
||||
onClick={() => props.close()}
|
||||
>
|
||||
{i18n.t("modals.confirm_dialog.cancel")}
|
||||
</Button>
|
||||
<Button layout="small" intent="blue" onClick={createContact}>
|
||||
{i18n.t("common.continue")}
|
||||
</Button>
|
||||
</div>
|
||||
</SimpleDialog>
|
||||
);
|
||||
}
|
||||
|
||||
export function CombinedActivity() {
|
||||
const [state, _actions] = useMegaStore();
|
||||
const i18n = useI18n();
|
||||
@@ -239,6 +318,8 @@ export function CombinedActivity() {
|
||||
}
|
||||
});
|
||||
|
||||
const [newContact, setNewContact] = createSignal<PseudoContact>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Show when={detailsId() && detailsKind()}>
|
||||
@@ -249,6 +330,12 @@ export function CombinedActivity() {
|
||||
setOpen={setDetailsOpen}
|
||||
/>
|
||||
</Show>
|
||||
<Show when={newContact()}>
|
||||
<NewContactModal
|
||||
profile={newContact()!}
|
||||
close={() => setNewContact(undefined)}
|
||||
/>
|
||||
</Show>
|
||||
<Switch>
|
||||
<Match when={activity().length === 0}>
|
||||
<Show when={state.federations?.length === 0}>
|
||||
@@ -301,6 +388,7 @@ export function CombinedActivity() {
|
||||
<UnifiedActivityItem
|
||||
item={activityItem}
|
||||
onClick={openDetailsModal}
|
||||
onNewContactClick={setNewContact}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
import { Copy, QrCode } from "lucide-solid";
|
||||
import { createSignal, Match, Switch } from "solid-js";
|
||||
import { createMemo, createSignal, Match, Show, Switch } from "solid-js";
|
||||
import { QRCodeSVG } from "solid-qr-code";
|
||||
|
||||
import { FancyCard, SimpleDialog } from "~/components";
|
||||
import { FancyCard, LabelCircle, SimpleDialog } from "~/components";
|
||||
import { useI18n } from "~/i18n/context";
|
||||
import { UserProfile } from "~/routes";
|
||||
import { useCopy } from "~/utils";
|
||||
|
||||
export function LightningAddressShower(props: { lud16: string }) {
|
||||
export function LightningAddressShower(props: {
|
||||
lud16?: string;
|
||||
profile?: UserProfile;
|
||||
}) {
|
||||
const i18n = useI18n();
|
||||
const [showQr, setShowQr] = createSignal(false);
|
||||
|
||||
const [copy, copied] = useCopy({ copiedTimeout: 1000 });
|
||||
|
||||
const lud16 = createMemo(() => {
|
||||
return props.lud16 || props.profile?.lud16 || "";
|
||||
});
|
||||
|
||||
return (
|
||||
<FancyCard>
|
||||
<Switch>
|
||||
<Match when={props.lud16}>
|
||||
<Match when={lud16()}>
|
||||
<p class="break-all text-center font-system-mono text-base ">
|
||||
{props.lud16}
|
||||
{lud16()}
|
||||
</p>
|
||||
<div class="flex w-full justify-center gap-8">
|
||||
<button onClick={() => setShowQr(true)}>
|
||||
@@ -28,7 +36,7 @@ export function LightningAddressShower(props: { lud16: string }) {
|
||||
classList={{
|
||||
"bg-m-red rounded": copied()
|
||||
}}
|
||||
onClick={() => copy(props.lud16)}
|
||||
onClick={() => copy(lud16())}
|
||||
>
|
||||
<Copy class="inline-block" />
|
||||
</button>
|
||||
@@ -38,11 +46,30 @@ export function LightningAddressShower(props: { lud16: string }) {
|
||||
setOpen={(open) => {
|
||||
setShowQr(open);
|
||||
}}
|
||||
title={"Lightning Address"}
|
||||
title={i18n.t("profile.pay_me")}
|
||||
>
|
||||
<Show when={props.profile}>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex w-full justify-center">
|
||||
<LabelCircle
|
||||
name={props.profile?.name}
|
||||
image_url={props.profile?.picture}
|
||||
contact
|
||||
label={false}
|
||||
size="large"
|
||||
/>
|
||||
</div>
|
||||
<h2 class="text-center text-lg font-semibold">
|
||||
{props.profile?.name}
|
||||
</h2>
|
||||
</div>
|
||||
<p class="break-all text-center font-system-mono text-base ">
|
||||
{lud16()}
|
||||
</p>
|
||||
</Show>
|
||||
<div class="w-[10rem] self-center rounded bg-white p-[1rem]">
|
||||
<QRCodeSVG
|
||||
value={"lightning:" + props.lud16 || ""}
|
||||
value={"lightning:" + lud16() || ""}
|
||||
class="h-full max-h-[256px] w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -243,6 +243,8 @@ function MessageList(props: {
|
||||
<UnifiedActivityItem
|
||||
item={combined.content as IActivityItem}
|
||||
onClick={openDetailsModal}
|
||||
// This isn't applicable here
|
||||
onNewContactClick={() => {}}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
@@ -17,6 +17,13 @@ import { useI18n } from "~/i18n/context";
|
||||
import { useMegaStore } from "~/state/megaStore";
|
||||
import { DEFAULT_NOSTR_NAME } from "~/utils";
|
||||
|
||||
export type UserProfile = {
|
||||
name: string;
|
||||
picture?: string;
|
||||
lud16?: string;
|
||||
deleted: boolean | string;
|
||||
};
|
||||
|
||||
export function Profile() {
|
||||
const [state, _actions] = useMegaStore();
|
||||
const i18n = useI18n();
|
||||
@@ -26,12 +33,13 @@ export function Profile() {
|
||||
const profile = createMemo(() => {
|
||||
const profile = state.mutiny_wallet?.get_nostr_profile();
|
||||
|
||||
return {
|
||||
const userProfile: UserProfile = {
|
||||
name: profile?.display_name || profile?.name || DEFAULT_NOSTR_NAME,
|
||||
picture: profile?.picture || undefined,
|
||||
lud16: profile?.lud16 || undefined,
|
||||
deleted: profile?.deleted || false
|
||||
};
|
||||
return userProfile;
|
||||
});
|
||||
|
||||
const profileDeleted = createMemo(() => {
|
||||
@@ -44,9 +52,9 @@ export function Profile() {
|
||||
if (!hermes) {
|
||||
return false;
|
||||
}
|
||||
const hermesDoman = new URL(hermes).hostname;
|
||||
const afterAt = profile().lud16.split("@")[1];
|
||||
if (afterAt && afterAt.includes(hermesDoman)) {
|
||||
const hermesDomain = new URL(hermes).hostname;
|
||||
const afterAt = profile().lud16!.split("@")[1];
|
||||
if (afterAt && afterAt.includes(hermesDomain)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -69,7 +77,7 @@ export function Profile() {
|
||||
<Show when={profile().name}>{profile().name}</Show>
|
||||
</h1>
|
||||
|
||||
<LightningAddressShower lud16={profile().lud16} />
|
||||
<LightningAddressShower profile={profile()} />
|
||||
</div>
|
||||
<ButtonCard onClick={() => navigate("/editprofile")}>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -79,7 +87,11 @@ export function Profile() {
|
||||
</div>
|
||||
</ButtonCard>
|
||||
<Show
|
||||
when={state.federations?.length && !hasMutinyAddress()}
|
||||
when={
|
||||
state.federations?.length &&
|
||||
!hasMutinyAddress() &&
|
||||
import.meta.env.VITE_HERMES
|
||||
}
|
||||
>
|
||||
<ButtonCard
|
||||
onClick={() =>
|
||||
|
||||
@@ -52,7 +52,11 @@ function HermesForm(props: { onSubmit: (name: string) => void }) {
|
||||
}
|
||||
});
|
||||
|
||||
const network = state.mutiny_wallet?.get_network() || "signet";
|
||||
const hermes = import.meta.env.VITE_HERMES;
|
||||
if (!hermes) {
|
||||
throw new Error("Hermes not configured");
|
||||
}
|
||||
const hermesDomain = new URL(hermes).hostname;
|
||||
|
||||
const handleSubmit: SubmitHandler<HermesForm> = async (f: HermesForm) => {
|
||||
setSuccess("");
|
||||
@@ -67,13 +71,7 @@ function HermesForm(props: { onSubmit: (name: string) => void }) {
|
||||
await state.mutiny_wallet?.reserve_lnurl_name(name);
|
||||
console.log("lnurl name reserved:", name);
|
||||
|
||||
const hermes = import.meta.env.VITE_HERMES;
|
||||
if (!hermes) {
|
||||
throw new Error("Hermes not configured");
|
||||
}
|
||||
const hermesDoman = new URL(hermes).hostname;
|
||||
|
||||
const formattedName = `${name}${hermesDoman}`;
|
||||
const formattedName = `${name}@${hermesDomain}`;
|
||||
|
||||
const existingProfile = state.mutiny_wallet?.get_nostr_profile();
|
||||
|
||||
@@ -109,12 +107,7 @@ function HermesForm(props: { onSubmit: (name: string) => void }) {
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-0 self-end pb-2 text-2xl text-m-grey-350">
|
||||
<Show
|
||||
when={network === "signet"}
|
||||
fallback={"@mutiny.plus"}
|
||||
>
|
||||
@signet.mutiny.plus
|
||||
</Show>
|
||||
@{hermesDomain}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -145,7 +138,6 @@ export function LightningAddress() {
|
||||
const [error, setError] = createSignal<Error>();
|
||||
const [settingLnAddress, setSettingLnAddress] = createSignal(false);
|
||||
|
||||
// TODO: should be able to ask mutiny-node for this
|
||||
const [lnurlName, { refetch }] = createResource(async () => {
|
||||
try {
|
||||
const name = await state.mutiny_wallet?.check_lnurl_name();
|
||||
@@ -155,16 +147,17 @@ export function LightningAddress() {
|
||||
}
|
||||
});
|
||||
const ios = Capacitor.getPlatform() === "ios";
|
||||
// const ios = true;
|
||||
|
||||
const network = state.mutiny_wallet?.get_network() || "signet";
|
||||
const hermes = import.meta.env.VITE_HERMES;
|
||||
if (!hermes) {
|
||||
throw new Error("Hermes not configured");
|
||||
}
|
||||
const hermesDomain = new URL(hermes).hostname;
|
||||
|
||||
const formattedLnAddress = createMemo(() => {
|
||||
const name = lnurlName();
|
||||
if (name) {
|
||||
const suffix =
|
||||
network === "signet" ? "@signet.mutiny.plus" : "@mutiny.plus";
|
||||
return `${lnurlName()}${suffix}`;
|
||||
return `${lnurlName()}@${hermesDomain}`;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -185,10 +185,9 @@ export function Plus() {
|
||||
<Switch>
|
||||
<Match when={state.mutiny_plus}>
|
||||
<img src={party} class="mx-auto w-1/2" />
|
||||
{/* <NiceP>{i18n.t("settings.plus.thanks")}</NiceP> */}
|
||||
<NiceP>You're part of the Mutiny!</NiceP>
|
||||
<NiceP>{i18n.t("settings.plus.thanks")}</NiceP>
|
||||
|
||||
{/* <Perks alreadySubbed /> */}
|
||||
<Perks alreadySubbed />
|
||||
<NiceP>
|
||||
{i18n.t("settings.plus.renewal_time")}{" "}
|
||||
<strong class="text-white">
|
||||
@@ -204,7 +203,12 @@ export function Plus() {
|
||||
{i18n.t("settings.plus.wallet_connection")}
|
||||
</A>
|
||||
</NiceP>
|
||||
<Show when={state.federations?.length}>
|
||||
<Show
|
||||
when={
|
||||
state.federations?.length &&
|
||||
import.meta.env.VITE_HERMES
|
||||
}
|
||||
>
|
||||
<ButtonCard
|
||||
onClick={() =>
|
||||
navigate("/settings/lightningaddress")
|
||||
|
||||
Reference in New Issue
Block a user