From ecd64a6a4c232830db8cc4a8de4bd69689a9dc15 Mon Sep 17 00:00:00 2001 From: Mothana Date: Fri, 4 Oct 2024 19:24:57 +0400 Subject: [PATCH] edit source modal and prompt --- src/App.tsx | 2 + src/Components/Background.tsx | 14 +- .../BackgroundJobs/NewSourceCheck.tsx | 3 +- src/Components/Dropdowns/LVDropdown/index.tsx | 7 +- .../LVDropdown/styles/index.module.scss | 1 - .../Modals/EditSourceModal/index.tsx | 270 ++++++++++++++++++ .../EditSourceModal/styles/index.module.scss | 208 ++++++++++++++ .../Modals/PromptForActionModal/index.tsx | 57 ++++ .../styles/index.module.scss | 56 ++++ src/Pages/NodeUp/index.tsx | 5 +- src/Pages/Sources/index.tsx | 177 ++---------- src/State/Slices/modalsSlice.ts | 19 +- src/State/Slices/paySourcesSlice.ts | 24 +- src/State/Slices/spendSourcesSlice.ts | 20 +- src/State/bridgeMiddleware.ts | 2 +- src/globalTypes.ts | 12 +- src/styles/_buttons.module.scss | 2 + 17 files changed, 705 insertions(+), 174 deletions(-) create mode 100644 src/Components/Modals/EditSourceModal/index.tsx create mode 100644 src/Components/Modals/EditSourceModal/styles/index.module.scss create mode 100644 src/Components/Modals/PromptForActionModal/index.tsx create mode 100644 src/Components/Modals/PromptForActionModal/styles/index.module.scss diff --git a/src/App.tsx b/src/App.tsx index 3240ec5d..0d63ca70 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -51,6 +51,7 @@ import "@ionic/react/css/display.css"; import LoadingOverlay from "./Components/LoadingOverlay"; import { DebitRequestModal, EditDebitModal } from "./Components/Modals/DebitRequestModal"; +import { EditSourceModal } from "./Components/Modals/EditSourceModal"; setupIonicReact(); @@ -86,6 +87,7 @@ const AppContent: React.FC = () => { {/* Modals */} + {/* Modals */} diff --git a/src/Components/Background.tsx b/src/Components/Background.tsx index 94d5ac1f..52b6e035 100644 --- a/src/Components/Background.tsx +++ b/src/Components/Background.tsx @@ -141,10 +141,21 @@ export const Background = () => { ...source, balance: `${res.balance}`, maxWithdrawable: `${res.max_withdrawable}`, - ndebit: res.ndebit + ndebit: res.ndebit, }, meta: { skipChangelog: true } }) + const paySourceToEdit = paySource.sources[source.id] + if (paySourceToEdit && res.bridge_url && !paySourceToEdit.bridgeUrl) { + dispatch({ + type: "paySources/editPaySources", + payload: { + ...paySourceToEdit, + bridgeUrl: res.bridge_url + }, + meta: { skipChangelog: true } + }) + } } const fetchSourceHistory = useCallback(async (source: SpendFrom, client: NostrClient, sourceId: string, newCurosor?: Partial, newData?: Types.UserOperation[]) => { const req = populateCursorRequest(newCurosor || cursor, !!newData) @@ -216,6 +227,7 @@ export const Background = () => { } nostrSpends.forEach(async s => { const { pubkey, relays } = parseNprofile(s.pasteField) + console.log({relays}) const client = await getNostrClient({ pubkey, relays }, s.keys) await fetchSourceHistory(s, client, s.id) }) diff --git a/src/Components/BackgroundJobs/NewSourceCheck.tsx b/src/Components/BackgroundJobs/NewSourceCheck.tsx index febace0f..89357870 100644 --- a/src/Components/BackgroundJobs/NewSourceCheck.tsx +++ b/src/Components/BackgroundJobs/NewSourceCheck.tsx @@ -3,6 +3,7 @@ import { useDispatch, useSelector } from "../../State/store" import { NOSTR_PUB_DESTINATION, NOSTR_RELAYS, OLD_NOSTR_PUB_DESTINATION, options } from "../../constants" import { addSpendSources } from "../../State/Slices/spendSourcesSlice" import { nip19 } from "nostr-tools"; +import { SourceTrustLevel } from "../../globalTypes"; export const NewSourceCheck = () => { const spendSource = useSelector(({ spendSource }) => spendSource) @@ -28,7 +29,7 @@ export const NewSourceCheck = () => { id: NOSTR_PUB_DESTINATION, label: "Bootstrap Node", pasteField: newProfile, - option: options.little, + option: SourceTrustLevel.LOW, icon: "0", balance: "0", pubSource: true diff --git a/src/Components/Dropdowns/LVDropdown/index.tsx b/src/Components/Dropdowns/LVDropdown/index.tsx index 28344765..fe4a2b74 100644 --- a/src/Components/Dropdowns/LVDropdown/index.tsx +++ b/src/Components/Dropdowns/LVDropdown/index.tsx @@ -6,14 +6,17 @@ import { Period } from "../../../Pages/Metrics"; import useClickOutside from "../../../Hooks/useClickOutside"; import { Interval } from "../../../Pages/Automation"; import { WalletIntervalEnum } from "../../Modals/DebitRequestModal/helpers"; +import { SourceTrustLevel } from "../../../globalTypes"; + interface Props { setState: (data: T) => void; otherOptions: T[]; jsx: React.ReactNode; + className?: string } -const Dropdown = ({ setState, jsx, otherOptions }: Props) => { +const Dropdown = ({ setState, jsx, otherOptions, className }: Props) => { const [expand, setExpand] = useState(false); const dropDownRef = useRef(null); useClickOutside([dropDownRef], () => setExpand(false), false); @@ -35,7 +38,7 @@ const Dropdown = { + try { + new URL(url); + return true; + } catch { + return false; + } +} + + + + + + +const trustLevelsArray = Object.values(SourceTrustLevel); + +const substringNpub = (npub: string) => { + return `${npub.substring(0, 15)}...${npub.substring(npub.length - 15, npub.length)}`; +} + + + +export const EditSourceModal = () => { + const dispatch = useDispatch() + const paySources = useSelector(state => state.paySource) + const spendSources = useSelector(state => state.spendSource) + const sourceToEdit = useSelector(state => state.modalsSlice.sourceToEdit); + const [editValues, setEditValues] = useState({ + relay: "", + nameService: "", + trustLevel: SourceTrustLevel.MEDIUM + }) + + const [initialRelay, setInitialRelay] = useState("") + const [initialNameService, setInitialNameService] = useState("") + + const [isPromptConfirmDelete, setIsPromptConfirmDelete] = useState(false); + + + + useEffect(() => { + if (!sourceToEdit) return; + setIsPromptConfirmDelete(false) + + let relay = "" + let nameService = "" + if (sourceToEdit.source.pubSource) { + const { type, data } = nip19.decode(sourceToEdit.source.pasteField); + if (type === "nprofile" && data.relays) { + relay = data.relays[0] + setInitialRelay(relay) + } + } + if (sourceToEdit.type === "payTo") { + nameService = sourceToEdit.source.bridgeUrl || "" + setInitialNameService(nameService) + } + setEditValues(state => ({ ...state, nameService, relay })); + + }, [sourceToEdit]) + + + + + + + const handleSave = useCallback(() => { + if (!sourceToEdit) return; + + const shouldUpdate = (editValues.relay !== initialRelay) || (editValues.nameService !== initialNameService) || (editValues.trustLevel !== sourceToEdit.source.option) + + let newPasteField = sourceToEdit.source.pasteField + + if (editValues.relay !== initialRelay) { + if (!isValidUrl(editValues.relay)) { + toast.error(); + return; + } + + newPasteField = nip19.nprofileEncode({ pubkey: sourceToEdit.source.id.split("-")[0], relays: [editValues.relay] }) + } + + if (editValues.nameService !== initialNameService) { + if (!isValidUrl) { + toast.error(); + return; + } + } + + if (shouldUpdate) { + if (sourceToEdit.type === "payTo") { + dispatch(editPaySources({ + ...sourceToEdit.source, + pasteField: newPasteField, + option: editValues.trustLevel, + bridgeUrl: editValues.nameService + })) + const counterpartSource = spendSources.sources[sourceToEdit.source.id]; + if (counterpartSource) { + dispatch(editSpendSources({ + ...counterpartSource, + option: editValues.trustLevel, + pasteField: newPasteField, + })) + } + } else { + dispatch(editSpendSources({ + ...sourceToEdit.source, + pasteField: newPasteField, + option: editValues.trustLevel, + })) + + const counterpartSource = paySources.sources[sourceToEdit.source.id]; + if (counterpartSource) { + dispatch(editPaySources({ + ...counterpartSource, + option: editValues.trustLevel, + pasteField: newPasteField, + })) + } + } + + toast.success("Source Properties saved successfuly") + } + dispatch(setSourceToEdit(null)) + }, [sourceToEdit, editValues, initialNameService, initialRelay, dispatch, spendSources, paySources]); + + const handleSourceDelete = useCallback(() => { + if (!sourceToEdit) return; + dispatch(deleteSpendSources(sourceToEdit.source.id)) + dispatch(deletePaySources(sourceToEdit.source.id)) + dispatch(setSourceToEdit(null)) + toast.success() + }, [dispatch, sourceToEdit]) + + + + const modalContent = sourceToEdit ? ( +
+
setIsPromptConfirmDelete(true)}> + +
+
Source Properties
+
+
+ { + arrangeIcon(sourceToEdit.source.icon) + } + {sourceToEdit.source.label} + + setState={(option) => setEditValues(state => ({ ...state, trustLevel: option }))} + jsx={ +
{editValues.trustLevel} ▼
+ } + otherOptions={trustLevelsArray} + className={styles["dropdown-options"]} + /> + +
+
+ { + sourceToEdit.source.pubSource + && + <> +
+ + Source Key: + + + {substringNpub(nip19.npubEncode(sourceToEdit.source.id.split("-")[0]))} + +
+
+ + Local Key: + + + {substringNpub(nip19.npubEncode(sourceToEdit.source.id.split("-")[1]))} + +
+
+ + Relay: + + + setEditValues(state => ({ ...state, relay: e.target.value }))} /> + +
+ { + sourceToEdit.type === "payTo" + && +
+ + Name Service: + + + setEditValues(state => ({ ...state, nameService: e.target.value }))} /> + +
+ } + { + (sourceToEdit.type === "payTo" && sourceToEdit.source.vanityName) + && + {sourceToEdit.source.vanityName.split("@")[0]} + } + + + + } +
+
+ + +
+
+ +
+ ) : <>; + + + + return ( + <> + dispatch(setSourceToEdit(null))} modalContent={modalContent} headerText={''} /> + { + isPromptConfirmDelete + && + setIsPromptConfirmDelete(false)} + action={handleSourceDelete} + actionText="Delete" + /> + + } + + ) +} + diff --git a/src/Components/Modals/EditSourceModal/styles/index.module.scss b/src/Components/Modals/EditSourceModal/styles/index.module.scss new file mode 100644 index 00000000..e09be128 --- /dev/null +++ b/src/Components/Modals/EditSourceModal/styles/index.module.scss @@ -0,0 +1,208 @@ +@import "/src/styles/modals.module"; +@import "/src/styles/buttons.module"; +@import "/src/styles/inputs.module"; + + +.container { + @include modal-wrapper; + .requestor-container { + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 3px; + margin-bottom: 15px; + + .source-label { + font-size: 20px; + color: #999999; + } + + .dropdown-box { + @include input; + font-size: 16px; + width: 10rem; + color: white; + text-align: center; + padding: 5px 10px; + margin-top: 10px; + color: #999999; + } + .dropdown-options { + background-color: #494848; + width: 10rem; + padding: 4px 0; + border-radius: 0 0 7px 7px; + + & >* { + font-size: 16px; + padding: 5px 10px; + width: 95%; + margin: auto; + border-radius: 7px; + text-align: left; + + + } + } + + + } + + .source-items-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 60%; + margin: auto; + @media (max-width: 768px) { + width: 100%; + } + + .item-line { + + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + margin: 4px 0; + .item-label { + font-size: 14px; + font-weight: 500; + } + .item-value { + &.npub { + color: #29abe2; + text-decoration: underline; + font-size: 12px; + } + &.input { + input { + @include input; + font-size: 12px; + padding: 4px 7px; + } + } + } + } + } + .debit-info { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + margin-bottom: 15px; + + .sats-amount { + width: 140px; + font-size: 36px; + font-weight: bolder; + } + + .input { + @include input; + font-size: 36px; + color: white; + text-align: center; + } + + .sats-amount-input { + + width: 180px; + font-weight: bolder; + margin: 4px 0; + } + + + + .orange-text { + color: #ff7700; + font-size: 16px; + } + + .fiat { + color: #999999; + font-size: 12px; + + } + + + .rule-options { + display: flex; + align-items: center; + justify-content: center; + gap: 7px; + margin: 7px 0; + + &.disabled { + opacity: 0.5; + pointer-events: none; + } + + span { + font-size: 20px; + } + + .num-intervals-input { + @include input; + width: 30px; + height: 30px; + + font-size: 15px; + text-align: center; + } + + .dropdown-box { + @include input; + display: flex; + text-align: center; + justify-content: center; + align-items: center; + width: 8rem; + height: 30px; + } + } + + .adjust-checkbox-container { + display: flex; + justify-content: center; + align-items: flex-start; + padding-inline: 15px; + margin: 12px 0; + + p { + font-size: 11px; + text-align: left; + margin-left: 7px; + } + } + + + } + .buttons-container { + display: flex; + justify-content: center; + gap: 5%; + margin: auto; + padding-top: 20px; + button { + @include rainbow-button; + } + } + .checkbox-container { + display: flex; + align-items: center; + justify-content: center; + + span { + font-size: 20px; + margin-left: 5px; + font-weight: 500; + } + } + .faded-text { + color: #8c8c8c; + } +} \ No newline at end of file diff --git a/src/Components/Modals/PromptForActionModal/index.tsx b/src/Components/Modals/PromptForActionModal/index.tsx new file mode 100644 index 00000000..69195ffc --- /dev/null +++ b/src/Components/Modals/PromptForActionModal/index.tsx @@ -0,0 +1,57 @@ +import classNames from "classnames"; +import styles from "./styles/index.module.scss"; +import { ReactNode } from "react"; +import { Modal } from "../Modal"; + +export enum ActionType { + DANGER = "danger", + NORMAL = "normal" +} +interface Props { + descriptionText?: string; + action: () => void; + title: string; + actionType: ActionType; + closeModal: () => void; + actionText: string; + jsx?: ReactNode; + blur?: boolean; +} +export default function PromptForActionModal({ descriptionText, action, title, actionType, closeModal, actionText, blur, jsx }: Props) { + + const handleConfirm = () => { + action(); + closeModal(); + } + + const modalContent = ( +
+
+ {title} +
+
+
+ {descriptionText} + {jsx || null} +
+
+
closeModal()}> + Close +
+
handleConfirm()} + > + {actionText} +
+
+
+
+ ) + + return +} \ No newline at end of file diff --git a/src/Components/Modals/PromptForActionModal/styles/index.module.scss b/src/Components/Modals/PromptForActionModal/styles/index.module.scss new file mode 100644 index 00000000..b0701471 --- /dev/null +++ b/src/Components/Modals/PromptForActionModal/styles/index.module.scss @@ -0,0 +1,56 @@ +@import "/src/styles/modals.module"; +@import "/src/styles/buttons.module"; +.container { + @include modal-wrapper; + .content-container { + padding: 12px 20px; + display: flex; + flex-direction: column; + gap: 12px; + .description { + + font-weight: 500; + font-size: 16px; + text-align: center; + } + .action-buttons { + display: flex; + align-items: center; + justify-content: space-around; + gap: 20px; + + .button { + @include normal-button; + border-radius: 8px; + &.danger { + background-color: #f04437; + border: none; + color: white; + font-weight: 600; + } + &.normal { + background-color: var(--primary); + color: var(--white); + } + } + } + } +} +.modal-wrapper { + + &.blur { + backdrop-filter: blur(20px); + } + .modal-container { + flex-direction: column; + max-width: 400px; + .header { + justify-content: center; + font-weight: 700; + font-size: 18px; + padding: 18px 0; + + } + + } +} \ No newline at end of file diff --git a/src/Pages/NodeUp/index.tsx b/src/Pages/NodeUp/index.tsx index e741ca52..317f280b 100644 --- a/src/Pages/NodeUp/index.tsx +++ b/src/Pages/NodeUp/index.tsx @@ -6,6 +6,7 @@ import { addPaySources } from "../../State/Slices/paySourcesSlice"; import { addSpendSources } from "../../State/Slices/spendSourcesSlice"; import { generateNewKeyPair } from "../../Api/helpers"; import { nip19 } from "nostr-tools"; +import { SourceTrustLevel } from "../../globalTypes"; export const NodeUp = () => { const router = useIonRouter(); @@ -61,7 +62,7 @@ export const NodeUp = () => { id: id, label: "Bootstrap Node", pasteField: nprofile, - option: options.little, + option: SourceTrustLevel.LOW, icon: "0", pubSource: true, keys: keyPair @@ -72,7 +73,7 @@ export const NodeUp = () => { id: id, label: "Bootstrap Node", pasteField: nprofile, - option: options.little, + option: SourceTrustLevel.LOW, icon: "0", balance: bootstrapBalance, pubSource: true, diff --git a/src/Pages/Sources/index.tsx b/src/Pages/Sources/index.tsx index cd2d5d81..a8788cd4 100644 --- a/src/Pages/Sources/index.tsx +++ b/src/Pages/Sources/index.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { PayTo, SpendFrom } from "../../globalTypes"; +import { PayTo, SourceTrustLevel, SpendFrom } from "../../globalTypes"; //It import modal component import { UseModal } from "../../Hooks/UseModal"; @@ -10,16 +10,15 @@ import { questionMark } from '../../Assets/SvgIconLibrary'; import { isAxiosError } from 'axios'; import { useSelector, useDispatch } from '../../State/store'; //import reducer -import { addPaySources, editPaySources, deletePaySources, setPaySources } from '../../State/Slices/paySourcesSlice'; -import { addSpendSources, editSpendSources, deleteSpendSources, setSpendSources } from '../../State/Slices/spendSourcesSlice'; +import { addPaySources, setPaySources } from '../../State/Slices/paySourcesSlice'; +import { addSpendSources, editSpendSources, setSpendSources } from '../../State/Slices/spendSourcesSlice'; import { Modal } from '../../Components/Modals/Modal'; -import { Destination, InputClassification, NOSTR_RELAYS, options, parseBitcoinInput, decodeNprofile } from '../../constants'; +import { Destination, InputClassification, NOSTR_RELAYS, parseBitcoinInput, decodeNprofile } from '../../constants'; import BootstrapSource from "../../Assets/Images/bootstrap_source.jpg"; import Sortable from 'sortablejs'; import { useIonRouter } from '@ionic/react'; import { createLnurlInvoice, createNostrInvoice, generateNewKeyPair, handlePayInvoice } from '../../Api/helpers'; import { toggleLoading } from '../../State/Slices/loadingOverlay'; -import { removeNotify } from '../../State/Slices/notificationSlice'; import { useLocation } from 'react-router'; import { toast } from "react-toastify"; @@ -28,6 +27,7 @@ import { truncateString } from '../../Hooks/truncateString'; import { getNostrClient } from '../../Api'; import { fetchBeacon } from '../../helpers/remoteBackups'; import { nip19 } from 'nostr-tools'; +import { setSourceToEdit } from '../../State/Slices/modalsSlice'; const arrayMove = (arr: string[], oldIndex: number, newIndex: number) => { const newArr = arr.map(e => e); @@ -44,7 +44,6 @@ export const Sources = () => { const [tempParsedWithdraw, setTempParsedWithdraw] = useState(); const [notifySourceId, setNotifySourceId] = useState(""); - const notifications = useSelector(state => state.notify.notifications); const [integrationData, setIntegrationData] = useState({ token: "", lnAddress: "" @@ -118,13 +117,10 @@ export const Sources = () => { const spendSources = useSelector((state) => state.spendSource); const [sourcePasteField, setSourcePasteField] = useState(""); - const [sourceLabel, setSourceLabel] = useState(""); - const [optional, setOptional] = useState(options.little); const [modalContent, setModalContent] = useState(); //This is the state variables what can be used to save sorce id temporarily when edit Source item - const [editSourceId, setEditSourceId] = useState(""); const [processingSource, setProcessingSource] = useState(false); const { isShown, toggle } = UseModal(); @@ -141,24 +137,14 @@ export const Sources = () => { const openEditSourcePay = (key: string) => { const source = paySources.sources[key] - if (source) { - setEditSourceId(key); - setOptional(source.option || ''); - setSourceLabel(source.label || ''); - setModalContent("editSourcepay"); - toggle(); - } + dispatch(setSourceToEdit({ source: source, type: "payTo" })) + }; const EditSourceSpend_Modal = (key: string) => { const source = spendSources.sources[key]; - if (source) { - setEditSourceId(key); - setOptional(source.option || ''); - setSourceLabel(source.label || ''); - setModalContent("editSourcespend"); - toggle(); - } + dispatch(setSourceToEdit({ source: source, type: "spendFrom" })) + }; const Notify_Modal = () => { @@ -180,13 +166,6 @@ export const Sources = () => { return sweepLnurlModal; case 'addSource': return contentAddContent - - case 'editSourcepay': - return contentEditContent - - case 'editSourcespend': - return contentEditContent - case 'notify': return notifyContent case "sourceNotify": @@ -194,10 +173,6 @@ export const Sources = () => { case "acceptInvite": return acceptInviteContent - - case "deleteSource": - return deleteSource - default: return notifyContent } @@ -211,7 +186,7 @@ export const Sources = () => { return; } - if (!sourcePasteField || !optional) { + if (!sourcePasteField) { toast.error() return; } @@ -302,7 +277,7 @@ export const Sources = () => { const addedPaySource = { id: id, - option: optional, + option: SourceTrustLevel.LOW, icon: sndleveldomain, label: resultLnurl.hostname, pasteField: inputSource, @@ -314,7 +289,7 @@ export const Sources = () => { const addedSpendSource = { id: id, label: resultLnurl.hostname, - option: optional, + option: SourceTrustLevel.LOW, icon: sndleveldomain, balance: "0", pasteField: inputSource, @@ -344,7 +319,7 @@ export const Sources = () => { if (parsed.lnurlType === "payRequest") { const addedSource = { id: parsed.data, - option: optional, + option: SourceTrustLevel.LOW, icon: parsed.domainName, label: parsed.hostName, pasteField: parsed.data, @@ -355,7 +330,7 @@ export const Sources = () => { const addedSource = { id: parsed.data, label: parsed.hostName, - option: optional, + option: SourceTrustLevel.LOW, icon: parsed.domainName, balance: parsed.max.toString(), pasteField: parsed.data, @@ -365,7 +340,7 @@ export const Sources = () => { } else if (parsed.type === InputClassification.LN_ADDRESS) { const addedSource = { id: parsed.data, - option: optional, + option: SourceTrustLevel.LOW, icon: parsed.domainName, label: parsed.data, pasteField: parsed.data, @@ -383,62 +358,18 @@ export const Sources = () => { dispatch(toggleLoading({ loadingMessage: "" })) setProcessingSource(false); - }, [sourcePasteField, dispatch, optional, inviteToken, paySources, spendSources, toggle, processingSource, integrationData]); + }, [sourcePasteField, dispatch, inviteToken, paySources, spendSources, toggle, processingSource, integrationData]); + + - const editPaySource = () => { - if (!sourceLabel || !optional) { - toast.error() - return; - } - const paySourceToEdit: PayTo = { - ...paySources.sources[editSourceId], - option: optional, - label: sourceLabel, - }; - dispatch(editPaySources(paySourceToEdit)) - resetValue(); - toggle(); - }; - const editSpendSource = () => { - if (!sourceLabel || !optional) { - toast.error() - return - } - const spendSourceToEdit: SpendFrom = { - ...spendSources.sources[editSourceId], - option: optional, - label: sourceLabel - }; - dispatch(editSpendSources(spendSourceToEdit)) - resetValue(); - toggle(); - }; - const deletePaySource = () => { - setEditSourceId(""); - dispatch(deletePaySources(editSourceId)); - resetValue(); - toggle(); - }; - const deleteSpendSource = () => { - setEditSourceId(""); - const associatedNotification = notifications.find(n => n.link === `/sources?sourceId=${editSourceId}`); - if (associatedNotification) { - dispatch(removeNotify(associatedNotification.date)); - } - dispatch(deleteSpendSources(editSourceId)); - resetValue(); - toggle(); - }; const resetValue = () => { - setOptional(options.little); setSourcePasteField(""); - setSourceLabel(""); } const arrangeIcon = (value?: string, sourcePub?: string) => { @@ -513,27 +444,7 @@ export const Sources = () => { const contentAddContent =
Add Source
-
How well do you trust this node?
-
-
setOptional(options.little)}> -
-

🔓

- A little. -
-
-
setOptional(options.very)}> -
-

🫡

- Very well. -
-
-
setOptional(options.mine)}> -
-

🏠

- It's my node. -
-
-
+
{
- {(Object.values(paySources.sources).filter((e) => e.icon != "0").length == 0 && Object.values(spendSources.sources).filter((e) => e.icon != "0").length == 0) ? (
-

or

- -
) : null} - ; + ; - const contentEditContent = -
x
-
Edit Source
-
How well do you trust this node?
-
-
setOptional(options.little)}> -
🔓
-
A little.
-
-
setOptional(options.very)}> -
🫡
-
Very well.
-
-
setOptional(options.mine)}> -
🏠
-
It's my node.
-
-
-
- setSourceLabel(e.target.value)} - /> -
-
- - -
- -
; const notifyContent =
@@ -642,20 +519,6 @@ export const Sources = () => {
- const deleteSource = ( - -
- Are you sure you want to delete this source? -
-
- - -
-
- ); useEffect(() => { diff --git a/src/State/Slices/modalsSlice.ts b/src/State/Slices/modalsSlice.ts index ec901cb1..e1e13e87 100644 --- a/src/State/Slices/modalsSlice.ts +++ b/src/State/Slices/modalsSlice.ts @@ -1,12 +1,22 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import { DebitAuthorization, LiveDebitRequest } from '../../Api/pub/autogenerated/ts/types'; +import { PayTo, SpendFrom } from '../../globalTypes'; type ModalRequest = { request: LiveDebitRequest, sourceId: string } export type EditDebit = DebitAuthorization & { sourceId: string, domainName?: string, avatarUrl?: string } + +type SourceToEdit = { + type: "payTo"; + source: PayTo; +} | { + type: "spendFrom"; + source: SpendFrom; +} | null interface State { debitRequests: ModalRequest[] editDebit: EditDebit | null; debitsRefetchHook: number + sourceToEdit: SourceToEdit } @@ -16,7 +26,7 @@ interface State { const modalsSlice = createSlice({ name: "modals", - initialState: { debitRequests: [], editDebit: null, debitsRefetchHook: 0 } as State, + initialState: { debitRequests: [], editDebit: null, debitsRefetchHook: 0, sourceToEdit: null } as State, reducers: { refetchDebits: (state) => { state.debitsRefetchHook = Math.random(); @@ -32,10 +42,15 @@ const modalsSlice = createSlice({ // edit debit modal setDebitToEdit: (state, action: PayloadAction) => { state.editDebit = action.payload; + }, + + // edit source modal + setSourceToEdit: (state, action: PayloadAction) => { + state.sourceToEdit = action.payload; } }, }); -export const { addDebitRequest, removeDebitRequest, setDebitToEdit, refetchDebits } = modalsSlice.actions; +export const { addDebitRequest, removeDebitRequest, setDebitToEdit, refetchDebits, setSourceToEdit } = modalsSlice.actions; export default modalsSlice.reducer; diff --git a/src/State/Slices/paySourcesSlice.ts b/src/State/Slices/paySourcesSlice.ts index 62735119..7e527fca 100644 --- a/src/State/Slices/paySourcesSlice.ts +++ b/src/State/Slices/paySourcesSlice.ts @@ -1,8 +1,8 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit'; -import { PayTo } from '../../globalTypes'; +import { PayTo, SourceTrustLevel } from '../../globalTypes'; import { getDiffAsActionDispatch, mergeArrayValues } from './dataMerge'; import loadInitialState, { MigrationFunction, getStateAndVersion, applyMigrations } from './migrations'; -import { decodeNprofile, OLD_NOSTR_PUB_DESTINATION } from '../../constants'; +import { decodeNprofile } from '../../constants'; import { syncRedux } from '../store'; import { getNostrPrivateKey } from '../../Api/nostr'; import { getPublicKey } from 'nostr-tools'; @@ -21,7 +21,7 @@ export interface PaySourceState { export const storageKey = "payTo" -export const VERSION = 4; +export const VERSION = 5; export const migrations: Record> = { // the bridge url encoded in nprofile migration 1: (data) => { @@ -121,6 +121,24 @@ export const migrations: Record> = { state.sources = newSourcesObject; return state + }, + 5: (state) => { + // option field more strict + console.log({state}) + state.order.forEach((id: any) => { + const source = state.sources[id]; + if (!source.option) { + state.sources[id] = { ...source, option: SourceTrustLevel.MEDIUM } + } else if (source.option === "A little.") { + state.sources[id] = { ...source, option: SourceTrustLevel.LOW } + } else if (source.option === "Very well.") { + state.sources[id] = { ...source, option: SourceTrustLevel.MEDIUM } + } else { + state.sources[id] = { ...source, option: SourceTrustLevel.HIGH } + } + + }) + return state } }; diff --git a/src/State/Slices/spendSourcesSlice.ts b/src/State/Slices/spendSourcesSlice.ts index 38395ca7..1aca5363 100644 --- a/src/State/Slices/spendSourcesSlice.ts +++ b/src/State/Slices/spendSourcesSlice.ts @@ -1,5 +1,5 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit'; -import { SpendFrom } from '../../globalTypes'; +import { SourceTrustLevel, SpendFrom } from '../../globalTypes'; import { getDiffAsActionDispatch, mergeArrayValues } from './dataMerge'; import loadInitialState, { MigrationFunction, applyMigrations, getStateAndVersion } from './migrations'; import { syncRedux } from '../store'; @@ -9,7 +9,7 @@ import { BackupAction } from '../types'; import { decodeNprofile } from '../../constants'; import { Buffer } from 'buffer'; export const storageKey = "spendFrom" -export const VERSION = 3; +export const VERSION = 4; export type SpendSourceRecord = Record; @@ -91,6 +91,22 @@ export const migrations: Record> = { state.sources = newSourcesObject; return state + }, + 4: (state) => { + state.order.forEach((id: any) => { + const source = state.sources[id]; + if (!source.option) { + state.sources[id] = { ...source, option: SourceTrustLevel.MEDIUM } + } else if (source.option === "A little.") { + state.sources[id] = { ...source, option: SourceTrustLevel.LOW } + } else if (source.option === "Very well.") { + state.sources[id] = { ...source, option: SourceTrustLevel.MEDIUM } + } else { + state.sources[id] = { ...source, option: SourceTrustLevel.HIGH } + } + + }) + return state; } }; diff --git a/src/State/bridgeMiddleware.ts b/src/State/bridgeMiddleware.ts index a4a1029d..9571221f 100644 --- a/src/State/bridgeMiddleware.ts +++ b/src/State/bridgeMiddleware.ts @@ -33,7 +33,7 @@ const enrollToBridge = async (source: PayTo, dispatchCallback: (vanityname: stri k1 = lnurlPayLinkRes.k1 } - const bridgeUrl = userInfoRes.bridge_url; + const bridgeUrl = source.bridgeUrl || userInfoRes.bridge_url; if (!bridgeUrl) return; diff --git a/src/globalTypes.ts b/src/globalTypes.ts index 0c8c641f..3b6eed3a 100644 --- a/src/globalTypes.ts +++ b/src/globalTypes.ts @@ -130,7 +130,7 @@ export interface SpendFrom { pasteField: string; icon: string; balance: string; - option: string; + option: SourceTrustLevel; maxWithdrawable?: string; // Max sats payable to out of pub invoice disabled?: string // the error message disconnected?: boolean; @@ -144,12 +144,13 @@ export interface PayTo { id: string; label: string; pasteField: string; - option: string; + option: SourceTrustLevel; icon: string; disconnected?: boolean vanityName?: string; pubSource?: boolean, keys: NostrKeyPair + bridgeUrl?: string } export interface FiatCurrency { @@ -158,4 +159,11 @@ export interface FiatCurrency { symbol: string; } +export enum SourceTrustLevel { + HIGH = "My node.", + MEDIUM = "Well trusted.", + LOW = "Little trust.", +} + + export type SourceDebitRequest = { request: LiveDebitRequest, source: SpendFrom } \ No newline at end of file diff --git a/src/styles/_buttons.module.scss b/src/styles/_buttons.module.scss index 4543892a..80dcb90c 100644 --- a/src/styles/_buttons.module.scss +++ b/src/styles/_buttons.module.scss @@ -9,6 +9,7 @@ transition-duration: 400ms; display: flex; align-items: center; + cursor: pointer; > *:first-child { @@ -41,6 +42,7 @@ background-clip: padding-box; border: solid $border transparent; border-radius: 5px; + cursor: pointer; > *:first-child { margin-right: 5px;