From 6e0cb369dc381f31e9acc28042eee50bee414acb Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 11 Feb 2024 16:20:19 -0600 Subject: [PATCH 01/60] Add boilerplate NostrSignin modal --- src/features/ui/components/modal-root.tsx | 2 + .../nostr-signin-modal/nostr-signin-modal.tsx | 52 +++++++++++++++++++ .../steps/extension-step.tsx | 17 ++++++ .../steps/identity-step.tsx | 17 ++++++ .../ui/components/panels/sign-up-panel.tsx | 6 ++- src/features/ui/util/async-components.ts | 1 + 6 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx create mode 100644 src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx create mode 100644 src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx diff --git a/src/features/ui/components/modal-root.tsx b/src/features/ui/components/modal-root.tsx index 6a3f91fb9..7fec9c25c 100644 --- a/src/features/ui/components/modal-root.tsx +++ b/src/features/ui/components/modal-root.tsx @@ -30,6 +30,7 @@ import { MentionsModal, MissingDescriptionModal, MuteModal, + NostrSigninModal, ReactionsModal, ReblogsModal, ReplyMentionsModal, @@ -70,6 +71,7 @@ const MODAL_COMPONENTS: Record> = { 'MENTIONS': MentionsModal, 'MISSING_DESCRIPTION': MissingDescriptionModal, 'MUTE': MuteModal, + 'NOSTR_SIGNIN': NostrSigninModal, 'REACTIONS': ReactionsModal, 'REBLOGS': ReblogsModal, 'REPLY_MENTIONS': ReplyMentionsModal, diff --git a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx new file mode 100644 index 000000000..0d3497f51 --- /dev/null +++ b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx @@ -0,0 +1,52 @@ +import React, { useState } from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { Modal, Stack } from 'soapbox/components/ui'; + +import ExtensionStep from './steps/extension-step'; +import IdentityStep from './steps/identity-step'; + +interface INostrSigninModal { + onClose: (type?: string) => void; +} + +const NostrSigninModal: React.FC = ({ onClose }) => { + const [step, setStep] = useState(0); + + const handleClose = () => { + onClose('NOSTR_SIGNIN'); + }; + + const renderStep = () => { + switch (step) { + case 0: + return ; + case 1: + return ; + } + }; + + const renderModalTitle = () => { + switch (step) { + case 0: + return ; + case 1: + return ; + default: + return null; + } + }; + + return ( + + + {renderStep()} + + + ); +}; + +export default NostrSigninModal; diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx new file mode 100644 index 000000000..28fc8621f --- /dev/null +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +import { Stack } from 'soapbox/components/ui'; + +interface IExtensionStep { + setStep: (step: number) => void; +} + +const ExtensionStep: React.FC = () => { + return ( + + extension step + + ); +}; + +export default ExtensionStep; diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx new file mode 100644 index 000000000..f343e4ce3 --- /dev/null +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +import { Stack } from 'soapbox/components/ui'; + +interface IIdentityStep { + setStep: (step: number) => void; +} + +const IdentityStep: React.FC = () => { + return ( + + identity step + + ); +}; + +export default IdentityStep; diff --git a/src/features/ui/components/panels/sign-up-panel.tsx b/src/features/ui/components/panels/sign-up-panel.tsx index e12ae02c4..059fa9d19 100644 --- a/src/features/ui/components/panels/sign-up-panel.tsx +++ b/src/features/ui/components/panels/sign-up-panel.tsx @@ -1,13 +1,15 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import { openModal } from 'soapbox/actions/modals'; import { Button, Stack, Text } from 'soapbox/components/ui'; -import { useAppSelector, useInstance, useRegistrationStatus } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector, useInstance, useRegistrationStatus } from 'soapbox/hooks'; const SignUpPanel = () => { const instance = useInstance(); const { isOpen } = useRegistrationStatus(); const me = useAppSelector((state) => state.me); + const dispatch = useAppDispatch(); if (me || !isOpen) return null; @@ -23,7 +25,7 @@ const SignUpPanel = () => { - diff --git a/src/features/ui/util/async-components.ts b/src/features/ui/util/async-components.ts index 952f3c639..d1313e0af 100644 --- a/src/features/ui/util/async-components.ts +++ b/src/features/ui/util/async-components.ts @@ -162,3 +162,4 @@ export const EditAnnouncementModal = lazy(() => import('soapbox/features/ui/comp export const FollowedTags = lazy(() => import('soapbox/features/followed-tags')); export const AccountNotePanel = lazy(() => import('soapbox/features/ui/components/panels/account-note-panel')); export const ComposeEditor = lazy(() => import('soapbox/features/compose/editor')); +export const NostrSigninModal = lazy(() => import('soapbox/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal')); From b9c771a3695ed80625fa2cfa4aa3c91af732e5c0 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 11 Feb 2024 16:39:45 -0600 Subject: [PATCH 02/60] vite: allow setting PORT envvar --- vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vite.config.ts b/vite.config.ts index aa5320e61..a174b0644 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -27,7 +27,7 @@ export default defineConfig(({ command }) => ({ }, assetsInclude: ['**/*.oga'], server: { - port: 3036, + port: Number(process.env.PORT ?? 3036), }, optimizeDeps: { exclude: command === 'serve' ? ['@soapbox.pub/wasmboy'] : [], From e02ca0e0af5d6d9eea0851fc6bd46eaace112238 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 11 Feb 2024 16:40:08 -0600 Subject: [PATCH 03/60] Reduce circular imports in UI components --- src/components/ui/emoji-selector/emoji-selector.tsx | 4 +++- src/components/ui/widget/widget.tsx | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/ui/emoji-selector/emoji-selector.tsx b/src/components/ui/emoji-selector/emoji-selector.tsx index e27763bde..2876aa0a4 100644 --- a/src/components/ui/emoji-selector/emoji-selector.tsx +++ b/src/components/ui/emoji-selector/emoji-selector.tsx @@ -2,7 +2,9 @@ import { shift, useFloating, Placement, offset, OffsetOptions } from '@floating- import clsx from 'clsx'; import React, { useEffect, useState } from 'react'; -import { Emoji as EmojiComponent, HStack, IconButton } from 'soapbox/components/ui'; +import EmojiComponent from 'soapbox/components/ui/emoji/emoji'; +import HStack from 'soapbox/components/ui/hstack/hstack'; +import IconButton from 'soapbox/components/ui/icon-button/icon-button'; import EmojiPickerDropdown from 'soapbox/features/emoji/components/emoji-picker-dropdown'; import { useClickOutside, useFeatures, useSoapboxConfig } from 'soapbox/hooks'; diff --git a/src/components/ui/widget/widget.tsx b/src/components/ui/widget/widget.tsx index 7398b8dc0..79c001674 100644 --- a/src/components/ui/widget/widget.tsx +++ b/src/components/ui/widget/widget.tsx @@ -1,6 +1,9 @@ import React from 'react'; -import { HStack, IconButton, Stack, Text } from 'soapbox/components/ui'; +import HStack from 'soapbox/components/ui/hstack/hstack'; +import IconButton from 'soapbox/components/ui/icon-button/icon-button'; +import Stack from 'soapbox/components/ui/stack/stack'; +import Text from 'soapbox/components/ui/text/text'; interface IWidgetTitle { /** Title text for the widget. */ From a9d311028e90a7f7e15e67f64b9c7aab80b47a73 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 11 Feb 2024 17:13:29 -0600 Subject: [PATCH 04/60] ExtensionStep: make it kind of work --- .../nostr-signin-modal/nostr-signin-modal.tsx | 2 +- .../steps/extension-step.tsx | 25 ++++++++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx index 0d3497f51..f12d9230a 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx @@ -29,7 +29,7 @@ const NostrSigninModal: React.FC = ({ onClose }) => { const renderModalTitle = () => { switch (step) { case 0: - return ; + return ; case 1: return ; default: diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx index 28fc8621f..97077353c 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx @@ -1,15 +1,32 @@ import React from 'react'; +import { FormattedMessage } from 'react-intl'; -import { Stack } from 'soapbox/components/ui'; +import Button from 'soapbox/components/ui/button/button'; +import Stack from 'soapbox/components/ui/stack/stack'; +import { signer } from 'soapbox/features/nostr/sign'; interface IExtensionStep { setStep: (step: number) => void; } -const ExtensionStep: React.FC = () => { +const ExtensionStep: React.FC = ({ setStep }) => { + const onClick = () => { + signer!.getPublicKey(); + }; + + const onClickAlt = () => { + setStep(1); + }; + return ( - - extension step + + + + ); }; From 15af206a452849960a3edec7f1a03e2398a5e2f4 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 12 Feb 2024 17:57:28 -0600 Subject: [PATCH 05/60] useAppSelector: export type --- src/hooks/useAppSelector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useAppSelector.ts b/src/hooks/useAppSelector.ts index 576990079..4394afb21 100644 --- a/src/hooks/useAppSelector.ts +++ b/src/hooks/useAppSelector.ts @@ -1,5 +1,5 @@ import { TypedUseSelectorHook, useSelector } from 'react-redux'; -import { RootState } from 'soapbox/store'; +import type { RootState } from 'soapbox/store'; export const useAppSelector: TypedUseSelectorHook = useSelector; From 1c97a163d07f7c64bbb31b57d6fdae3683229536 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 12 Feb 2024 17:57:44 -0600 Subject: [PATCH 06/60] NostrSigninModal: add extension indicator, flesh out IdentityStep --- .../nostr-extension-indicator.tsx | 31 +++++++++++++++++++ .../steps/identity-step.tsx | 26 +++++++++++++--- 2 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 src/features/ui/components/modals/nostr-signin-modal/nostr-extension-indicator.tsx diff --git a/src/features/ui/components/modals/nostr-signin-modal/nostr-extension-indicator.tsx b/src/features/ui/components/modals/nostr-signin-modal/nostr-extension-indicator.tsx new file mode 100644 index 000000000..735674a9a --- /dev/null +++ b/src/features/ui/components/modals/nostr-signin-modal/nostr-extension-indicator.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; + +import Stack from 'soapbox/components/ui/stack/stack'; +import Text from 'soapbox/components/ui/text/text'; + +interface INostrExtensionIndicator { + signinAction: () => void; +} + +const NostrExtensionIndicator: React.FC = ({ signinAction }) => { + return ( + + + {window.nostr ? ( + , + }} + /> + ) : ( + + )} + + + ); +}; + +export default NostrExtensionIndicator; \ No newline at end of file diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx index f343e4ce3..db82a0cc9 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx @@ -1,15 +1,33 @@ import React from 'react'; -import { Stack } from 'soapbox/components/ui'; +import Button from 'soapbox/components/ui/button/button'; +import FormGroup from 'soapbox/components/ui/form-group/form-group'; +import HStack from 'soapbox/components/ui/hstack/hstack'; +import Input from 'soapbox/components/ui/input/input'; +import Stack from 'soapbox/components/ui/stack/stack'; + +import NostrExtensionIndicator from '../nostr-extension-indicator'; interface IIdentityStep { setStep: (step: number) => void; } -const IdentityStep: React.FC = () => { +const IdentityStep: React.FC = ({ setStep }) => { return ( - - identity step + + setStep(0)} /> + + + + + + + + + ); }; From 1618fbbb8f4adcdb57a78859f64b87efc829559c Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 12 Feb 2024 19:39:34 -0600 Subject: [PATCH 07/60] NostrSigninModal: scaffold steps --- .../nostr-signin-modal/nostr-signin-modal.tsx | 15 ++++++++++++++- .../nostr-signin-modal/steps/account-step.tsx | 16 ++++++++++++++++ .../nostr-signin-modal/steps/identity-step.tsx | 10 +++++++--- .../modals/nostr-signin-modal/steps/key-step.tsx | 16 ++++++++++++++++ .../nostr-signin-modal/steps/register-step.tsx | 16 ++++++++++++++++ 5 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx create mode 100644 src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx create mode 100644 src/features/ui/components/modals/nostr-signin-modal/steps/register-step.tsx diff --git a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx index f12d9230a..ce4ffa76e 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx @@ -3,8 +3,11 @@ import { FormattedMessage } from 'react-intl'; import { Modal, Stack } from 'soapbox/components/ui'; +import AccountStep from './steps/account-step'; import ExtensionStep from './steps/extension-step'; import IdentityStep from './steps/identity-step'; +import KeyStep from './steps/key-step'; +import RegisterStep from './steps/register-step'; interface INostrSigninModal { onClose: (type?: string) => void; @@ -13,6 +16,8 @@ interface INostrSigninModal { const NostrSigninModal: React.FC = ({ onClose }) => { const [step, setStep] = useState(0); + const [username, setUsername] = useState(''); + const handleClose = () => { onClose('NOSTR_SIGNIN'); }; @@ -22,7 +27,13 @@ const NostrSigninModal: React.FC = ({ onClose }) => { case 0: return ; case 1: - return ; + return ; + case 2: + return ; + case 3: + return ; + case 4: + return ; } }; @@ -32,6 +43,8 @@ const NostrSigninModal: React.FC = ({ onClose }) => { return ; case 1: return ; + case 2: + return ; default: return null; } diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx new file mode 100644 index 000000000..5bf641eba --- /dev/null +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import Stack from 'soapbox/components/ui/stack/stack'; + +interface IAccountStep { +} + +const AccountStep: React.FC = () => { + return ( + + account step + + ); +}; + +export default AccountStep; diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx index db82a0cc9..be4fdd04b 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx @@ -9,18 +9,22 @@ import Stack from 'soapbox/components/ui/stack/stack'; import NostrExtensionIndicator from '../nostr-extension-indicator'; interface IIdentityStep { - setStep: (step: number) => void; + username: string; + setUsername(username: string): void; + setStep(step: number): void; } -const IdentityStep: React.FC = ({ setStep }) => { +const IdentityStep: React.FC = ({ username, setUsername, setStep }) => { return ( - + setStep(0)} /> setUsername(e.target.value)} /> diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx new file mode 100644 index 000000000..101db28b5 --- /dev/null +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import Stack from 'soapbox/components/ui/stack/stack'; + +interface IKeyStep { +} + +const KeyStep: React.FC = () => { + return ( + + key step + + ); +}; + +export default KeyStep; diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/register-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/register-step.tsx new file mode 100644 index 000000000..af6f7020d --- /dev/null +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/register-step.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import Stack from 'soapbox/components/ui/stack/stack'; + +interface IRegisterStep { +} + +const RegisterStep: React.FC = () => { + return ( + + register step + + ); +}; + +export default RegisterStep; From b950a7a0523387dfba6a88328982dda5c2e59c78 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 13 Feb 2024 17:01:07 -0600 Subject: [PATCH 08/60] Rework Nostr modal actions --- src/actions/nostr.ts | 13 +++++++------ .../nostr-extension-indicator.tsx | 12 +++++++----- .../nostr-signin-modal/steps/extension-step.tsx | 12 +++++------- .../nostr-signin-modal/steps/identity-step.tsx | 2 +- src/features/ui/components/navbar.tsx | 6 ++---- 5 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/actions/nostr.ts b/src/actions/nostr.ts index 21044140b..ab7dfb8e0 100644 --- a/src/actions/nostr.ts +++ b/src/actions/nostr.ts @@ -1,22 +1,23 @@ import { nip19 } from 'nostr-tools'; -import { signer } from 'soapbox/features/nostr/sign'; import { type AppDispatch } from 'soapbox/store'; import { verifyCredentials } from './auth'; +import { closeModal } from './modals'; -/** Log in with a Nostr pubkey. */ -function nostrLogIn() { +/** Log in with a Nostr extension. */ +function nostrExtensionLogIn() { return async (dispatch: AppDispatch) => { - if (!signer) { + if (!window.nostr) { throw new Error('No Nostr signer available'); } - const pubkey = await signer.getPublicKey(); + const pubkey = await window.nostr.getPublicKey(); const npub = nip19.npubEncode(pubkey); + dispatch(closeModal('NOSTR_SIGNIN')); return dispatch(verifyCredentials(npub)); }; } -export { nostrLogIn }; \ No newline at end of file +export { nostrExtensionLogIn }; \ No newline at end of file diff --git a/src/features/ui/components/modals/nostr-signin-modal/nostr-extension-indicator.tsx b/src/features/ui/components/modals/nostr-signin-modal/nostr-extension-indicator.tsx index 735674a9a..4c5333b51 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/nostr-extension-indicator.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/nostr-extension-indicator.tsx @@ -1,14 +1,16 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import { nostrExtensionLogIn } from 'soapbox/actions/nostr'; import Stack from 'soapbox/components/ui/stack/stack'; import Text from 'soapbox/components/ui/text/text'; +import { useAppDispatch } from 'soapbox/hooks'; -interface INostrExtensionIndicator { - signinAction: () => void; -} +const NostrExtensionIndicator: React.FC = () => { + const dispatch = useAppDispatch(); + + const onClick = () => dispatch(nostrExtensionLogIn()); -const NostrExtensionIndicator: React.FC = ({ signinAction }) => { return ( @@ -17,7 +19,7 @@ const NostrExtensionIndicator: React.FC = ({ signinAct id='nostr_extension.found' defaultMessage='Sign in with browser extension.' values={{ - link: (node) => , + link: (node) => , }} /> ) : ( diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx index 97077353c..58c079f3e 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx @@ -1,22 +1,20 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import { nostrExtensionLogIn } from 'soapbox/actions/nostr'; import Button from 'soapbox/components/ui/button/button'; import Stack from 'soapbox/components/ui/stack/stack'; -import { signer } from 'soapbox/features/nostr/sign'; +import { useAppDispatch } from 'soapbox/hooks'; interface IExtensionStep { setStep: (step: number) => void; } const ExtensionStep: React.FC = ({ setStep }) => { - const onClick = () => { - signer!.getPublicKey(); - }; + const dispatch = useAppDispatch(); - const onClickAlt = () => { - setStep(1); - }; + const onClick = () => dispatch(nostrExtensionLogIn()); + const onClickAlt = () => setStep(1); return ( diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx index be4fdd04b..ac7c409a9 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx @@ -17,7 +17,7 @@ interface IIdentityStep { const IdentityStep: React.FC = ({ username, setUsername, setStep }) => { return ( - setStep(0)} /> + { const onOpenSidebar = () => dispatch(openSidebar()); const handleNostrLogin = async () => { - setLoading(true); - await dispatch(nostrLogIn()).catch(console.error); - setLoading(false); + dispatch(openModal('NOSTR_SIGNIN')); }; const handleSubmit: React.FormEventHandler = (event) => { From a0a37b72964b07f5f7ed8af45151e0c74d30357b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 13 Feb 2024 20:22:56 -0600 Subject: [PATCH 09/60] Make accountLookup work with Nostr --- package.json | 2 +- src/api/hooks/accounts/useAccountLookup.ts | 2 +- .../modals/nostr-signin-modal/nostr-signin-modal.tsx | 2 +- .../modals/nostr-signin-modal/steps/account-step.tsx | 8 ++++++-- .../modals/nostr-signin-modal/steps/identity-step.tsx | 2 +- src/schemas/account.ts | 4 ++++ yarn.lock | 2 +- 7 files changed, 15 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index f86f62c0a..6c6ec2d82 100644 --- a/package.json +++ b/package.json @@ -185,7 +185,7 @@ "vite-plugin-require": "^1.1.10", "vite-plugin-static-copy": "^1.0.0", "wicg-inert": "^3.1.1", - "zod": "^3.21.4" + "zod": "^3.22.4" }, "devDependencies": { "@formatjs/cli": "^6.2.0", diff --git a/src/api/hooks/accounts/useAccountLookup.ts b/src/api/hooks/accounts/useAccountLookup.ts index 7aed05f98..0e88435b6 100644 --- a/src/api/hooks/accounts/useAccountLookup.ts +++ b/src/api/hooks/accounts/useAccountLookup.ts @@ -22,7 +22,7 @@ function useAccountLookup(acct: string | undefined, opts: UseAccountLookupOpts = const { entity: account, isUnauthorized, ...result } = useEntityLookup( Entities.ACCOUNTS, - (account) => account.acct.toLowerCase() === acct?.toLowerCase(), + (account) => account.acct.toLowerCase() === acct?.toLowerCase() || account.nostr.npub === acct, () => api.get(`/api/v1/accounts/lookup?acct=${acct}`), { schema: accountSchema, enabled: !!acct }, ); diff --git a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx index ce4ffa76e..186d5169e 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx @@ -31,7 +31,7 @@ const NostrSigninModal: React.FC = ({ onClose }) => { case 2: return ; case 3: - return ; + return ; case 4: return ; } diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx index 5bf641eba..1a01cc77d 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx @@ -1,14 +1,18 @@ import React from 'react'; +import { useAccountLookup } from 'soapbox/api/hooks'; import Stack from 'soapbox/components/ui/stack/stack'; interface IAccountStep { + username: string; } -const AccountStep: React.FC = () => { +const AccountStep: React.FC = ({ username }) => { + const { account } = useAccountLookup(username); + return ( - account step + {JSON.stringify(account, null, 2)} ); }; diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx index ac7c409a9..853e57dc8 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx @@ -30,7 +30,7 @@ const IdentityStep: React.FC = ({ username, setUsername, setStep - + ); diff --git a/src/schemas/account.ts b/src/schemas/account.ts index b419b13cf..40a5e5537 100644 --- a/src/schemas/account.ts +++ b/src/schemas/account.ts @@ -1,5 +1,6 @@ import escapeTextContentForBrowser from 'escape-html'; import DOMPurify from 'isomorphic-dompurify'; +import { NSchema as n } from 'nspec'; import z from 'zod'; import emojify from 'soapbox/features/emoji'; @@ -50,6 +51,9 @@ const baseAccountSchema = z.object({ z.string(), z.null(), ]).catch(null), + nostr: coerceObject({ + npub: n.bech32().optional().catch(undefined), + }), note: contentSchema, /** Fedibird extra settings. */ other_settings: z.object({ diff --git a/yarn.lock b/yarn.lock index 6a49d2e0a..5a9ae368e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9698,7 +9698,7 @@ yocto-queue@^1.0.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== -zod@^3.21.0, zod@^3.21.4: +zod@^3.21.0: version "3.22.3" resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.3.tgz#2fbc96118b174290d94e8896371c95629e87a060" integrity sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug== From 20328961fd1c3eabc663a57ec9ff1d1f8ff8cb85 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 16 Feb 2024 17:47:43 -0600 Subject: [PATCH 10/60] Fill out KeyStep --- .../nostr-signin-modal/nostr-signin-modal.tsx | 4 +++- .../steps/identity-step.tsx | 2 +- .../nostr-signin-modal/steps/key-step.tsx | 20 ++++++++++++++++--- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx index 186d5169e..479ea53e5 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx @@ -29,7 +29,7 @@ const NostrSigninModal: React.FC = ({ onClose }) => { case 1: return ; case 2: - return ; + return ; case 3: return ; case 4: @@ -45,6 +45,8 @@ const NostrSigninModal: React.FC = ({ onClose }) => { return ; case 2: return ; + case 3: + return ; default: return null; } diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx index 853e57dc8..373b8cfcf 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx @@ -29,7 +29,7 @@ const IdentityStep: React.FC = ({ username, setUsername, setStep - + diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx index 101db28b5..530af4f4a 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx @@ -1,14 +1,28 @@ import React from 'react'; +import Button from 'soapbox/components/ui/button/button'; import Stack from 'soapbox/components/ui/stack/stack'; +import NostrExtensionIndicator from '../nostr-extension-indicator'; + interface IKeyStep { + setStep(step: number): void; } -const KeyStep: React.FC = () => { +const KeyStep: React.FC = ({ setStep }) => { return ( - - key step + + + + + + + + ); }; From 407b19321d8126365be3c99d6a97da8f2c396bb9 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 16 Feb 2024 20:04:25 -0600 Subject: [PATCH 11/60] NostrSigninModal: add EmojiGraphic, use an emoji on each step --- .../components/emoji-graphic.tsx | 20 +++++++++++++++++++ .../steps/extension-step.tsx | 4 ++++ .../steps/identity-step.tsx | 3 +++ .../nostr-signin-modal/steps/key-step.tsx | 3 +++ 4 files changed, 30 insertions(+) create mode 100644 src/features/ui/components/modals/nostr-signin-modal/components/emoji-graphic.tsx diff --git a/src/features/ui/components/modals/nostr-signin-modal/components/emoji-graphic.tsx b/src/features/ui/components/modals/nostr-signin-modal/components/emoji-graphic.tsx new file mode 100644 index 000000000..5d340f8a5 --- /dev/null +++ b/src/features/ui/components/modals/nostr-signin-modal/components/emoji-graphic.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import Emoji from 'soapbox/components/ui/emoji/emoji'; + +interface IEmojiGraphic { + emoji: string; +} + +/** Large emoji with a background for display purposes (eg breaking up a page). */ +const EmojiGraphic: React.FC = ({ emoji }) => { + return ( +
+
+ +
+
+ ); +}; + +export default EmojiGraphic; \ No newline at end of file diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx index 58c079f3e..b7fa74ae4 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx @@ -6,6 +6,8 @@ import Button from 'soapbox/components/ui/button/button'; import Stack from 'soapbox/components/ui/stack/stack'; import { useAppDispatch } from 'soapbox/hooks'; +import EmojiGraphic from '../components/emoji-graphic'; + interface IExtensionStep { setStep: (step: number) => void; } @@ -18,6 +20,8 @@ const ExtensionStep: React.FC = ({ setStep }) => { return ( + + diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx index 373b8cfcf..a0a5b6729 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx @@ -6,6 +6,7 @@ import HStack from 'soapbox/components/ui/hstack/hstack'; import Input from 'soapbox/components/ui/input/input'; import Stack from 'soapbox/components/ui/stack/stack'; +import EmojiGraphic from '../components/emoji-graphic'; import NostrExtensionIndicator from '../nostr-extension-indicator'; interface IIdentityStep { @@ -19,6 +20,8 @@ const IdentityStep: React.FC = ({ username, setUsername, setStep + + = ({ setStep }) => { + + , + link: (node) => , }} /> ) : ( diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx index 4f499c743..c4a207250 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx @@ -1,10 +1,13 @@ -import React from 'react'; +import React, { useState } from 'react'; +import { accountLookup } from 'soapbox/actions/accounts'; import Button from 'soapbox/components/ui/button/button'; +import Form from 'soapbox/components/ui/form/form'; import FormGroup from 'soapbox/components/ui/form-group/form-group'; import HStack from 'soapbox/components/ui/hstack/hstack'; import Input from 'soapbox/components/ui/input/input'; import Stack from 'soapbox/components/ui/stack/stack'; +import { useAppDispatch } from 'soapbox/hooks'; import EmojiGraphic from '../components/emoji-graphic'; import NostrExtensionIndicator from '../components/nostr-extension-indicator'; @@ -16,26 +19,62 @@ interface IIdentityStep { } const IdentityStep: React.FC = ({ username, setUsername, setStep }) => { + const dispatch = useAppDispatch(); + + const [loading, setLoading] = useState(false); + const [notFound, setNotFound] = useState(false); + + const handleChangeUsername: React.ChangeEventHandler = (e) => { + setNotFound(false); + setUsername(e.target.value); + }; + + const handleSubmit = async () => { + setLoading(true); + + await dispatch(accountLookup(username)) + .then(() => { + setStep(3); + setNotFound(false); + setLoading(false); + }) + .catch((error) => { + if (error.response?.status === 404) { + setNotFound(true); + } + setLoading(false); + }); + }; + + const errors: string[] = []; + if (notFound) { + errors.push('Account not found'); + } + return ( - - - - - - - setUsername(e.target.value)} - /> - - - - - - - +
+ + + + + + + + + + + + + + +
); }; From 872be9ead16eb626fef32576c1e48e15a975b4bf Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 18 Feb 2024 11:54:22 -0600 Subject: [PATCH 14/60] Flesh out AccountStep --- .../nostr-signin-modal/steps/account-step.tsx | 56 +++++++++++++++++-- .../steps/identity-step.tsx | 9 ++- src/schemas/account.ts | 1 + 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx index 1a01cc77d..2527bd779 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx @@ -1,7 +1,9 @@ -import React from 'react'; +import { NSchema as n } from 'nspec'; +import React, { useMemo } from 'react'; import { useAccountLookup } from 'soapbox/api/hooks'; -import Stack from 'soapbox/components/ui/stack/stack'; +import { Avatar, Text, Stack, Emoji, Button, Tooltip } from 'soapbox/components/ui'; +import { useInstance } from 'soapbox/hooks'; interface IAccountStep { username: string; @@ -9,10 +11,56 @@ interface IAccountStep { const AccountStep: React.FC = ({ username }) => { const { account } = useAccountLookup(username); + const instance = useInstance(); + + const isBech32 = useMemo( + () => n.bech32().safeParse(account?.acct).success, + [account?.acct], + ); + + if (!account) { + return null; + } return ( - - {JSON.stringify(account, null, 2)} + + + + + + + + + + {isBech32 ? ( + account.acct.slice(0, 13) + ) : ( + account.acct + )} + + + + + + {!account.ditto.is_registered && ( + + + + + + You need an account on {instance.title} to continue. + + + + + + )} ); }; diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx index c4a207250..5d20c0b1c 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx @@ -71,7 +71,14 @@ const IdentityStep: React.FC = ({ username, setUsername, setStep - + + diff --git a/src/schemas/account.ts b/src/schemas/account.ts index 40a5e5537..696ed1375 100644 --- a/src/schemas/account.ts +++ b/src/schemas/account.ts @@ -33,6 +33,7 @@ const baseAccountSchema = z.object({ display_name: z.string().catch(''), ditto: coerceObject({ accepts_zaps: z.boolean().catch(false), + is_registered: z.boolean().catch(false), }), emojis: filteredArray(customEmojiSchema), fields: filteredArray(fieldSchema), From 9ddcb1634e79a256f4baef95f0377390a9e32c73 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 18 Feb 2024 13:00:37 -0600 Subject: [PATCH 15/60] NostrSigninModal: hold accountId instead of username in the state --- src/api/hooks/accounts/useAccountLookup.ts | 2 +- .../modals/nostr-signin-modal/nostr-signin-modal.tsx | 6 +++--- .../modals/nostr-signin-modal/steps/account-step.tsx | 8 ++++---- .../modals/nostr-signin-modal/steps/identity-step.tsx | 9 +++++---- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/api/hooks/accounts/useAccountLookup.ts b/src/api/hooks/accounts/useAccountLookup.ts index 0e88435b6..7aed05f98 100644 --- a/src/api/hooks/accounts/useAccountLookup.ts +++ b/src/api/hooks/accounts/useAccountLookup.ts @@ -22,7 +22,7 @@ function useAccountLookup(acct: string | undefined, opts: UseAccountLookupOpts = const { entity: account, isUnauthorized, ...result } = useEntityLookup( Entities.ACCOUNTS, - (account) => account.acct.toLowerCase() === acct?.toLowerCase() || account.nostr.npub === acct, + (account) => account.acct.toLowerCase() === acct?.toLowerCase(), () => api.get(`/api/v1/accounts/lookup?acct=${acct}`), { schema: accountSchema, enabled: !!acct }, ); diff --git a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx index 479ea53e5..da8ada336 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx @@ -16,7 +16,7 @@ interface INostrSigninModal { const NostrSigninModal: React.FC = ({ onClose }) => { const [step, setStep] = useState(0); - const [username, setUsername] = useState(''); + const [accountId, setAccountId] = useState(); const handleClose = () => { onClose('NOSTR_SIGNIN'); @@ -27,11 +27,11 @@ const NostrSigninModal: React.FC = ({ onClose }) => { case 0: return ; case 1: - return ; + return ; case 2: return ; case 3: - return ; + return ; case 4: return ; } diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx index 2527bd779..8df2447fa 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx @@ -1,16 +1,16 @@ import { NSchema as n } from 'nspec'; import React, { useMemo } from 'react'; -import { useAccountLookup } from 'soapbox/api/hooks'; +import { useAccount } from 'soapbox/api/hooks'; import { Avatar, Text, Stack, Emoji, Button, Tooltip } from 'soapbox/components/ui'; import { useInstance } from 'soapbox/hooks'; interface IAccountStep { - username: string; + accountId: string; } -const AccountStep: React.FC = ({ username }) => { - const { account } = useAccountLookup(username); +const AccountStep: React.FC = ({ accountId }) => { + const { account } = useAccount(accountId); const instance = useInstance(); const isBech32 = useMemo( diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx index 5d20c0b1c..7b043eac5 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx @@ -13,16 +13,16 @@ import EmojiGraphic from '../components/emoji-graphic'; import NostrExtensionIndicator from '../components/nostr-extension-indicator'; interface IIdentityStep { - username: string; - setUsername(username: string): void; + setAccountId(accountId: string): void; setStep(step: number): void; } -const IdentityStep: React.FC = ({ username, setUsername, setStep }) => { +const IdentityStep: React.FC = ({ setAccountId, setStep }) => { const dispatch = useAppDispatch(); const [loading, setLoading] = useState(false); const [notFound, setNotFound] = useState(false); + const [username, setUsername] = useState(''); const handleChangeUsername: React.ChangeEventHandler = (e) => { setNotFound(false); @@ -33,7 +33,8 @@ const IdentityStep: React.FC = ({ username, setUsername, setStep setLoading(true); await dispatch(accountLookup(username)) - .then(() => { + .then((account) => { + setAccountId(account.id); setStep(3); setNotFound(false); setLoading(false); From 9267ef28e3dda67723c6e356ec9e91cb040fe4b9 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 18 Feb 2024 13:18:07 -0600 Subject: [PATCH 16/60] Modal: support back button --- src/components/ui/modal/modal.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/components/ui/modal/modal.tsx b/src/components/ui/modal/modal.tsx index dd4541ae3..85f8fc168 100644 --- a/src/components/ui/modal/modal.tsx +++ b/src/components/ui/modal/modal.tsx @@ -8,6 +8,7 @@ import HStack from '../hstack/hstack'; import IconButton from '../icon-button/icon-button'; const messages = defineMessages({ + back: { id: 'card.back.label', defaultMessage: 'Back' }, close: { id: 'lightbox.close', defaultMessage: 'Close' }, confirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, }); @@ -56,6 +57,7 @@ interface IModal { width?: keyof typeof widths; children?: React.ReactNode; className?: string; + onBack?: () => void; } /** Displays a modal dialog box. */ @@ -78,6 +80,7 @@ const Modal = React.forwardRef(({ title, width = 'xl', className, + onBack, }, ref) => { const intl = useIntl(); const buttonRef = React.useRef(null); @@ -102,6 +105,15 @@ const Modal = React.forwardRef(({ 'flex-row-reverse': closePosition === 'left', })} > + {onBack && ( + + )} +

{title}

From ba04c43477cbf717816379c9c256de8150436560 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 18 Feb 2024 13:27:30 -0600 Subject: [PATCH 17/60] NostrSigninModal: make each step render a Modal directly --- .../nostr-signin-modal/nostr-signin-modal.tsx | 63 ++++----------- .../nostr-signin-modal/steps/account-step.tsx | 81 +++++++++++-------- .../steps/extension-step.tsx | 30 +++---- .../steps/identity-step.tsx | 65 ++++++++------- .../nostr-signin-modal/steps/key-step.tsx | 31 +++---- .../steps/register-step.tsx | 14 ++-- 6 files changed, 136 insertions(+), 148 deletions(-) diff --git a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx index da8ada336..540707005 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx @@ -1,7 +1,4 @@ import React, { useState } from 'react'; -import { FormattedMessage } from 'react-intl'; - -import { Modal, Stack } from 'soapbox/components/ui'; import AccountStep from './steps/account-step'; import ExtensionStep from './steps/extension-step'; @@ -18,50 +15,22 @@ const NostrSigninModal: React.FC = ({ onClose }) => { const [accountId, setAccountId] = useState(); - const handleClose = () => { - onClose('NOSTR_SIGNIN'); - }; - - const renderStep = () => { - switch (step) { - case 0: - return ; - case 1: - return ; - case 2: - return ; - case 3: - return ; - case 4: - return ; - } - }; - - const renderModalTitle = () => { - switch (step) { - case 0: - return ; - case 1: - return ; - case 2: - return ; - case 3: - return ; - default: - return null; - } - }; - - return ( - - - {renderStep()} - - - ); + const handleClose = () => onClose('NOSTR_SIGNIN'); + + switch (step) { + case 0: + return ; + case 1: + return ; + case 2: + return ; + case 3: + return ; + case 4: + return ; + default: + return null; + } }; export default NostrSigninModal; diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx index 8df2447fa..320c21aef 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx @@ -1,15 +1,18 @@ import { NSchema as n } from 'nspec'; import React, { useMemo } from 'react'; +import { FormattedMessage } from 'react-intl'; import { useAccount } from 'soapbox/api/hooks'; -import { Avatar, Text, Stack, Emoji, Button, Tooltip } from 'soapbox/components/ui'; +import { Avatar, Text, Stack, Emoji, Button, Tooltip, HStack, Modal } from 'soapbox/components/ui'; import { useInstance } from 'soapbox/hooks'; interface IAccountStep { accountId: string; + setStep(step: number): void; + onClose(): void; } -const AccountStep: React.FC = ({ accountId }) => { +const AccountStep: React.FC = ({ accountId, setStep, onClose }) => { const { account } = useAccount(accountId); const instance = useInstance(); @@ -18,50 +21,58 @@ const AccountStep: React.FC = ({ accountId }) => { [account?.acct], ); + const goBack = () => setStep(1); + if (!account) { return null; } return ( - - - + } onClose={onClose}> + + + - - + + - - - {isBech32 ? ( - account.acct.slice(0, 13) - ) : ( - account.acct - )} - - + + + {isBech32 ? account.acct.slice(0, 13) : account.acct} + + + - - {!account.ditto.is_registered && ( - - - + {account.ditto.is_registered ? ( + + + + + ) : ( + + + - - You need an account on {instance.title} to continue. - - + + You need an account on {instance.title} to continue. + + - - - )} - + + + + + + )} + + ); }; diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx index b7fa74ae4..1e5afd869 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx @@ -2,34 +2,36 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import { nostrExtensionLogIn } from 'soapbox/actions/nostr'; -import Button from 'soapbox/components/ui/button/button'; -import Stack from 'soapbox/components/ui/stack/stack'; +import { Button, Stack, Modal } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; import EmojiGraphic from '../components/emoji-graphic'; interface IExtensionStep { setStep: (step: number) => void; + onClose(): void; } -const ExtensionStep: React.FC = ({ setStep }) => { +const ExtensionStep: React.FC = ({ setStep, onClose }) => { const dispatch = useAppDispatch(); const onClick = () => dispatch(nostrExtensionLogIn()); const onClickAlt = () => setStep(1); return ( - - - - - - - + } onClose={onClose}> + + + + + + + + ); }; diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx index 7b043eac5..57de1dd37 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx @@ -1,12 +1,8 @@ import React, { useState } from 'react'; +import { FormattedMessage } from 'react-intl'; import { accountLookup } from 'soapbox/actions/accounts'; -import Button from 'soapbox/components/ui/button/button'; -import Form from 'soapbox/components/ui/form/form'; -import FormGroup from 'soapbox/components/ui/form-group/form-group'; -import HStack from 'soapbox/components/ui/hstack/hstack'; -import Input from 'soapbox/components/ui/input/input'; -import Stack from 'soapbox/components/ui/stack/stack'; +import { Button, Form, FormGroup, HStack, Input, Stack, Modal } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; import EmojiGraphic from '../components/emoji-graphic'; @@ -15,9 +11,10 @@ import NostrExtensionIndicator from '../components/nostr-extension-indicator'; interface IIdentityStep { setAccountId(accountId: string): void; setStep(step: number): void; + onClose(): void; } -const IdentityStep: React.FC = ({ setAccountId, setStep }) => { +const IdentityStep: React.FC = ({ setAccountId, setStep, onClose }) => { const dispatch = useAppDispatch(); const [loading, setLoading] = useState(false); @@ -53,36 +50,38 @@ const IdentityStep: React.FC = ({ setAccountId, setStep }) => { } return ( -
- - + } onClose={onClose}> + + + - + - - - + + + - - + + - - - - + + +
+ + ); }; diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx index a31433a07..f474b6b71 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx @@ -1,32 +1,35 @@ import React from 'react'; +import { FormattedMessage } from 'react-intl'; -import Button from 'soapbox/components/ui/button/button'; -import Stack from 'soapbox/components/ui/stack/stack'; +import { Button, Stack, Modal } from 'soapbox/components/ui'; import EmojiGraphic from '../components/emoji-graphic'; import NostrExtensionIndicator from '../components/nostr-extension-indicator'; interface IKeyStep { setStep(step: number): void; + onClose(): void; } -const KeyStep: React.FC = ({ setStep }) => { +const KeyStep: React.FC = ({ setStep, onClose }) => { return ( - - + } onClose={onClose}> + + - + - - + + - + + - + ); }; diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/register-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/register-step.tsx index af6f7020d..4e6cdbc5e 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/register-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/register-step.tsx @@ -1,15 +1,19 @@ import React from 'react'; +import { FormattedMessage } from 'react-intl'; -import Stack from 'soapbox/components/ui/stack/stack'; +import { Stack, Modal } from 'soapbox/components/ui'; interface IRegisterStep { + onClose(): void; } -const RegisterStep: React.FC = () => { +const RegisterStep: React.FC = ({ onClose }) => { return ( - - register step - + } onClose={onClose}> + + register step + + ); }; From b382a96d6a921a2e3d9a806ce43f4798a51af433 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 18 Feb 2024 13:34:52 -0600 Subject: [PATCH 18/60] NostrSigninModal: make use of Back button --- .../nostr-signin-modal/steps/account-step.tsx | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx index 320c21aef..a69f6b635 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx @@ -3,7 +3,7 @@ import React, { useMemo } from 'react'; import { FormattedMessage } from 'react-intl'; import { useAccount } from 'soapbox/api/hooks'; -import { Avatar, Text, Stack, Emoji, Button, Tooltip, HStack, Modal } from 'soapbox/components/ui'; +import { Avatar, Text, Stack, Emoji, Button, Tooltip, Modal } from 'soapbox/components/ui'; import { useInstance } from 'soapbox/hooks'; interface IAccountStep { @@ -21,14 +21,16 @@ const AccountStep: React.FC = ({ accountId, setStep, onClose }) => [account?.acct], ); - const goBack = () => setStep(1); - if (!account) { return null; } return ( - } onClose={onClose}> + } + onClose={onClose} + onBack={() => setStep(1)} + > @@ -51,10 +53,7 @@ const AccountStep: React.FC = ({ accountId, setStep, onClose }) => {account.ditto.is_registered ? ( - - - - + ) : ( @@ -65,10 +64,7 @@ const AccountStep: React.FC = ({ accountId, setStep, onClose }) =>
- - - - +
)}
From 15ae362a8ec893dc8b50500bb8a491867308f318 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 18 Feb 2024 16:43:15 -0600 Subject: [PATCH 19/60] Add NKeyStorage class to retrieve and set keys in browser storage in a mostly-secure way --- package.json | 2 +- src/features/nostr/NKeyStorage.ts | 114 ++++++++++++++++++++++++++++++ src/features/nostr/keys.ts | 6 ++ src/schemas/nostr.ts | 4 +- src/utils/storage.ts | 15 ++++ src/workers/pow.worker.ts | 2 +- yarn.lock | 21 +++++- 7 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 src/features/nostr/NKeyStorage.ts create mode 100644 src/features/nostr/keys.ts create mode 100644 src/utils/storage.ts diff --git a/package.json b/package.json index 0cf19cd48..5f46b4ec7 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ "lodash": "^4.7.11", "mini-css-extract-plugin": "^2.6.0", "nostr-machina": "^0.1.0", - "nostr-tools": "^1.14.2", + "nostr-tools": "^2.3.0", "nspec": "^0.1.0", "path-browserify": "^1.0.1", "postcss": "^8.4.29", diff --git a/src/features/nostr/NKeyStorage.ts b/src/features/nostr/NKeyStorage.ts new file mode 100644 index 000000000..e26794736 --- /dev/null +++ b/src/features/nostr/NKeyStorage.ts @@ -0,0 +1,114 @@ +import { getPublicKey, nip19 } from 'nostr-tools'; +import { NSchema as n, NostrSigner, NSecSigner } from 'nspec'; +import { z } from 'zod'; + +import { lockStorageKey } from 'soapbox/utils/storage'; + +/** + * Gets Nostr keypairs from storage and returns a `Map`-like object of signers. + * When instantiated, it will lock the storage key to prevent tampering. + * Changes to the object will sync to storage. + */ +export class NKeyStorage implements ReadonlyMap { + + #keypairs = new Map(); + #storage: Storage; + #storageKey: string; + + constructor(storage: Storage, storageKey: string) { + this.#storage = storage; + this.#storageKey = storageKey; + + const data = this.#storage.getItem(storageKey); + lockStorageKey(storageKey); + + try { + for (const nsec of this.#dataSchema().parse(data)) { + const { data: secretKey } = nip19.decode(nsec); + const pubkey = getPublicKey(secretKey); + this.#keypairs.set(pubkey, secretKey); + } + } catch (e) { + this.clear(); + } + } + + #dataSchema() { + return n.json().pipe(z.set(this.#nsecSchema())); + } + + #nsecSchema() { + return n.bech32().refine((v): v is `nsec1${string}` => v.startsWith('nsec1'), { message: 'Invalid secret key' }); + } + + #syncStorage() { + const secretKeys = [...this.#keypairs.values()].map(nip19.nsecEncode); + this.#storage.setItem(this.#storageKey, JSON.stringify(secretKeys)); + } + + get size(): number { + return this.#keypairs.size; + } + + clear(): void { + this.#keypairs.clear(); + this.#syncStorage(); + } + + delete(pubkey: string): boolean { + const result = this.#keypairs.delete(pubkey); + this.#syncStorage(); + return result; + } + + forEach(callbackfn: (signer: NostrSigner, pubkey: string, map: typeof this) => void, thisArg?: any): void { + for (const [pubkey] of this.#keypairs) { + const signer = this.get(pubkey); + if (signer) { + callbackfn.call(thisArg, signer, pubkey, this); + } + } + } + + get(pubkey: string): NostrSigner | undefined { + const secretKey = this.#keypairs.get(pubkey); + if (secretKey) { + return new NSecSigner(secretKey); + } + } + + has(pubkey: string): boolean { + return this.#keypairs.has(pubkey); + } + + add(secretKey: Uint8Array): void { + const pubkey = getPublicKey(secretKey); + this.#keypairs.set(pubkey, secretKey); + this.#syncStorage(); + } + + *entries(): IterableIterator<[string, NostrSigner]> { + for (const [pubkey] of this.#keypairs) { + yield [pubkey, this.get(pubkey)!]; + } + } + + *keys(): IterableIterator { + for (const pubkey of this.#keypairs.keys()) { + yield pubkey; + } + } + + *values(): IterableIterator { + for (const pubkey of this.#keypairs.keys()) { + yield this.get(pubkey)!; + } + } + + [Symbol.iterator](): IterableIterator<[string, NostrSigner]> { + return this.entries(); + } + + [Symbol.toStringTag] = 'NKeyStorage'; + +} \ No newline at end of file diff --git a/src/features/nostr/keys.ts b/src/features/nostr/keys.ts new file mode 100644 index 000000000..92f9fc09f --- /dev/null +++ b/src/features/nostr/keys.ts @@ -0,0 +1,6 @@ +import { NKeyStorage } from './NKeyStorage'; + +export const NKeys = new NKeyStorage( + localStorage, + 'soapbox:nostr:keys', +); diff --git a/src/schemas/nostr.ts b/src/schemas/nostr.ts index 549bd497a..087b3f24e 100644 --- a/src/schemas/nostr.ts +++ b/src/schemas/nostr.ts @@ -1,4 +1,4 @@ -import { verifySignature } from 'nostr-tools'; +import { verifyEvent } from 'nostr-tools'; import { z } from 'zod'; /** Schema to validate Nostr hex IDs such as event IDs and pubkeys. */ @@ -22,7 +22,7 @@ const eventSchema = eventTemplateSchema.extend({ }); /** Nostr event schema that also verifies the event's signature. */ -const signedEventSchema = eventSchema.refine(verifySignature); +const signedEventSchema = eventSchema.refine(verifyEvent); /** NIP-46 signer options. */ const signEventOptsSchema = z.object({ diff --git a/src/utils/storage.ts b/src/utils/storage.ts new file mode 100644 index 000000000..a269b1504 --- /dev/null +++ b/src/utils/storage.ts @@ -0,0 +1,15 @@ +/** Lock a key from being accessed by `localStorage` and `sessionStorage`. */ +function lockStorageKey(key: string): void { + const proto = Object.getPrototypeOf(localStorage ?? sessionStorage); + const _getItem = proto.getItem; + + proto.getItem = function(_key: string) { + if (_key === key) { + throw new Error(`${_key} is locked`); + } else { + return _getItem.bind(this)(_key); + } + }; +} + +export { lockStorageKey }; \ No newline at end of file diff --git a/src/workers/pow.worker.ts b/src/workers/pow.worker.ts index dcfb948e5..24c342018 100644 --- a/src/workers/pow.worker.ts +++ b/src/workers/pow.worker.ts @@ -2,7 +2,7 @@ import * as Comlink from 'comlink'; import { nip13, type UnsignedEvent } from 'nostr-tools'; export const PowWorker = { - mine(event: UnsignedEvent, difficulty: number) { + mine(event: UnsignedEvent, difficulty: number) { return nip13.minePow(event, difficulty); }, }; diff --git a/yarn.lock b/yarn.lock index 5b6dddc39..e010e9afe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1837,6 +1837,11 @@ resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-0.2.0.tgz#a12cda60f3cf1ab5d7c77068c3711d2366649ed7" integrity sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw== +"@noble/ciphers@^0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-0.5.1.tgz#292f388b69c9ed80d49dca1a5cbfd4ff06852111" + integrity sha512-aNE06lbe36ifvMbbWvmmF/8jx6EQPu2HVg70V95T+iGjOuYwPpAccwAQc2HlXO2D0aiQ3zavbMga4jjWnrpiPA== + "@noble/curves@1.1.0", "@noble/curves@~1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" @@ -6578,7 +6583,7 @@ nostr-machina@^0.1.0: nostr-tools "^1.14.0" zod "^3.21.0" -nostr-tools@^1.14.0, nostr-tools@^1.14.2: +nostr-tools@^1.14.0: version "1.16.0" resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.16.0.tgz#5867f1d8bd055a5a3b27aadb199457dceb244314" integrity sha512-sx/aOl0gmkeHVoIVbyOhEQhzF88NsrBXMC8bsjhPASqA6oZ8uSOAyEGgRLMfC3SKgzQD5Gr6KvDoAahaD6xKcg== @@ -6604,6 +6609,20 @@ nostr-tools@^2.1.4: optionalDependencies: nostr-wasm v0.1.0 +nostr-tools@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-2.3.0.tgz#687d1af486a21e3e4805f0d4167c01221d871e65" + integrity sha512-jWD71y9JJ7DJ5/Si/DhREkjwyCWgMmY7x8qXfA9xC1HeosoGnaXuyYtspfYuiy8B8B2969C1iR6rWt6Fyf3IaA== + dependencies: + "@noble/ciphers" "^0.5.1" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.1" + "@scure/base" "1.1.1" + "@scure/bip32" "1.3.1" + "@scure/bip39" "1.2.1" + optionalDependencies: + nostr-wasm v0.1.0 + nostr-wasm@v0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/nostr-wasm/-/nostr-wasm-0.1.0.tgz#17af486745feb2b7dd29503fdd81613a24058d94" From f2bfa6e2f6b621a14a207c8968aae148ed66812d Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 18 Feb 2024 18:00:37 -0600 Subject: [PATCH 20/60] Upgrade nspec, parse nsec the easier way --- package.json | 2 +- src/features/nostr/NKeyStorage.ts | 6 +----- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 5f46b4ec7..ffa1bd2dd 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "mini-css-extract-plugin": "^2.6.0", "nostr-machina": "^0.1.0", "nostr-tools": "^2.3.0", - "nspec": "^0.1.0", + "nspec": "^0.2.0", "path-browserify": "^1.0.1", "postcss": "^8.4.29", "process": "^0.11.10", diff --git a/src/features/nostr/NKeyStorage.ts b/src/features/nostr/NKeyStorage.ts index e26794736..6100a8613 100644 --- a/src/features/nostr/NKeyStorage.ts +++ b/src/features/nostr/NKeyStorage.ts @@ -34,11 +34,7 @@ export class NKeyStorage implements ReadonlyMap { } #dataSchema() { - return n.json().pipe(z.set(this.#nsecSchema())); - } - - #nsecSchema() { - return n.bech32().refine((v): v is `nsec1${string}` => v.startsWith('nsec1'), { message: 'Invalid secret key' }); + return n.json().pipe(z.set(n.bech32('nsec'))); } #syncStorage() { diff --git a/yarn.lock b/yarn.lock index e010e9afe..a85703988 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6642,10 +6642,10 @@ npm-run-path@^5.1.0: dependencies: path-key "^4.0.0" -nspec@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/nspec/-/nspec-0.1.0.tgz#abde817cf34cb042d7315a70cf515037e489401b" - integrity sha512-HPVyFFVR2x49K7HJzEjlvvBR7x5t79G6bh7/SQvfm25hXVFq9xvYBQ6i3nluwJkizcBxm+fvErM5yqJEnM/1tA== +nspec@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/nspec/-/nspec-0.2.0.tgz#b59d8573ab60095e42f4b9773479fa7fbc78b756" + integrity sha512-bJjrt0u6/2rihfriSRR93woKIW0735WF0R1fFxJT40UfeK06Ky6dMWNKa/1LXtQg6lonxDlRYJ4LO/AuIBiFDw== dependencies: "@scure/base" "^1.1.5" "@scure/bip32" "^1.3.3" From 49bde675c3034851a11f8cce5c3d97b2870b9826 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 18 Feb 2024 22:55:05 -0600 Subject: [PATCH 21/60] Add KeygenStep, only show ExtensionStep when window.nostr is present --- src/features/nostr/NKeyStorage.ts | 3 +- .../components/emoji-graphic.tsx | 2 +- .../nostr-signin-modal/nostr-signin-modal.tsx | 7 ++- .../steps/identity-step.tsx | 4 +- .../nostr-signin-modal/steps/key-step.tsx | 4 +- .../nostr-signin-modal/steps/keygen-step.tsx | 62 +++++++++++++++++++ src/utils/input.ts | 9 +++ 7 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx diff --git a/src/features/nostr/NKeyStorage.ts b/src/features/nostr/NKeyStorage.ts index 6100a8613..156733082 100644 --- a/src/features/nostr/NKeyStorage.ts +++ b/src/features/nostr/NKeyStorage.ts @@ -77,10 +77,11 @@ export class NKeyStorage implements ReadonlyMap { return this.#keypairs.has(pubkey); } - add(secretKey: Uint8Array): void { + add(secretKey: Uint8Array): NostrSigner { const pubkey = getPublicKey(secretKey); this.#keypairs.set(pubkey, secretKey); this.#syncStorage(); + return this.get(pubkey)!; } *entries(): IterableIterator<[string, NostrSigner]> { diff --git a/src/features/ui/components/modals/nostr-signin-modal/components/emoji-graphic.tsx b/src/features/ui/components/modals/nostr-signin-modal/components/emoji-graphic.tsx index 5d340f8a5..541920307 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/components/emoji-graphic.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/components/emoji-graphic.tsx @@ -10,7 +10,7 @@ interface IEmojiGraphic { const EmojiGraphic: React.FC = ({ emoji }) => { return (
-
+
diff --git a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx index 540707005..c0c45a4ab 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx @@ -1,9 +1,11 @@ +import { NostrSigner } from 'nspec'; import React, { useState } from 'react'; import AccountStep from './steps/account-step'; import ExtensionStep from './steps/extension-step'; import IdentityStep from './steps/identity-step'; import KeyStep from './steps/key-step'; +import KeygenStep from './steps/keygen-step'; import RegisterStep from './steps/register-step'; interface INostrSigninModal { @@ -11,8 +13,9 @@ interface INostrSigninModal { } const NostrSigninModal: React.FC = ({ onClose }) => { - const [step, setStep] = useState(0); + const [step, setStep] = useState(window.nostr ? 0 : 1); + const [, setSigner] = useState(); const [accountId, setAccountId] = useState(); const handleClose = () => onClose('NOSTR_SIGNIN'); @@ -28,6 +31,8 @@ const NostrSigninModal: React.FC = ({ onClose }) => { return ; case 4: return ; + case 5: + return ; default: return null; } diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx index 57de1dd37..97e704436 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx @@ -53,7 +53,9 @@ const IdentityStep: React.FC = ({ setAccountId, setStep, onClose } onClose={onClose}>
- +
+ +
diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx index f474b6b71..e8a97c9b8 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx @@ -14,13 +14,13 @@ interface IKeyStep { const KeyStep: React.FC = ({ setStep, onClose }) => { return ( } onClose={onClose}> - + - diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx new file mode 100644 index 000000000..d1e1055ad --- /dev/null +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx @@ -0,0 +1,62 @@ +import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools'; +import { NostrSigner } from 'nspec'; +import React, { useMemo, useState } from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { Button, Stack, Modal } from 'soapbox/components/ui'; +import { NKeys } from 'soapbox/features/nostr/keys'; +import { useInstance } from 'soapbox/hooks'; +import { download } from 'soapbox/utils/download'; +import { slugify } from 'soapbox/utils/input'; + +import EmojiGraphic from '../components/emoji-graphic'; + +interface IKeygenStep { + setSigner(signer: NostrSigner): void; + setStep(step: number): void; + onClose(): void; +} + +const KeygenStep: React.FC = ({ setSigner, setStep, onClose }) => { + const instance = useInstance(); + + const secretKey = useMemo(() => generateSecretKey(), []); + const pubkey = useMemo(() => getPublicKey(secretKey), [secretKey]); + + const nsec = useMemo(() => nip19.nsecEncode(secretKey), [secretKey]); + const npub = useMemo(() => nip19.npubEncode(pubkey), [pubkey]); + + const [downloaded, setDownloaded] = useState(false); + + const handleDownload = () => { + download(nsec, `${slugify(instance.title)}-${npub.slice(5, 9)}.nsec`); + setDownloaded(true); + }; + + const handleNext = () => { + const signer = NKeys.add(secretKey); + setSigner(signer); + }; + + return ( + } onClose={onClose}> + + + + + + + + + + + + + ); +}; + +export default KeygenStep; diff --git a/src/utils/input.ts b/src/utils/input.ts index e9d8c2d85..8a91edfcc 100644 --- a/src/utils/input.ts +++ b/src/utils/input.ts @@ -8,6 +8,15 @@ const normalizeUsername = (username: string): string => { } }; +function slugify(text: string): string { + return text + .trim() + .toLowerCase() + .replace(/[^\w]/g, '-') // replace non-word characters with a hyphen + .replace(/-+/g, '-'); // replace multiple hyphens with a single hyphen +} + export { normalizeUsername, + slugify, }; \ No newline at end of file From f20687313fa83d2d1d2cb09b63c309a40169b3a1 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 18 Feb 2024 23:20:20 -0600 Subject: [PATCH 22/60] NostrSigninModal: represent steps as strings instead of numbers --- .../nostr-signin-modal/nostr-signin-modal.tsx | 20 +++++++++++-------- .../nostr-signin-modal/steps/account-step.tsx | 6 ++++-- .../steps/extension-step.tsx | 5 +++-- .../steps/identity-step.tsx | 7 ++++--- .../nostr-signin-modal/steps/key-step.tsx | 7 ++++--- .../nostr-signin-modal/steps/keygen-step.tsx | 3 ++- 6 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx index c0c45a4ab..215202ebf 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx @@ -8,12 +8,14 @@ import KeyStep from './steps/key-step'; import KeygenStep from './steps/keygen-step'; import RegisterStep from './steps/register-step'; +type Step = 'extension' | 'identity' | 'key' | 'keygen' | 'account' | 'register'; + interface INostrSigninModal { onClose: (type?: string) => void; } const NostrSigninModal: React.FC = ({ onClose }) => { - const [step, setStep] = useState(window.nostr ? 0 : 1); + const [step, setStep] = useState(window.nostr ? 'extension' : 'identity'); const [, setSigner] = useState(); const [accountId, setAccountId] = useState(); @@ -21,21 +23,23 @@ const NostrSigninModal: React.FC = ({ onClose }) => { const handleClose = () => onClose('NOSTR_SIGNIN'); switch (step) { - case 0: + case 'extension': return ; - case 1: + case 'identity': return ; - case 2: + case 'key': return ; - case 3: + case 'keygen': + return ; + case 'account': return ; - case 4: + case 'register': return ; - case 5: - return ; default: return null; } }; export default NostrSigninModal; + +export type { Step }; diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx index a69f6b635..fcd023a2a 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx @@ -6,9 +6,11 @@ import { useAccount } from 'soapbox/api/hooks'; import { Avatar, Text, Stack, Emoji, Button, Tooltip, Modal } from 'soapbox/components/ui'; import { useInstance } from 'soapbox/hooks'; +import { Step } from '../nostr-signin-modal'; + interface IAccountStep { accountId: string; - setStep(step: number): void; + setStep(step: Step): void; onClose(): void; } @@ -29,7 +31,7 @@ const AccountStep: React.FC = ({ accountId, setStep, onClose }) => } onClose={onClose} - onBack={() => setStep(1)} + onBack={() => setStep('identity')} > diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx index 1e5afd869..82a9f43e5 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx @@ -6,9 +6,10 @@ import { Button, Stack, Modal } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; import EmojiGraphic from '../components/emoji-graphic'; +import { Step } from '../nostr-signin-modal'; interface IExtensionStep { - setStep: (step: number) => void; + setStep: (step: Step) => void; onClose(): void; } @@ -16,7 +17,7 @@ const ExtensionStep: React.FC = ({ setStep, onClose }) => { const dispatch = useAppDispatch(); const onClick = () => dispatch(nostrExtensionLogIn()); - const onClickAlt = () => setStep(1); + const onClickAlt = () => setStep('identity'); return ( } onClose={onClose}> diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx index 97e704436..91c12711d 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx @@ -7,10 +7,11 @@ import { useAppDispatch } from 'soapbox/hooks'; import EmojiGraphic from '../components/emoji-graphic'; import NostrExtensionIndicator from '../components/nostr-extension-indicator'; +import { Step } from '../nostr-signin-modal'; interface IIdentityStep { setAccountId(accountId: string): void; - setStep(step: number): void; + setStep(step: Step): void; onClose(): void; } @@ -32,7 +33,7 @@ const IdentityStep: React.FC = ({ setAccountId, setStep, onClose await dispatch(accountLookup(username)) .then((account) => { setAccountId(account.id); - setStep(3); + setStep('account'); setNotFound(false); setLoading(false); }) @@ -71,7 +72,7 @@ const IdentityStep: React.FC = ({ setAccountId, setStep, onClose - + - diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx index d1e1055ad..630068f08 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx @@ -10,10 +10,11 @@ import { download } from 'soapbox/utils/download'; import { slugify } from 'soapbox/utils/input'; import EmojiGraphic from '../components/emoji-graphic'; +import { Step } from '../nostr-signin-modal'; interface IKeygenStep { setSigner(signer: NostrSigner): void; - setStep(step: number): void; + setStep(step: Step): void; onClose(): void; } From 772cecf26bf7d1a29f35780087f47172f27c1efc Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 19 Feb 2024 13:26:39 -0600 Subject: [PATCH 23/60] KeygenStep: display user's keys as copyable inputs --- src/components/copyable-input.tsx | 10 +++++++-- .../nostr-signin-modal/steps/keygen-step.tsx | 21 +++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/components/copyable-input.tsx b/src/components/copyable-input.tsx index 2422c8276..90089927c 100644 --- a/src/components/copyable-input.tsx +++ b/src/components/copyable-input.tsx @@ -6,10 +6,14 @@ import { Button, HStack, Input } from './ui'; interface ICopyableInput { /** Text to be copied. */ value: string; + /** Input type. */ + type?: 'text' | 'password'; + /** Callback after the value has been copied. */ + onCopy?(): void; } /** An input with copy abilities. */ -const CopyableInput: React.FC = ({ value }) => { +const CopyableInput: React.FC = ({ value, type = 'text', onCopy }) => { const input = useRef(null); const selectInput = () => { @@ -20,13 +24,15 @@ const CopyableInput: React.FC = ({ value }) => { } else { document.execCommand('copy'); } + + onCopy?.(); }; return ( = ({ setSigner, setStep, onClose }) => { const [downloaded, setDownloaded] = useState(false); const handleDownload = () => { - download(nsec, `${slugify(instance.title)}-${npub.slice(5, 9)}.nsec`); + download(nsec, `${slugify(instance.title)}-${npub.slice(5, 9)}.nsec.txt`); setDownloaded(true); }; + const handleCopy = () => setDownloaded(true); + const handleNext = () => { const signer = NKeys.add(secretKey); setSigner(signer); @@ -50,8 +53,18 @@ const KeygenStep: React.FC = ({ setSigner, setStep, onClose }) => { - - From 451e7ab96d778554f90297ee117e55d5a0223936 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 19 Feb 2024 13:49:05 -0600 Subject: [PATCH 24/60] ExtensionStep: improve whitespace between elements --- .../nostr-signin-modal/steps/extension-step.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx index 82a9f43e5..7b7eca96a 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx @@ -21,16 +21,18 @@ const ExtensionStep: React.FC = ({ setStep, onClose }) => { return ( } onClose={onClose}> - + - + + - + + ); From b1dc2486de51d4d50ef07050e4b8ba7f610952ba Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 19 Feb 2024 14:07:02 -0600 Subject: [PATCH 25/60] ExtensionStep: get rid of my-6 --- .../modals/nostr-signin-modal/steps/extension-step.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx index 7b7eca96a..d4c5bc26e 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx @@ -21,7 +21,7 @@ const ExtensionStep: React.FC = ({ setStep, onClose }) => { return ( } onClose={onClose}> - + From 5eb388bb3b6492f5f49e622bfd912f18a68fa52d Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 19 Feb 2024 14:07:18 -0600 Subject: [PATCH 26/60] KeygenStep: simplify it, add a tooltip over "Next" button --- src/components/ui/tooltip/tooltip.tsx | 8 ++++++- .../nostr-signin-modal/steps/keygen-step.tsx | 24 +++++++++---------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/components/ui/tooltip/tooltip.tsx b/src/components/ui/tooltip/tooltip.tsx index 9c86b94be..08d270c5d 100644 --- a/src/components/ui/tooltip/tooltip.tsx +++ b/src/components/ui/tooltip/tooltip.tsx @@ -15,13 +15,15 @@ interface ITooltip { children: React.ReactElement>; /** Text to display in the tooltip. */ text: string; + /** If disabled, it will render the children without wrapping them. */ + disabled?: boolean; } /** * Tooltip */ const Tooltip: React.FC = (props) => { - const { children, text } = props; + const { children, text, disabled = false } = props; const [isOpen, setIsOpen] = useState(false); @@ -55,6 +57,10 @@ const Tooltip: React.FC = (props) => { hover, ]); + if (disabled) { + return children; + } + return ( <> {React.cloneElement(children, { diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx index 6e63f64e8..cb4fd34ac 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx @@ -4,7 +4,7 @@ import React, { useMemo, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import CopyableInput from 'soapbox/components/copyable-input'; -import { Button, Stack, Modal, FormGroup } from 'soapbox/components/ui'; +import { Button, Stack, Modal, FormGroup, Text, Tooltip } from 'soapbox/components/ui'; import { NKeys } from 'soapbox/features/nostr/keys'; import { useInstance } from 'soapbox/hooks'; import { download } from 'soapbox/utils/download'; @@ -44,7 +44,7 @@ const KeygenStep: React.FC = ({ setSigner, setStep, onClose }) => { return ( } onClose={onClose}> - + @@ -53,20 +53,20 @@ const KeygenStep: React.FC = ({ setSigner, setStep, onClose }) => { - - - - + + + - - - + + Back up your secret key in a secure place. If lost, your account cannot be recovered. Never share your secret key with anyone. - + + + From 623fb234b368286434d8ffecaa2c7e187dc5b21b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 19 Feb 2024 14:28:43 -0600 Subject: [PATCH 27/60] Fix ModalLoading component --- src/features/ui/components/modal-loading.tsx | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/features/ui/components/modal-loading.tsx b/src/features/ui/components/modal-loading.tsx index fbafa5071..61bccffb3 100644 --- a/src/features/ui/components/modal-loading.tsx +++ b/src/features/ui/components/modal-loading.tsx @@ -1,18 +1,11 @@ import React from 'react'; -import { Spinner } from 'soapbox/components/ui'; +import { Modal, Spinner } from 'soapbox/components/ui'; const ModalLoading = () => ( -
-
- -
-
-
-
-
-
+ + + ); export default ModalLoading; From 0a3eb6b18709119e833e3668aacd3bb206fe319e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 19 Feb 2024 15:13:10 -0600 Subject: [PATCH 28/60] NKeyStorage: fix schema parser --- src/features/nostr/NKeyStorage.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/features/nostr/NKeyStorage.ts b/src/features/nostr/NKeyStorage.ts index 156733082..926249448 100644 --- a/src/features/nostr/NKeyStorage.ts +++ b/src/features/nostr/NKeyStorage.ts @@ -1,6 +1,5 @@ import { getPublicKey, nip19 } from 'nostr-tools'; import { NSchema as n, NostrSigner, NSecSigner } from 'nspec'; -import { z } from 'zod'; import { lockStorageKey } from 'soapbox/utils/storage'; @@ -23,7 +22,9 @@ export class NKeyStorage implements ReadonlyMap { lockStorageKey(storageKey); try { - for (const nsec of this.#dataSchema().parse(data)) { + const nsecs = new Set(this.#dataSchema().parse(data)); + + for (const nsec of nsecs) { const { data: secretKey } = nip19.decode(nsec); const pubkey = getPublicKey(secretKey); this.#keypairs.set(pubkey, secretKey); @@ -34,7 +35,7 @@ export class NKeyStorage implements ReadonlyMap { } #dataSchema() { - return n.json().pipe(z.set(n.bech32('nsec'))); + return n.json().pipe(n.bech32('nsec').array()); } #syncStorage() { From 31579696454c7658ff595095812ac6d17308b92e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 19 Feb 2024 15:17:35 -0600 Subject: [PATCH 29/60] Advance from KeygenStep to AccountStep --- src/actions/accounts.ts | 2 +- .../nostr-signin-modal/nostr-signin-modal.tsx | 2 +- .../nostr-signin-modal/steps/account-step.tsx | 3 ++- .../nostr-signin-modal/steps/keygen-step.tsx | 16 +++++++++++++--- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/actions/accounts.ts b/src/actions/accounts.ts index 2099cb63f..37dea3cc7 100644 --- a/src/actions/accounts.ts +++ b/src/actions/accounts.ts @@ -154,7 +154,7 @@ const fetchAccount = (id: string) => const account = selectAccount(getState(), id); if (account) { - return null; + return Promise.resolve(null); } dispatch(fetchAccountRequest(id)); diff --git a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx index 215202ebf..40a7c86e9 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx @@ -30,7 +30,7 @@ const NostrSigninModal: React.FC = ({ onClose }) => { case 'key': return ; case 'keygen': - return ; + return ; case 'account': return ; case 'register': diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx index fcd023a2a..4d8d889ac 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx @@ -4,6 +4,7 @@ import { FormattedMessage } from 'react-intl'; import { useAccount } from 'soapbox/api/hooks'; import { Avatar, Text, Stack, Emoji, Button, Tooltip, Modal } from 'soapbox/components/ui'; +import ModalLoading from 'soapbox/features/ui/components/modal-loading'; import { useInstance } from 'soapbox/hooks'; import { Step } from '../nostr-signin-modal'; @@ -24,7 +25,7 @@ const AccountStep: React.FC = ({ accountId, setStep, onClose }) => ); if (!account) { - return null; + return ; } return ( diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx index cb4fd34ac..ff73fb1dd 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx @@ -1,12 +1,13 @@ import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools'; import { NostrSigner } from 'nspec'; -import React, { useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { FormattedMessage } from 'react-intl'; +import { fetchAccount } from 'soapbox/actions/accounts'; import CopyableInput from 'soapbox/components/copyable-input'; import { Button, Stack, Modal, FormGroup, Text, Tooltip } from 'soapbox/components/ui'; import { NKeys } from 'soapbox/features/nostr/keys'; -import { useInstance } from 'soapbox/hooks'; +import { useAppDispatch, useInstance } from 'soapbox/hooks'; import { download } from 'soapbox/utils/download'; import { slugify } from 'soapbox/utils/input'; @@ -14,13 +15,15 @@ import EmojiGraphic from '../components/emoji-graphic'; import { Step } from '../nostr-signin-modal'; interface IKeygenStep { + setAccountId(accountId: string): void; setSigner(signer: NostrSigner): void; setStep(step: Step): void; onClose(): void; } -const KeygenStep: React.FC = ({ setSigner, setStep, onClose }) => { +const KeygenStep: React.FC = ({ setAccountId, setSigner, setStep, onClose }) => { const instance = useInstance(); + const dispatch = useAppDispatch(); const secretKey = useMemo(() => generateSecretKey(), []); const pubkey = useMemo(() => getPublicKey(secretKey), [secretKey]); @@ -30,6 +33,11 @@ const KeygenStep: React.FC = ({ setSigner, setStep, onClose }) => { const [downloaded, setDownloaded] = useState(false); + useEffect(() => { + // Pre-fetch into cache. + dispatch(fetchAccount(pubkey)).catch(() => {}); + }, [pubkey]); + const handleDownload = () => { download(nsec, `${slugify(instance.title)}-${npub.slice(5, 9)}.nsec.txt`); setDownloaded(true); @@ -40,6 +48,8 @@ const KeygenStep: React.FC = ({ setSigner, setStep, onClose }) => { const handleNext = () => { const signer = NKeys.add(secretKey); setSigner(signer); + setAccountId(pubkey); // HACK: Ditto uses pubkeys as account IDs. + setStep('account'); }; return ( From 470511acbb48f9ac5367289b772d4790734c89c7 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 19 Feb 2024 16:24:11 -0600 Subject: [PATCH 30/60] IdentityStep: prevent entry of nsec --- .../steps/identity-step.tsx | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx index 91c12711d..64f8a1814 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { accountLookup } from 'soapbox/actions/accounts'; import { Button, Form, FormGroup, HStack, Input, Stack, Modal } from 'soapbox/components/ui'; @@ -15,40 +15,44 @@ interface IIdentityStep { onClose(): void; } +const messages = defineMessages({ + notFound: { id: 'nostr_signin.identity.not_found', defaultMessage: 'Account not found' }, + nsec: { id: 'nostr_signin.identity.nsec', defaultMessage: 'Enter your public key' }, +}); + const IdentityStep: React.FC = ({ setAccountId, setStep, onClose }) => { + const intl = useIntl(); const dispatch = useAppDispatch(); + const [error, setError] = useState(); const [loading, setLoading] = useState(false); - const [notFound, setNotFound] = useState(false); const [username, setUsername] = useState(''); const handleChangeUsername: React.ChangeEventHandler = (e) => { - setNotFound(false); + setError(undefined); setUsername(e.target.value); }; const handleSubmit = async () => { setLoading(true); - await dispatch(accountLookup(username)) - .then((account) => { - setAccountId(account.id); - setStep('account'); - setNotFound(false); - setLoading(false); - }) - .catch((error) => { - if (error.response?.status === 404) { - setNotFound(true); - } - setLoading(false); - }); - }; + if (username.startsWith('nsec1')) { + setError(intl.formatMessage(messages.nsec)); + setLoading(false); + return; + } - const errors: string[] = []; - if (notFound) { - errors.push('Account not found'); - } + try { + const account = await dispatch(accountLookup(username)); + setAccountId(account.id); + setStep('account'); + } catch (e: any) { + if (e.response?.status === 404) { + setError(intl.formatMessage(messages.notFound)); + } + setLoading(false); + } + }; return ( } onClose={onClose}> @@ -60,7 +64,7 @@ const IdentityStep: React.FC = ({ setAccountId, setStep, onClose - + = ({ setAccountId, setStep, onClose +
)}
From 34bd5b5020426c4bf06edf2bd3265661851ae05b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 14 Mar 2024 16:17:53 -0500 Subject: [PATCH 37/60] Use pubkey instead of npub --- .../modals/nostr-signin-modal/steps/account-step.tsx | 5 ++++- src/schemas/account.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx index 3dd9d1457..4fee21fc8 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx @@ -1,4 +1,5 @@ import { NSchema as n } from '@soapbox/nspec'; +import { nip19 } from 'nostr-tools'; import React, { useMemo, useState } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -62,6 +63,8 @@ const AccountStep: React.FC = ({ accountId, setStep, onClose }) => return ; } + const acct = account.nostr.pubkey ? nip19.npubEncode(account.nostr.pubkey) : account.acct; + return ( } @@ -81,7 +84,7 @@ const AccountStep: React.FC = ({ accountId, setStep, onClose }) => truncate /> - + {username} diff --git a/src/schemas/account.ts b/src/schemas/account.ts index 21c941801..725cffefa 100644 --- a/src/schemas/account.ts +++ b/src/schemas/account.ts @@ -53,7 +53,7 @@ const baseAccountSchema = z.object({ z.null(), ]).catch(null), nostr: coerceObject({ - npub: n.bech32().optional().catch(undefined), + pubkey: n.id().optional().catch(undefined), }), note: contentSchema, /** Fedibird extra settings. */ From 44411fdf293878a1934684009761450782813606 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 15 Mar 2024 12:25:38 -0500 Subject: [PATCH 38/60] Add weblock package --- package.json | 1 + src/features/nostr/NKeyStorage.ts | 5 ++--- src/utils/storage.ts | 15 --------------- yarn.lock | 5 +++++ 4 files changed, 8 insertions(+), 18 deletions(-) delete mode 100644 src/utils/storage.ts diff --git a/package.json b/package.json index 5a7e6bca4..5fdca8015 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "@sentry/react": "^7.74.1", "@soapbox.pub/wasmboy": "^0.8.0", "@soapbox/nspec": "npm:@jsr/soapbox__nspec", + "@soapbox/weblock": "npm:@jsr/soapbox__weblock", "@tabler/icons": "^2.0.0", "@tailwindcss/aspect-ratio": "^0.4.2", "@tailwindcss/forms": "^0.5.7", diff --git a/src/features/nostr/NKeyStorage.ts b/src/features/nostr/NKeyStorage.ts index d5ed117b9..e297edb96 100644 --- a/src/features/nostr/NKeyStorage.ts +++ b/src/features/nostr/NKeyStorage.ts @@ -1,9 +1,8 @@ import { NSchema as n, NostrSigner, NSecSigner } from '@soapbox/nspec'; +import { WebLock } from '@soapbox/weblock'; import { getPublicKey, nip19 } from 'nostr-tools'; import { z } from 'zod'; -import { lockStorageKey } from 'soapbox/utils/storage'; - /** * Gets Nostr keypairs from storage and returns a `Map`-like object of signers. * When instantiated, it will lock the storage key to prevent tampering. @@ -20,7 +19,7 @@ export class NKeyStorage implements ReadonlyMap { this.#storageKey = storageKey; const data = this.#storage.getItem(storageKey); - lockStorageKey(storageKey); + WebLock.storages.lockKey(storageKey); try { const nsecs = new Set(this.#dataSchema().parse(data)); diff --git a/src/utils/storage.ts b/src/utils/storage.ts deleted file mode 100644 index a269b1504..000000000 --- a/src/utils/storage.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** Lock a key from being accessed by `localStorage` and `sessionStorage`. */ -function lockStorageKey(key: string): void { - const proto = Object.getPrototypeOf(localStorage ?? sessionStorage); - const _getItem = proto.getItem; - - proto.getItem = function(_key: string) { - if (_key === key) { - throw new Error(`${_key} is locked`); - } else { - return _getItem.bind(this)(_key); - } - }; -} - -export { lockStorageKey }; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index efe92922f..8a450c10b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2277,6 +2277,11 @@ websocket-ts "^2.1.5" zod "^3.22.4" +"@soapbox/weblock@npm:@jsr/soapbox__weblock": + version "0.1.0" + resolved "https://npm.jsr.io/~/7/@jsr/soapbox__weblock/0.1.0.tgz#749AEE0872D23CC4E37366D5F0D092B87986C5E1" + integrity sha512-FLLJL6xYk+k7f2bMXJ1nbcn3lhbEZXA0yboKLm8wns0hrcoEDOrWwmxkYF7xpVRndiAzFBctBGVbIAa3sA72ew== + "@surma/rollup-plugin-off-main-thread@^2.2.3": version "2.2.3" resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz#ee34985952ca21558ab0d952f00298ad2190c053" From a83916537afb8c13fbbcfac1c86c85eb82111f79 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 15 Mar 2024 15:15:53 -0500 Subject: [PATCH 39/60] Git Nostr signer from currently-logged-in account --- src/actions/accounts.ts | 11 +------- src/actions/nostr.ts | 11 ++++++-- src/contexts/nostr-context.tsx | 8 +++++- src/features/nostr/SoapboxSigner.ts | 44 ----------------------------- src/features/nostr/sign.ts | 13 --------- 5 files changed, 17 insertions(+), 70 deletions(-) delete mode 100644 src/features/nostr/SoapboxSigner.ts delete mode 100644 src/features/nostr/sign.ts diff --git a/src/actions/accounts.ts b/src/actions/accounts.ts index 37dea3cc7..f1d0f6c32 100644 --- a/src/actions/accounts.ts +++ b/src/actions/accounts.ts @@ -1,8 +1,5 @@ -import { nip19 } from 'nostr-tools'; - import { importEntities } from 'soapbox/entity-store/actions'; import { Entities } from 'soapbox/entity-store/entities'; -import { signer } from 'soapbox/features/nostr/sign'; import { selectAccount } from 'soapbox/selectors'; import { isLoggedIn } from 'soapbox/utils/auth'; import { getFeatures, parseVersion, PLEROMA } from 'soapbox/utils/features'; @@ -132,14 +129,8 @@ const noOp = () => new Promise(f => f(undefined)); const createAccount = (params: Record) => async (dispatch: AppDispatch, getState: () => RootState) => { - const { instance } = getState(); - const { nostrSignup } = getFeatures(instance); - const pubkey = (signer && nostrSignup) ? await signer.getPublicKey() : undefined; - dispatch({ type: ACCOUNT_CREATE_REQUEST, params }); - return api(getState, 'app').post('/api/v1/accounts', params, { - headers: pubkey ? { authorization: `Bearer ${nip19.npubEncode(pubkey)}` } : undefined, - }).then(({ data: token }) => { + return api(getState, 'app').post('/api/v1/accounts', params).then(({ data: token }) => { return dispatch({ type: ACCOUNT_CREATE_SUCCESS, params, token }); }).catch(error => { dispatch({ type: ACCOUNT_CREATE_FAIL, error, params }); diff --git a/src/actions/nostr.ts b/src/actions/nostr.ts index ab7dfb8e0..82e1006e3 100644 --- a/src/actions/nostr.ts +++ b/src/actions/nostr.ts @@ -5,6 +5,14 @@ import { type AppDispatch } from 'soapbox/store'; import { verifyCredentials } from './auth'; import { closeModal } from './modals'; +/** Log in with a Nostr pubkey. */ +function logInNostr(pubkey: string) { + return (dispatch: AppDispatch) => { + const npub = nip19.npubEncode(pubkey); + return dispatch(verifyCredentials(npub)); + }; +} + /** Log in with a Nostr extension. */ function nostrExtensionLogIn() { return async (dispatch: AppDispatch) => { @@ -13,10 +21,9 @@ function nostrExtensionLogIn() { } const pubkey = await window.nostr.getPublicKey(); - const npub = nip19.npubEncode(pubkey); dispatch(closeModal('NOSTR_SIGNIN')); - return dispatch(verifyCredentials(npub)); + return dispatch(logInNostr(pubkey)); }; } diff --git a/src/contexts/nostr-context.tsx b/src/contexts/nostr-context.tsx index de02a0a85..4664068c6 100644 --- a/src/contexts/nostr-context.tsx +++ b/src/contexts/nostr-context.tsx @@ -1,7 +1,8 @@ import { NRelay, NRelay1, NostrSigner } from '@soapbox/nspec'; import React, { createContext, useContext, useState, useEffect } from 'react'; -import { signer } from 'soapbox/features/nostr/sign'; +import { NKeys } from 'soapbox/features/nostr/keys'; +import { useOwnAccount } from 'soapbox/hooks'; import { useInstance } from 'soapbox/hooks/useInstance'; interface NostrContextType { @@ -20,8 +21,13 @@ export const NostrProvider: React.FC = ({ children }) => { const instance = useInstance(); const [relay, setRelay] = useState(); + const { account } = useOwnAccount(); + const url = instance.nostr?.relay; const pubkey = instance.nostr?.pubkey; + const accountPubkey = account?.nostr.pubkey; + + const signer = (accountPubkey ? NKeys.get(accountPubkey) : undefined) ?? window.nostr; useEffect(() => { if (url) { diff --git a/src/features/nostr/SoapboxSigner.ts b/src/features/nostr/SoapboxSigner.ts deleted file mode 100644 index 1b1d8bb81..000000000 --- a/src/features/nostr/SoapboxSigner.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { hexToBytes } from '@noble/hashes/utils'; -import { type NostrSigner, type NostrEvent, NSecSigner } from '@soapbox/nspec'; - -/** Use key from `localStorage` if available, falling back to NIP-07. */ -export class SoapboxSigner implements NostrSigner { - - #signer: NostrSigner; - - constructor() { - const privateKey = localStorage.getItem('soapbox:nostr:privateKey'); - const signer = privateKey ? new NSecSigner(hexToBytes(privateKey)) : window.nostr; - - if (!signer) { - throw new Error('No Nostr signer available'); - } - - this.#signer = signer; - } - - async getPublicKey(): Promise { - return this.#signer.getPublicKey(); - } - - async signEvent(event: Omit): Promise { - return this.#signer.signEvent(event); - } - - nip04 = { - encrypt: (pubkey: string, plaintext: string): Promise => { - if (!this.#signer.nip04) { - throw new Error('NIP-04 not supported by signer'); - } - return this.#signer.nip04.encrypt(pubkey, plaintext); - }, - - decrypt: (pubkey: string, ciphertext: string): Promise => { - if (!this.#signer.nip04) { - throw new Error('NIP-04 not supported by signer'); - } - return this.#signer.nip04.decrypt(pubkey, ciphertext); - }, - }; - -} \ No newline at end of file diff --git a/src/features/nostr/sign.ts b/src/features/nostr/sign.ts deleted file mode 100644 index e036678ca..000000000 --- a/src/features/nostr/sign.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { type NostrSigner } from '@soapbox/nspec'; - -import { SoapboxSigner } from './SoapboxSigner'; - -let signer: NostrSigner | undefined; - -try { - signer = new SoapboxSigner(); -} catch (_) { - // No signer available -} - -export { signer }; \ No newline at end of file From df24beeb54c8a5ab41c8ed974a566b623138e3cc Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 15 Mar 2024 16:10:03 -0500 Subject: [PATCH 40/60] Add key import step --- .../nostr-signin-modal/nostr-signin-modal.tsx | 5 +- .../steps/extension-step.tsx | 2 +- .../nostr-signin-modal/steps/key-add-step.tsx | 73 +++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 src/features/ui/components/modals/nostr-signin-modal/steps/key-add-step.tsx diff --git a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx index c6ed26196..69f0852aa 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx @@ -4,10 +4,11 @@ import React, { useState } from 'react'; import AccountStep from './steps/account-step'; import ExtensionStep from './steps/extension-step'; import IdentityStep from './steps/identity-step'; +import KeyAddStep from './steps/key-add-step'; import KeyStep from './steps/key-step'; import KeygenStep from './steps/keygen-step'; -type Step = 'extension' | 'identity' | 'key' | 'keygen' | 'account'; +type Step = 'extension' | 'identity' | 'key' | 'keygen' | 'key-add' | 'account'; interface INostrSigninModal { onClose: (type?: string) => void; @@ -28,6 +29,8 @@ const NostrSigninModal: React.FC = ({ onClose }) => { return ; case 'key': return ; + case 'key-add': + return ; case 'keygen': return ; case 'account': diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx index d4c5bc26e..2e6a1a796 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx @@ -17,7 +17,7 @@ const ExtensionStep: React.FC = ({ setStep, onClose }) => { const dispatch = useAppDispatch(); const onClick = () => dispatch(nostrExtensionLogIn()); - const onClickAlt = () => setStep('identity'); + const onClickAlt = () => setStep('key-add'); return ( } onClose={onClose}> diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/key-add-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/key-add-step.tsx new file mode 100644 index 000000000..d44c7accd --- /dev/null +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/key-add-step.tsx @@ -0,0 +1,73 @@ +import { NostrSigner } from '@soapbox/nspec'; +import { getPublicKey, nip19 } from 'nostr-tools'; +import React, { useState } from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { Button, Stack, Modal, Input, FormGroup, Form } from 'soapbox/components/ui'; +import { NKeys } from 'soapbox/features/nostr/keys'; + +import EmojiGraphic from '../components/emoji-graphic'; +import NostrExtensionIndicator from '../components/nostr-extension-indicator'; +import { Step } from '../nostr-signin-modal'; + +interface IKeyAddStep { + setAccountId(accountId: string): void; + setSigner(signer: NostrSigner): void; + setStep(step: Step): void; + onClose(): void; +} + +const KeyAddStep: React.FC = ({ setAccountId, setSigner, setStep, onClose }) => { + const [nsec, setNsec] = useState(''); + const [error, setError] = useState(); + + const handleChange = (e: React.ChangeEvent) => { + setNsec(e.target.value); + setError(undefined); + }; + + const handleSubmit = () => { + try { + const result = nip19.decode(nsec); + if (result.type === 'nsec') { + const seckey = result.data; + const pubkey = getPublicKey(seckey); + const signer = NKeys.add(seckey); + setAccountId(pubkey); + setSigner(signer); + setStep('account'); + } + } catch (e) { + setError('Invalid nsec'); + } + }; + + return ( + } onClose={onClose}> + + + + + + + + + + + + + + + + + ); +}; + +export default KeyAddStep; From 611e0e59f2d871fa6e45d16c99be943f75b5c304 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 16 Mar 2024 14:50:52 -0500 Subject: [PATCH 41/60] CSP: allow wss connections --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 5fee5c8e5..5e786dd97 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ - + From b8b22fc637d3e3154a883fcd435880c63f068175 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 16 Mar 2024 15:02:16 -0500 Subject: [PATCH 42/60] NostrSignin: remove unused setSigner state --- .../modals/nostr-signin-modal/nostr-signin-modal.tsx | 9 +++------ .../modals/nostr-signin-modal/steps/key-add-step.tsx | 7 ++----- .../modals/nostr-signin-modal/steps/keygen-step.tsx | 7 ++----- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx index 69f0852aa..b31ac38b0 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx @@ -1,4 +1,3 @@ -import { NostrSigner } from '@soapbox/nspec'; import React, { useState } from 'react'; import AccountStep from './steps/account-step'; @@ -16,8 +15,6 @@ interface INostrSigninModal { const NostrSigninModal: React.FC = ({ onClose }) => { const [step, setStep] = useState(window.nostr ? 'extension' : 'identity'); - - const [, setSigner] = useState(); const [accountId, setAccountId] = useState(); const handleClose = () => onClose('NOSTR_SIGNIN'); @@ -28,11 +25,11 @@ const NostrSigninModal: React.FC = ({ onClose }) => { case 'identity': return ; case 'key': - return ; + return ; case 'key-add': - return ; + return ; case 'keygen': - return ; + return ; case 'account': return ; default: diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/key-add-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/key-add-step.tsx index d44c7accd..ea3e6e9ba 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/key-add-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/key-add-step.tsx @@ -1,4 +1,3 @@ -import { NostrSigner } from '@soapbox/nspec'; import { getPublicKey, nip19 } from 'nostr-tools'; import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -12,12 +11,11 @@ import { Step } from '../nostr-signin-modal'; interface IKeyAddStep { setAccountId(accountId: string): void; - setSigner(signer: NostrSigner): void; setStep(step: Step): void; onClose(): void; } -const KeyAddStep: React.FC = ({ setAccountId, setSigner, setStep, onClose }) => { +const KeyAddStep: React.FC = ({ setAccountId, setStep, onClose }) => { const [nsec, setNsec] = useState(''); const [error, setError] = useState(); @@ -32,9 +30,8 @@ const KeyAddStep: React.FC = ({ setAccountId, setSigner, setStep, o if (result.type === 'nsec') { const seckey = result.data; const pubkey = getPublicKey(seckey); - const signer = NKeys.add(seckey); + NKeys.add(seckey); setAccountId(pubkey); - setSigner(signer); setStep('account'); } } catch (e) { diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx index d8bc2cc2e..506a1313b 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx @@ -1,4 +1,3 @@ -import { NostrSigner } from '@soapbox/nspec'; import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools'; import React, { useEffect, useMemo, useState } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -16,12 +15,11 @@ import { Step } from '../nostr-signin-modal'; interface IKeygenStep { setAccountId(accountId: string): void; - setSigner(signer: NostrSigner): void; setStep(step: Step): void; onClose(): void; } -const KeygenStep: React.FC = ({ setAccountId, setSigner, setStep, onClose }) => { +const KeygenStep: React.FC = ({ setAccountId, setStep, onClose }) => { const instance = useInstance(); const dispatch = useAppDispatch(); @@ -46,8 +44,7 @@ const KeygenStep: React.FC = ({ setAccountId, setSigner, setStep, o const handleCopy = () => setDownloaded(true); const handleNext = () => { - const signer = NKeys.add(secretKey); - setSigner(signer); + NKeys.add(secretKey); setAccountId(pubkey); // HACK: Ditto uses pubkeys as account IDs. setStep('account'); }; From 5b23166fb7fbb55c20910950d86bf1c52efa6284 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 16 Mar 2024 15:06:55 -0500 Subject: [PATCH 43/60] NostrSignin: remove IdentityStep, AccountStep, other useless code --- .../nostr-signin-modal/nostr-signin-modal.tsx | 15 +-- .../nostr-signin-modal/steps/account-step.tsx | 127 ------------------ .../steps/identity-step.tsx | 95 ------------- .../nostr-signin-modal/steps/key-add-step.tsx | 11 +- .../nostr-signin-modal/steps/key-step.tsx | 2 +- .../nostr-signin-modal/steps/keygen-step.tsx | 8 +- 6 files changed, 10 insertions(+), 248 deletions(-) delete mode 100644 src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx delete mode 100644 src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx diff --git a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx index b31ac38b0..259f25348 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx @@ -1,37 +1,30 @@ import React, { useState } from 'react'; -import AccountStep from './steps/account-step'; import ExtensionStep from './steps/extension-step'; -import IdentityStep from './steps/identity-step'; import KeyAddStep from './steps/key-add-step'; import KeyStep from './steps/key-step'; import KeygenStep from './steps/keygen-step'; -type Step = 'extension' | 'identity' | 'key' | 'keygen' | 'key-add' | 'account'; +type Step = 'extension' | 'key' | 'keygen' | 'key-add'; interface INostrSigninModal { onClose: (type?: string) => void; } const NostrSigninModal: React.FC = ({ onClose }) => { - const [step, setStep] = useState(window.nostr ? 'extension' : 'identity'); - const [accountId, setAccountId] = useState(); + const [step, setStep] = useState(window.nostr ? 'extension' : 'key-add'); const handleClose = () => onClose('NOSTR_SIGNIN'); switch (step) { case 'extension': return ; - case 'identity': - return ; case 'key': return ; case 'key-add': - return ; + return ; case 'keygen': - return ; - case 'account': - return ; + return ; default: return null; } diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx deleted file mode 100644 index 4fee21fc8..000000000 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/account-step.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { NSchema as n } from '@soapbox/nspec'; -import { nip19 } from 'nostr-tools'; -import React, { useMemo, useState } from 'react'; -import { FormattedMessage } from 'react-intl'; - -import { useAccount } from 'soapbox/api/hooks'; -import { Avatar, Text, Stack, Emoji, Button, Tooltip, Modal } from 'soapbox/components/ui'; -import { useNostr } from 'soapbox/contexts/nostr-context'; -import { useNostrReq } from 'soapbox/features/nostr/hooks/useNostrReq'; -import ModalLoading from 'soapbox/features/ui/components/modal-loading'; -import { useInstance } from 'soapbox/hooks'; - -import { Step } from '../nostr-signin-modal'; - -interface IAccountStep { - accountId: string; - setStep(step: Step): void; - onClose(): void; -} - -const AccountStep: React.FC = ({ accountId, setStep, onClose }) => { - const { relay, signer } = useNostr(); - const { account } = useAccount(accountId); - const [submitting, setSubmitting] = useState(false); - const [submitted, setSubmitted] = useState(false); - const instance = useInstance(); - - const { events } = useNostrReq((instance.nostr && account?.nostr) ? [{ - kinds: [7000, 6951], - authors: [instance.nostr.pubkey], - '#p': [account.nostr.pubkey], - }] : []); - - const success = events.find((event) => event.kind === 6951); - const feedback = events.find((event) => event.kind === 7000); - - const handleJoin = async () => { - if (!relay || !signer || !instance.nostr) return; - setSubmitting(true); - - const event = await signer.signEvent({ - kind: 5951, - content: '', - tags: [ - ['i', instance.nostr.relay, 'text'], - ['p', instance.nostr.pubkey, 'text'], - ], - created_at: Math.floor(Date.now() / 1000), - }); - - await relay.event(event); - - setSubmitting(false); - setSubmitted(true); - }; - - const username = useMemo( - () => n.bech32().safeParse(account?.acct).success ? account?.acct.slice(0, 13) : account?.acct, - [account?.acct], - ); - - if (!account) { - return ; - } - - const acct = account.nostr.pubkey ? nip19.npubEncode(account.nostr.pubkey) : account.acct; - - return ( - } - onClose={onClose} - onBack={() => setStep('identity')} - > - - - - - - - - - - {username} - - - - - - {account.ditto.is_registered ? ( - - ) : ( - - - - - - {(success || feedback) ? ( - JSON.stringify(success || feedback, null, 2) - ) : ( - <>You need an account on {instance.title} to continue. - )} - - - - - - )} - - - ); -}; - -export default AccountStep; diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx deleted file mode 100644 index 64f8a1814..000000000 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/identity-step.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React, { useState } from 'react'; -import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; - -import { accountLookup } from 'soapbox/actions/accounts'; -import { Button, Form, FormGroup, HStack, Input, Stack, Modal } from 'soapbox/components/ui'; -import { useAppDispatch } from 'soapbox/hooks'; - -import EmojiGraphic from '../components/emoji-graphic'; -import NostrExtensionIndicator from '../components/nostr-extension-indicator'; -import { Step } from '../nostr-signin-modal'; - -interface IIdentityStep { - setAccountId(accountId: string): void; - setStep(step: Step): void; - onClose(): void; -} - -const messages = defineMessages({ - notFound: { id: 'nostr_signin.identity.not_found', defaultMessage: 'Account not found' }, - nsec: { id: 'nostr_signin.identity.nsec', defaultMessage: 'Enter your public key' }, -}); - -const IdentityStep: React.FC = ({ setAccountId, setStep, onClose }) => { - const intl = useIntl(); - const dispatch = useAppDispatch(); - - const [error, setError] = useState(); - const [loading, setLoading] = useState(false); - const [username, setUsername] = useState(''); - - const handleChangeUsername: React.ChangeEventHandler = (e) => { - setError(undefined); - setUsername(e.target.value); - }; - - const handleSubmit = async () => { - setLoading(true); - - if (username.startsWith('nsec1')) { - setError(intl.formatMessage(messages.nsec)); - setLoading(false); - return; - } - - try { - const account = await dispatch(accountLookup(username)); - setAccountId(account.id); - setStep('account'); - } catch (e: any) { - if (e.response?.status === 404) { - setError(intl.formatMessage(messages.notFound)); - } - setLoading(false); - } - }; - - return ( - } onClose={onClose}> -
- -
- -
- - - - - - - - - - - - -
-
-
- ); -}; - -export default IdentityStep; diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/key-add-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/key-add-step.tsx index ea3e6e9ba..06ab0c2c5 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/key-add-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/key-add-step.tsx @@ -1,4 +1,4 @@ -import { getPublicKey, nip19 } from 'nostr-tools'; +import { nip19 } from 'nostr-tools'; import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -7,15 +7,12 @@ import { NKeys } from 'soapbox/features/nostr/keys'; import EmojiGraphic from '../components/emoji-graphic'; import NostrExtensionIndicator from '../components/nostr-extension-indicator'; -import { Step } from '../nostr-signin-modal'; interface IKeyAddStep { - setAccountId(accountId: string): void; - setStep(step: Step): void; onClose(): void; } -const KeyAddStep: React.FC = ({ setAccountId, setStep, onClose }) => { +const KeyAddStep: React.FC = ({ onClose }) => { const [nsec, setNsec] = useState(''); const [error, setError] = useState(); @@ -29,10 +26,8 @@ const KeyAddStep: React.FC = ({ setAccountId, setStep, onClose }) = const result = nip19.decode(nsec); if (result.type === 'nsec') { const seckey = result.data; - const pubkey = getPublicKey(seckey); NKeys.add(seckey); - setAccountId(pubkey); - setStep('account'); + // TODO: log in, close modal } } catch (e) { setError('Invalid nsec'); diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx index 7d2e6ef62..fef9bd9b1 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx @@ -25,7 +25,7 @@ const KeyStep: React.FC = ({ setStep, onClose }) => { Generate key -
diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx index 506a1313b..b024b2356 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx @@ -11,15 +11,12 @@ import { download } from 'soapbox/utils/download'; import { slugify } from 'soapbox/utils/input'; import EmojiGraphic from '../components/emoji-graphic'; -import { Step } from '../nostr-signin-modal'; interface IKeygenStep { - setAccountId(accountId: string): void; - setStep(step: Step): void; onClose(): void; } -const KeygenStep: React.FC = ({ setAccountId, setStep, onClose }) => { +const KeygenStep: React.FC = ({ onClose }) => { const instance = useInstance(); const dispatch = useAppDispatch(); @@ -45,8 +42,7 @@ const KeygenStep: React.FC = ({ setAccountId, setStep, onClose }) = const handleNext = () => { NKeys.add(secretKey); - setAccountId(pubkey); // HACK: Ditto uses pubkeys as account IDs. - setStep('account'); + // TODO: log in, close modal }; return ( From 77f766ff64ecf1601b2ae5790e4992263a9ad14d Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 16 Mar 2024 15:08:48 -0500 Subject: [PATCH 44/60] Remove unused DittoSignup module --- .../modals/nostr-signin-modal/DittoSignup.ts | 55 ------------------- 1 file changed, 55 deletions(-) delete mode 100644 src/features/ui/components/modals/nostr-signin-modal/DittoSignup.ts diff --git a/src/features/ui/components/modals/nostr-signin-modal/DittoSignup.ts b/src/features/ui/components/modals/nostr-signin-modal/DittoSignup.ts deleted file mode 100644 index efe4f46f4..000000000 --- a/src/features/ui/components/modals/nostr-signin-modal/DittoSignup.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { NRelay, NostrEvent, NostrSigner } from '@soapbox/nspec'; - -interface DittoSignupRequestOpts { - dvm: string; - url: string; - relay: NRelay; - signer: NostrSigner; - signal?: AbortSignal; -} - -export class DittoSignup { - - static async request(opts: DittoSignupRequestOpts): Promise { - const { dvm, url, relay, signer, signal } = opts; - - const pubkey = await signer.getPublicKey(); - const event = await signer.signEvent({ - kind: 5951, - content: '', - tags: [ - ['i', url, 'text'], - ['p', dvm], - ], - created_at: Math.floor(Date.now() / 1000), - }); - - const subscription = relay.req( - [{ kinds: [7000, 6951], authors: [dvm], '#p': [pubkey], '#e': [event.id] }], - { signal }, - ); - - await relay.event(event, { signal }); - - for await (const msg of subscription) { - if (msg[0] === 'EVENT') { - return msg[2]; - } - } - - throw new Error('DittoSignup: no response'); - } - - static async check(opts: Omit): Promise { - const { dvm, relay, signer, signal } = opts; - - const pubkey = await signer.getPublicKey(); - const [event] = await relay.query( - [{ kinds: [7000, 6951], authors: [dvm], '#p': [pubkey] }], - { signal }, - ); - - return event; - } - -} \ No newline at end of file From a8e786a578e806003bc676b0db5afa28436650da Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 16 Mar 2024 15:10:34 -0500 Subject: [PATCH 45/60] NostrSignin: update steps --- .../components/modals/nostr-signin-modal/nostr-signin-modal.tsx | 2 +- .../modals/nostr-signin-modal/steps/extension-step.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx index 259f25348..9188796ba 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx @@ -12,7 +12,7 @@ interface INostrSigninModal { } const NostrSigninModal: React.FC = ({ onClose }) => { - const [step, setStep] = useState(window.nostr ? 'extension' : 'key-add'); + const [step, setStep] = useState(window.nostr ? 'extension' : 'key'); const handleClose = () => onClose('NOSTR_SIGNIN'); diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx index 2e6a1a796..7957f3f2c 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx @@ -17,7 +17,7 @@ const ExtensionStep: React.FC = ({ setStep, onClose }) => { const dispatch = useAppDispatch(); const onClick = () => dispatch(nostrExtensionLogIn()); - const onClickAlt = () => setStep('key-add'); + const onClickAlt = () => setStep('key'); return ( } onClose={onClose}> From 5bc6a9a2200bed4a6b20a5a62344ab4b5c82e6df Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 16 Mar 2024 15:50:26 -0500 Subject: [PATCH 46/60] Rework nostrExtensionLogIn action --- src/actions/nostr.ts | 4 ---- .../components/nostr-extension-indicator.tsx | 6 +++++- .../modals/nostr-signin-modal/steps/extension-step.tsx | 6 +++++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/actions/nostr.ts b/src/actions/nostr.ts index 82e1006e3..d82fa49f4 100644 --- a/src/actions/nostr.ts +++ b/src/actions/nostr.ts @@ -3,7 +3,6 @@ import { nip19 } from 'nostr-tools'; import { type AppDispatch } from 'soapbox/store'; import { verifyCredentials } from './auth'; -import { closeModal } from './modals'; /** Log in with a Nostr pubkey. */ function logInNostr(pubkey: string) { @@ -19,10 +18,7 @@ function nostrExtensionLogIn() { if (!window.nostr) { throw new Error('No Nostr signer available'); } - const pubkey = await window.nostr.getPublicKey(); - - dispatch(closeModal('NOSTR_SIGNIN')); return dispatch(logInNostr(pubkey)); }; } diff --git a/src/features/ui/components/modals/nostr-signin-modal/components/nostr-extension-indicator.tsx b/src/features/ui/components/modals/nostr-signin-modal/components/nostr-extension-indicator.tsx index 63fddbdc1..78dddd33d 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/components/nostr-extension-indicator.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/components/nostr-extension-indicator.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import { closeModal } from 'soapbox/actions/modals'; import { nostrExtensionLogIn } from 'soapbox/actions/nostr'; import Stack from 'soapbox/components/ui/stack/stack'; import Text from 'soapbox/components/ui/text/text'; @@ -9,7 +10,10 @@ import { useAppDispatch } from 'soapbox/hooks'; const NostrExtensionIndicator: React.FC = () => { const dispatch = useAppDispatch(); - const onClick = () => dispatch(nostrExtensionLogIn()); + const onClick = () => { + dispatch(nostrExtensionLogIn()); + dispatch(closeModal('NOSTR_SIGNIN')); + }; return ( diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx index 7957f3f2c..42bfb4d04 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx @@ -16,7 +16,11 @@ interface IExtensionStep { const ExtensionStep: React.FC = ({ setStep, onClose }) => { const dispatch = useAppDispatch(); - const onClick = () => dispatch(nostrExtensionLogIn()); + const onClick = () => { + dispatch(nostrExtensionLogIn()); + onClose(); + }; + const onClickAlt = () => setStep('key'); return ( From 62723a2c1690248b81f210e55c1dedc3619573e2 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 16 Mar 2024 16:03:54 -0500 Subject: [PATCH 47/60] Break NostrLogin into a separate flow from NostrSignin --- src/features/ui/components/modal-root.tsx | 2 + .../components/emoji-graphic.tsx | 20 +++++++++ .../components/nostr-extension-indicator.tsx | 37 +++++++++++++++ .../nostr-login-modal/nostr-login-modal.tsx | 29 ++++++++++++ .../steps/extension-step.tsx | 45 +++++++++++++++++++ .../steps/key-add-step.tsx | 0 .../nostr-signin-modal/nostr-signin-modal.tsx | 5 +-- .../nostr-signin-modal/steps/key-step.tsx | 11 ++++- src/features/ui/components/navbar.tsx | 2 +- src/features/ui/util/async-components.ts | 1 + 10 files changed, 146 insertions(+), 6 deletions(-) create mode 100644 src/features/ui/components/modals/nostr-login-modal/components/emoji-graphic.tsx create mode 100644 src/features/ui/components/modals/nostr-login-modal/components/nostr-extension-indicator.tsx create mode 100644 src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx create mode 100644 src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx rename src/features/ui/components/modals/{nostr-signin-modal => nostr-login-modal}/steps/key-add-step.tsx (100%) diff --git a/src/features/ui/components/modal-root.tsx b/src/features/ui/components/modal-root.tsx index 7fec9c25c..2f000ef2d 100644 --- a/src/features/ui/components/modal-root.tsx +++ b/src/features/ui/components/modal-root.tsx @@ -30,6 +30,7 @@ import { MentionsModal, MissingDescriptionModal, MuteModal, + NostrLoginModal, NostrSigninModal, ReactionsModal, ReblogsModal, @@ -71,6 +72,7 @@ const MODAL_COMPONENTS: Record> = { 'MENTIONS': MentionsModal, 'MISSING_DESCRIPTION': MissingDescriptionModal, 'MUTE': MuteModal, + 'NOSTR_LOGIN': NostrLoginModal, 'NOSTR_SIGNIN': NostrSigninModal, 'REACTIONS': ReactionsModal, 'REBLOGS': ReblogsModal, diff --git a/src/features/ui/components/modals/nostr-login-modal/components/emoji-graphic.tsx b/src/features/ui/components/modals/nostr-login-modal/components/emoji-graphic.tsx new file mode 100644 index 000000000..541920307 --- /dev/null +++ b/src/features/ui/components/modals/nostr-login-modal/components/emoji-graphic.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import Emoji from 'soapbox/components/ui/emoji/emoji'; + +interface IEmojiGraphic { + emoji: string; +} + +/** Large emoji with a background for display purposes (eg breaking up a page). */ +const EmojiGraphic: React.FC = ({ emoji }) => { + return ( +
+
+ +
+
+ ); +}; + +export default EmojiGraphic; \ No newline at end of file diff --git a/src/features/ui/components/modals/nostr-login-modal/components/nostr-extension-indicator.tsx b/src/features/ui/components/modals/nostr-login-modal/components/nostr-extension-indicator.tsx new file mode 100644 index 000000000..78dddd33d --- /dev/null +++ b/src/features/ui/components/modals/nostr-login-modal/components/nostr-extension-indicator.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { closeModal } from 'soapbox/actions/modals'; +import { nostrExtensionLogIn } from 'soapbox/actions/nostr'; +import Stack from 'soapbox/components/ui/stack/stack'; +import Text from 'soapbox/components/ui/text/text'; +import { useAppDispatch } from 'soapbox/hooks'; + +const NostrExtensionIndicator: React.FC = () => { + const dispatch = useAppDispatch(); + + const onClick = () => { + dispatch(nostrExtensionLogIn()); + dispatch(closeModal('NOSTR_SIGNIN')); + }; + + return ( + + + {window.nostr ? ( + , + }} + /> + ) : ( + + )} + + + ); +}; + +export default NostrExtensionIndicator; \ No newline at end of file diff --git a/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx b/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx new file mode 100644 index 000000000..d3aae0a8a --- /dev/null +++ b/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx @@ -0,0 +1,29 @@ +import React, { useState } from 'react'; + +import ExtensionStep from './steps/extension-step'; +import KeyAddStep from './steps/key-add-step'; + +type Step = 'extension' | 'key-add'; + +interface INostrLoginModal { + onClose: (type?: string) => void; +} + +const NostrLoginModal: React.FC = ({ onClose }) => { + const [step, setStep] = useState(window.nostr ? 'extension' : 'key-add'); + + const handleClose = () => onClose('NOSTR_SIGNIN'); + + switch (step) { + case 'extension': + return ; + case 'key-add': + return ; + default: + return null; + } +}; + +export default NostrLoginModal; + +export type { Step }; diff --git a/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx new file mode 100644 index 000000000..e155ab06b --- /dev/null +++ b/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { nostrExtensionLogIn } from 'soapbox/actions/nostr'; +import { Button, Stack, Modal } from 'soapbox/components/ui'; +import { useAppDispatch } from 'soapbox/hooks'; + +import EmojiGraphic from '../components/emoji-graphic'; +import { Step } from '../nostr-login-modal'; + +interface IExtensionStep { + setStep: (step: Step) => void; + onClose(): void; +} + +const ExtensionStep: React.FC = ({ setStep, onClose }) => { + const dispatch = useAppDispatch(); + + const onClick = () => { + dispatch(nostrExtensionLogIn()); + onClose(); + }; + + const onClickAlt = () => setStep('key-add'); + + return ( + } onClose={onClose}> + + + + + + + + + + + ); +}; + +export default ExtensionStep; diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/key-add-step.tsx b/src/features/ui/components/modals/nostr-login-modal/steps/key-add-step.tsx similarity index 100% rename from src/features/ui/components/modals/nostr-signin-modal/steps/key-add-step.tsx rename to src/features/ui/components/modals/nostr-login-modal/steps/key-add-step.tsx diff --git a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx index 9188796ba..6865e56ec 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx @@ -1,11 +1,10 @@ import React, { useState } from 'react'; import ExtensionStep from './steps/extension-step'; -import KeyAddStep from './steps/key-add-step'; import KeyStep from './steps/key-step'; import KeygenStep from './steps/keygen-step'; -type Step = 'extension' | 'key' | 'keygen' | 'key-add'; +type Step = 'extension' | 'key' | 'keygen'; interface INostrSigninModal { onClose: (type?: string) => void; @@ -21,8 +20,6 @@ const NostrSigninModal: React.FC = ({ onClose }) => { return ; case 'key': return ; - case 'key-add': - return ; case 'keygen': return ; default: diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx b/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx index fef9bd9b1..73a71e501 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx +++ b/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx @@ -1,7 +1,9 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import { openModal } from 'soapbox/actions/modals'; import { Button, Stack, Modal } from 'soapbox/components/ui'; +import { useAppDispatch } from 'soapbox/hooks'; import EmojiGraphic from '../components/emoji-graphic'; import NostrExtensionIndicator from '../components/nostr-extension-indicator'; @@ -13,6 +15,13 @@ interface IKeyStep { } const KeyStep: React.FC = ({ setStep, onClose }) => { + const dispatch = useAppDispatch(); + + const onAltClick = () => { + dispatch(openModal('NOSTR_LOGIN')); + onClose(); + }; + return ( } onClose={onClose}> @@ -25,7 +34,7 @@ const KeyStep: React.FC = ({ setStep, onClose }) => { Generate key - diff --git a/src/features/ui/components/navbar.tsx b/src/features/ui/components/navbar.tsx index 81245a214..0be4e3948 100644 --- a/src/features/ui/components/navbar.tsx +++ b/src/features/ui/components/navbar.tsx @@ -40,7 +40,7 @@ const Navbar = () => { const onOpenSidebar = () => dispatch(openSidebar()); const handleNostrLogin = async () => { - dispatch(openModal('NOSTR_SIGNIN')); + dispatch(openModal('NOSTR_LOGIN')); }; const handleSubmit: React.FormEventHandler = (event) => { diff --git a/src/features/ui/util/async-components.ts b/src/features/ui/util/async-components.ts index d1313e0af..26e86a1ce 100644 --- a/src/features/ui/util/async-components.ts +++ b/src/features/ui/util/async-components.ts @@ -163,3 +163,4 @@ export const FollowedTags = lazy(() => import('soapbox/features/followed-tags')) export const AccountNotePanel = lazy(() => import('soapbox/features/ui/components/panels/account-note-panel')); export const ComposeEditor = lazy(() => import('soapbox/features/compose/editor')); export const NostrSigninModal = lazy(() => import('soapbox/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal')); +export const NostrLoginModal = lazy(() => import('soapbox/features/ui/components/modals/nostr-login-modal/nostr-login-modal')); From f7b7260eea0e9e78a6427a889fff6abce2db506f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 16 Mar 2024 16:05:56 -0500 Subject: [PATCH 48/60] nostr-signin-modal -> nostr-signup-modal --- src/features/ui/components/modal-root.tsx | 4 ++-- .../components/nostr-extension-indicator.tsx | 2 +- .../modals/nostr-login-modal/nostr-login-modal.tsx | 2 +- .../modals/nostr-login-modal/steps/extension-step.tsx | 6 +++--- .../modals/nostr-login-modal/steps/key-add-step.tsx | 2 +- .../components/emoji-graphic.tsx | 0 .../components/nostr-extension-indicator.tsx | 2 +- .../nostr-signup-modal.tsx} | 6 +++--- .../steps/extension-step.tsx | 8 ++++---- .../steps/key-step.tsx | 4 ++-- .../steps/keygen-step.tsx | 2 +- src/features/ui/components/panels/sign-up-panel.tsx | 2 +- src/features/ui/util/async-components.ts | 2 +- 13 files changed, 21 insertions(+), 21 deletions(-) rename src/features/ui/components/modals/{nostr-signin-modal => nostr-signup-modal}/components/emoji-graphic.tsx (100%) rename src/features/ui/components/modals/{nostr-signin-modal => nostr-signup-modal}/components/nostr-extension-indicator.tsx (93%) rename src/features/ui/components/modals/{nostr-signin-modal/nostr-signin-modal.tsx => nostr-signup-modal/nostr-signup-modal.tsx} (82%) rename src/features/ui/components/modals/{nostr-signin-modal => nostr-signup-modal}/steps/extension-step.tsx (82%) rename src/features/ui/components/modals/{nostr-signin-modal => nostr-signup-modal}/steps/key-step.tsx (91%) rename src/features/ui/components/modals/{nostr-signin-modal => nostr-signup-modal}/steps/keygen-step.tsx (97%) diff --git a/src/features/ui/components/modal-root.tsx b/src/features/ui/components/modal-root.tsx index 2f000ef2d..36b099216 100644 --- a/src/features/ui/components/modal-root.tsx +++ b/src/features/ui/components/modal-root.tsx @@ -31,7 +31,7 @@ import { MissingDescriptionModal, MuteModal, NostrLoginModal, - NostrSigninModal, + NostrSignupModal, ReactionsModal, ReblogsModal, ReplyMentionsModal, @@ -73,7 +73,7 @@ const MODAL_COMPONENTS: Record> = { 'MISSING_DESCRIPTION': MissingDescriptionModal, 'MUTE': MuteModal, 'NOSTR_LOGIN': NostrLoginModal, - 'NOSTR_SIGNIN': NostrSigninModal, + 'NOSTR_SIGNUP': NostrSignupModal, 'REACTIONS': ReactionsModal, 'REBLOGS': ReblogsModal, 'REPLY_MENTIONS': ReplyMentionsModal, diff --git a/src/features/ui/components/modals/nostr-login-modal/components/nostr-extension-indicator.tsx b/src/features/ui/components/modals/nostr-login-modal/components/nostr-extension-indicator.tsx index 78dddd33d..38493efe6 100644 --- a/src/features/ui/components/modals/nostr-login-modal/components/nostr-extension-indicator.tsx +++ b/src/features/ui/components/modals/nostr-login-modal/components/nostr-extension-indicator.tsx @@ -12,7 +12,7 @@ const NostrExtensionIndicator: React.FC = () => { const onClick = () => { dispatch(nostrExtensionLogIn()); - dispatch(closeModal('NOSTR_SIGNIN')); + dispatch(closeModal('NOSTR_SIGNUP')); }; return ( diff --git a/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx b/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx index d3aae0a8a..5101cec1c 100644 --- a/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx +++ b/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx @@ -12,7 +12,7 @@ interface INostrLoginModal { const NostrLoginModal: React.FC = ({ onClose }) => { const [step, setStep] = useState(window.nostr ? 'extension' : 'key-add'); - const handleClose = () => onClose('NOSTR_SIGNIN'); + const handleClose = () => onClose('NOSTR_SIGNUP'); switch (step) { case 'extension': diff --git a/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx index e155ab06b..8144a47cd 100644 --- a/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx +++ b/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx @@ -24,17 +24,17 @@ const ExtensionStep: React.FC = ({ setStep, onClose }) => { const onClickAlt = () => setStep('key-add'); return ( - } onClose={onClose}> + } onClose={onClose}> diff --git a/src/features/ui/components/modals/nostr-login-modal/steps/key-add-step.tsx b/src/features/ui/components/modals/nostr-login-modal/steps/key-add-step.tsx index 06ab0c2c5..d1a8c2ac3 100644 --- a/src/features/ui/components/modals/nostr-login-modal/steps/key-add-step.tsx +++ b/src/features/ui/components/modals/nostr-login-modal/steps/key-add-step.tsx @@ -35,7 +35,7 @@ const KeyAddStep: React.FC = ({ onClose }) => { }; return ( - } onClose={onClose}> + } onClose={onClose}> diff --git a/src/features/ui/components/modals/nostr-signin-modal/components/emoji-graphic.tsx b/src/features/ui/components/modals/nostr-signup-modal/components/emoji-graphic.tsx similarity index 100% rename from src/features/ui/components/modals/nostr-signin-modal/components/emoji-graphic.tsx rename to src/features/ui/components/modals/nostr-signup-modal/components/emoji-graphic.tsx diff --git a/src/features/ui/components/modals/nostr-signin-modal/components/nostr-extension-indicator.tsx b/src/features/ui/components/modals/nostr-signup-modal/components/nostr-extension-indicator.tsx similarity index 93% rename from src/features/ui/components/modals/nostr-signin-modal/components/nostr-extension-indicator.tsx rename to src/features/ui/components/modals/nostr-signup-modal/components/nostr-extension-indicator.tsx index 78dddd33d..38493efe6 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/components/nostr-extension-indicator.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/components/nostr-extension-indicator.tsx @@ -12,7 +12,7 @@ const NostrExtensionIndicator: React.FC = () => { const onClick = () => { dispatch(nostrExtensionLogIn()); - dispatch(closeModal('NOSTR_SIGNIN')); + dispatch(closeModal('NOSTR_SIGNUP')); }; return ( diff --git a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx b/src/features/ui/components/modals/nostr-signup-modal/nostr-signup-modal.tsx similarity index 82% rename from src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx rename to src/features/ui/components/modals/nostr-signup-modal/nostr-signup-modal.tsx index 6865e56ec..18f63ba10 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/nostr-signup-modal.tsx @@ -6,14 +6,14 @@ import KeygenStep from './steps/keygen-step'; type Step = 'extension' | 'key' | 'keygen'; -interface INostrSigninModal { +interface INostrSignupModal { onClose: (type?: string) => void; } -const NostrSigninModal: React.FC = ({ onClose }) => { +const NostrSigninModal: React.FC = ({ onClose }) => { const [step, setStep] = useState(window.nostr ? 'extension' : 'key'); - const handleClose = () => onClose('NOSTR_SIGNIN'); + const handleClose = () => onClose('NOSTR_SIGNUP'); switch (step) { case 'extension': diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-signup-modal/steps/extension-step.tsx similarity index 82% rename from src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx rename to src/features/ui/components/modals/nostr-signup-modal/steps/extension-step.tsx index 42bfb4d04..d1f7e9ab8 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/extension-step.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/steps/extension-step.tsx @@ -6,7 +6,7 @@ import { Button, Stack, Modal } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; import EmojiGraphic from '../components/emoji-graphic'; -import { Step } from '../nostr-signin-modal'; +import { Step } from '../nostr-signup-modal'; interface IExtensionStep { setStep: (step: Step) => void; @@ -24,17 +24,17 @@ const ExtensionStep: React.FC = ({ setStep, onClose }) => { const onClickAlt = () => setStep('key'); return ( - } onClose={onClose}> + } onClose={onClose}> diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx b/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx similarity index 91% rename from src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx rename to src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx index 73a71e501..f4631adb7 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/key-step.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx @@ -7,7 +7,7 @@ import { useAppDispatch } from 'soapbox/hooks'; import EmojiGraphic from '../components/emoji-graphic'; import NostrExtensionIndicator from '../components/nostr-extension-indicator'; -import { Step } from '../nostr-signin-modal'; +import { Step } from '../nostr-signup-modal'; interface IKeyStep { setStep(step: Step): void; @@ -23,7 +23,7 @@ const KeyStep: React.FC = ({ setStep, onClose }) => { }; return ( - } onClose={onClose}> + } onClose={onClose}> diff --git a/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx b/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx similarity index 97% rename from src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx rename to src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx index b024b2356..8c472aea8 100644 --- a/src/features/ui/components/modals/nostr-signin-modal/steps/keygen-step.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx @@ -46,7 +46,7 @@ const KeygenStep: React.FC = ({ onClose }) => { }; return ( - } onClose={onClose}> + } onClose={onClose}> diff --git a/src/features/ui/components/panels/sign-up-panel.tsx b/src/features/ui/components/panels/sign-up-panel.tsx index 059fa9d19..203c84987 100644 --- a/src/features/ui/components/panels/sign-up-panel.tsx +++ b/src/features/ui/components/panels/sign-up-panel.tsx @@ -25,7 +25,7 @@ const SignUpPanel = () => { - diff --git a/src/features/ui/util/async-components.ts b/src/features/ui/util/async-components.ts index 26e86a1ce..1364a89f2 100644 --- a/src/features/ui/util/async-components.ts +++ b/src/features/ui/util/async-components.ts @@ -162,5 +162,5 @@ export const EditAnnouncementModal = lazy(() => import('soapbox/features/ui/comp export const FollowedTags = lazy(() => import('soapbox/features/followed-tags')); export const AccountNotePanel = lazy(() => import('soapbox/features/ui/components/panels/account-note-panel')); export const ComposeEditor = lazy(() => import('soapbox/features/compose/editor')); -export const NostrSigninModal = lazy(() => import('soapbox/features/ui/components/modals/nostr-signin-modal/nostr-signin-modal')); +export const NostrSignupModal = lazy(() => import('soapbox/features/ui/components/modals/nostr-signup-modal/nostr-signup-modal')); export const NostrLoginModal = lazy(() => import('soapbox/features/ui/components/modals/nostr-login-modal/nostr-login-modal')); From b94ea6cd42c296d028d56c337de7a145f6b1b153 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 16 Mar 2024 16:08:23 -0500 Subject: [PATCH 49/60] Move EmojiGraphic to soapbox/components --- .../components/emoji-graphic.tsx | 0 .../steps/extension-step.tsx | 2 +- .../nostr-login-modal/steps/key-add-step.tsx | 2 +- .../components/emoji-graphic.tsx | 20 ------------------- .../steps/extension-step.tsx | 2 +- .../nostr-signup-modal/steps/key-step.tsx | 2 +- .../nostr-signup-modal/steps/keygen-step.tsx | 3 +-- 7 files changed, 5 insertions(+), 26 deletions(-) rename src/{features/ui/components/modals/nostr-login-modal => }/components/emoji-graphic.tsx (100%) delete mode 100644 src/features/ui/components/modals/nostr-signup-modal/components/emoji-graphic.tsx diff --git a/src/features/ui/components/modals/nostr-login-modal/components/emoji-graphic.tsx b/src/components/emoji-graphic.tsx similarity index 100% rename from src/features/ui/components/modals/nostr-login-modal/components/emoji-graphic.tsx rename to src/components/emoji-graphic.tsx diff --git a/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx index 8144a47cd..da9406105 100644 --- a/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx +++ b/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx @@ -2,10 +2,10 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import { nostrExtensionLogIn } from 'soapbox/actions/nostr'; +import EmojiGraphic from 'soapbox/components/emoji-graphic'; import { Button, Stack, Modal } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; -import EmojiGraphic from '../components/emoji-graphic'; import { Step } from '../nostr-login-modal'; interface IExtensionStep { diff --git a/src/features/ui/components/modals/nostr-login-modal/steps/key-add-step.tsx b/src/features/ui/components/modals/nostr-login-modal/steps/key-add-step.tsx index d1a8c2ac3..15b20dcb0 100644 --- a/src/features/ui/components/modals/nostr-login-modal/steps/key-add-step.tsx +++ b/src/features/ui/components/modals/nostr-login-modal/steps/key-add-step.tsx @@ -2,10 +2,10 @@ import { nip19 } from 'nostr-tools'; import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; +import EmojiGraphic from 'soapbox/components/emoji-graphic'; import { Button, Stack, Modal, Input, FormGroup, Form } from 'soapbox/components/ui'; import { NKeys } from 'soapbox/features/nostr/keys'; -import EmojiGraphic from '../components/emoji-graphic'; import NostrExtensionIndicator from '../components/nostr-extension-indicator'; interface IKeyAddStep { diff --git a/src/features/ui/components/modals/nostr-signup-modal/components/emoji-graphic.tsx b/src/features/ui/components/modals/nostr-signup-modal/components/emoji-graphic.tsx deleted file mode 100644 index 541920307..000000000 --- a/src/features/ui/components/modals/nostr-signup-modal/components/emoji-graphic.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; - -import Emoji from 'soapbox/components/ui/emoji/emoji'; - -interface IEmojiGraphic { - emoji: string; -} - -/** Large emoji with a background for display purposes (eg breaking up a page). */ -const EmojiGraphic: React.FC = ({ emoji }) => { - return ( -
-
- -
-
- ); -}; - -export default EmojiGraphic; \ No newline at end of file diff --git a/src/features/ui/components/modals/nostr-signup-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-signup-modal/steps/extension-step.tsx index d1f7e9ab8..94194d7f2 100644 --- a/src/features/ui/components/modals/nostr-signup-modal/steps/extension-step.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/steps/extension-step.tsx @@ -2,10 +2,10 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import { nostrExtensionLogIn } from 'soapbox/actions/nostr'; +import EmojiGraphic from 'soapbox/components/emoji-graphic'; import { Button, Stack, Modal } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; -import EmojiGraphic from '../components/emoji-graphic'; import { Step } from '../nostr-signup-modal'; interface IExtensionStep { diff --git a/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx b/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx index f4631adb7..e6e4d40e4 100644 --- a/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx @@ -2,10 +2,10 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import { openModal } from 'soapbox/actions/modals'; +import EmojiGraphic from 'soapbox/components/emoji-graphic'; import { Button, Stack, Modal } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; -import EmojiGraphic from '../components/emoji-graphic'; import NostrExtensionIndicator from '../components/nostr-extension-indicator'; import { Step } from '../nostr-signup-modal'; diff --git a/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx b/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx index 8c472aea8..c68fa7546 100644 --- a/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx @@ -4,14 +4,13 @@ import { FormattedMessage } from 'react-intl'; import { fetchAccount } from 'soapbox/actions/accounts'; import CopyableInput from 'soapbox/components/copyable-input'; +import EmojiGraphic from 'soapbox/components/emoji-graphic'; import { Button, Stack, Modal, FormGroup, Text, Tooltip } from 'soapbox/components/ui'; import { NKeys } from 'soapbox/features/nostr/keys'; import { useAppDispatch, useInstance } from 'soapbox/hooks'; import { download } from 'soapbox/utils/download'; import { slugify } from 'soapbox/utils/input'; -import EmojiGraphic from '../components/emoji-graphic'; - interface IKeygenStep { onClose(): void; } From 7adb67665348a3b7664b2591b7b31355631d720f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 16 Mar 2024 16:10:08 -0500 Subject: [PATCH 50/60] Have only one NostrExtensionIndicator component --- .../components/nostr-extension-indicator.tsx | 37 ------------------- .../nostr-signup-modal/steps/key-step.tsx | 2 +- 2 files changed, 1 insertion(+), 38 deletions(-) delete mode 100644 src/features/ui/components/modals/nostr-signup-modal/components/nostr-extension-indicator.tsx diff --git a/src/features/ui/components/modals/nostr-signup-modal/components/nostr-extension-indicator.tsx b/src/features/ui/components/modals/nostr-signup-modal/components/nostr-extension-indicator.tsx deleted file mode 100644 index 38493efe6..000000000 --- a/src/features/ui/components/modals/nostr-signup-modal/components/nostr-extension-indicator.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import { FormattedMessage } from 'react-intl'; - -import { closeModal } from 'soapbox/actions/modals'; -import { nostrExtensionLogIn } from 'soapbox/actions/nostr'; -import Stack from 'soapbox/components/ui/stack/stack'; -import Text from 'soapbox/components/ui/text/text'; -import { useAppDispatch } from 'soapbox/hooks'; - -const NostrExtensionIndicator: React.FC = () => { - const dispatch = useAppDispatch(); - - const onClick = () => { - dispatch(nostrExtensionLogIn()); - dispatch(closeModal('NOSTR_SIGNUP')); - }; - - return ( - - - {window.nostr ? ( - , - }} - /> - ) : ( - - )} - - - ); -}; - -export default NostrExtensionIndicator; \ No newline at end of file diff --git a/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx b/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx index e6e4d40e4..fdb783bad 100644 --- a/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx @@ -6,7 +6,7 @@ import EmojiGraphic from 'soapbox/components/emoji-graphic'; import { Button, Stack, Modal } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; -import NostrExtensionIndicator from '../components/nostr-extension-indicator'; +import NostrExtensionIndicator from '../../nostr-login-modal/components/nostr-extension-indicator'; import { Step } from '../nostr-signup-modal'; interface IKeyStep { From 463f260b2eeb7c443010a504e1a70c6bac35f89b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 16 Mar 2024 16:10:54 -0500 Subject: [PATCH 51/60] NostrLoginModal: fix handleClose --- .../components/modals/nostr-login-modal/nostr-login-modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx b/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx index 5101cec1c..4d5b4eb4c 100644 --- a/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx +++ b/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx @@ -12,7 +12,7 @@ interface INostrLoginModal { const NostrLoginModal: React.FC = ({ onClose }) => { const [step, setStep] = useState(window.nostr ? 'extension' : 'key-add'); - const handleClose = () => onClose('NOSTR_SIGNUP'); + const handleClose = () => onClose('NOSTR_LOGIN'); switch (step) { case 'extension': From 10a12905ffbfc285664e236473be21af65207f3f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 16 Mar 2024 16:25:26 -0500 Subject: [PATCH 52/60] NostrExtensionIndicator: close either modal on click --- .../nostr-login-modal/components/nostr-extension-indicator.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/ui/components/modals/nostr-login-modal/components/nostr-extension-indicator.tsx b/src/features/ui/components/modals/nostr-login-modal/components/nostr-extension-indicator.tsx index 38493efe6..2c0258663 100644 --- a/src/features/ui/components/modals/nostr-login-modal/components/nostr-extension-indicator.tsx +++ b/src/features/ui/components/modals/nostr-login-modal/components/nostr-extension-indicator.tsx @@ -12,7 +12,7 @@ const NostrExtensionIndicator: React.FC = () => { const onClick = () => { dispatch(nostrExtensionLogIn()); - dispatch(closeModal('NOSTR_SIGNUP')); + dispatch(closeModal()); }; return ( From 1417d46af51ecdc44360c6523c8703cfcb1024be Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 17 Mar 2024 15:29:58 -0500 Subject: [PATCH 53/60] Make NostrLogin modal flow work --- src/actions/nostr.ts | 2 +- .../nostr-login-modal/steps/key-add-step.tsx | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/actions/nostr.ts b/src/actions/nostr.ts index d82fa49f4..6908f06b6 100644 --- a/src/actions/nostr.ts +++ b/src/actions/nostr.ts @@ -23,4 +23,4 @@ function nostrExtensionLogIn() { }; } -export { nostrExtensionLogIn }; \ No newline at end of file +export { logInNostr, nostrExtensionLogIn }; \ No newline at end of file diff --git a/src/features/ui/components/modals/nostr-login-modal/steps/key-add-step.tsx b/src/features/ui/components/modals/nostr-login-modal/steps/key-add-step.tsx index 15b20dcb0..9153d4966 100644 --- a/src/features/ui/components/modals/nostr-login-modal/steps/key-add-step.tsx +++ b/src/features/ui/components/modals/nostr-login-modal/steps/key-add-step.tsx @@ -2,9 +2,11 @@ import { nip19 } from 'nostr-tools'; import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; +import { logInNostr } from 'soapbox/actions/nostr'; import EmojiGraphic from 'soapbox/components/emoji-graphic'; import { Button, Stack, Modal, Input, FormGroup, Form } from 'soapbox/components/ui'; import { NKeys } from 'soapbox/features/nostr/keys'; +import { useAppDispatch } from 'soapbox/hooks'; import NostrExtensionIndicator from '../components/nostr-extension-indicator'; @@ -16,18 +18,22 @@ const KeyAddStep: React.FC = ({ onClose }) => { const [nsec, setNsec] = useState(''); const [error, setError] = useState(); + const dispatch = useAppDispatch(); + const handleChange = (e: React.ChangeEvent) => { setNsec(e.target.value); setError(undefined); }; - const handleSubmit = () => { + const handleSubmit = async () => { try { const result = nip19.decode(nsec); if (result.type === 'nsec') { const seckey = result.data; - NKeys.add(seckey); - // TODO: log in, close modal + const signer = NKeys.add(seckey); + const pubkey = await signer.getPublicKey(); + dispatch(logInNostr(pubkey)); + onClose(); } } catch (e) { setError('Invalid nsec'); @@ -52,7 +58,7 @@ const KeyAddStep: React.FC = ({ onClose }) => { /> -
From ab0f9b0a438ac96b32847f33746532d505db2df3 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 17 Mar 2024 15:35:39 -0500 Subject: [PATCH 54/60] KeyStep: fix modals, sort of --- .../ui/components/modals/nostr-signup-modal/steps/key-step.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx b/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx index fdb783bad..a95287f0d 100644 --- a/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx @@ -18,8 +18,8 @@ const KeyStep: React.FC = ({ setStep, onClose }) => { const dispatch = useAppDispatch(); const onAltClick = () => { - dispatch(openModal('NOSTR_LOGIN')); onClose(); + dispatch(openModal('NOSTR_LOGIN')); }; return ( From ddee1d9d4e632588f22516a41c83a8349a17cfc5 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 17 Mar 2024 17:49:41 -0500 Subject: [PATCH 55/60] KeygenStep: log the user in --- .../modals/nostr-signup-modal/steps/keygen-step.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx b/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx index c68fa7546..ab0c34d8a 100644 --- a/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx @@ -3,6 +3,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import { fetchAccount } from 'soapbox/actions/accounts'; +import { logInNostr } from 'soapbox/actions/nostr'; import CopyableInput from 'soapbox/components/copyable-input'; import EmojiGraphic from 'soapbox/components/emoji-graphic'; import { Button, Stack, Modal, FormGroup, Text, Tooltip } from 'soapbox/components/ui'; @@ -39,9 +40,11 @@ const KeygenStep: React.FC = ({ onClose }) => { const handleCopy = () => setDownloaded(true); - const handleNext = () => { - NKeys.add(secretKey); - // TODO: log in, close modal + const handleNext = async () => { + const signer = NKeys.add(secretKey); + const pubkey = await signer.getPublicKey(); + dispatch(logInNostr(pubkey)); + onClose(); }; return ( From e866b95282a277ef3c94aee488044fcecf6cc600 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 17 Mar 2024 18:00:32 -0500 Subject: [PATCH 56/60] Fix navigating between NostrSignup and NostrLogin --- .../modals/nostr-login-modal/nostr-login-modal.tsx | 5 +++-- .../components/modals/nostr-signup-modal/steps/key-step.tsx | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx b/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx index 4d5b4eb4c..b5c1c95d7 100644 --- a/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx +++ b/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx @@ -7,10 +7,11 @@ type Step = 'extension' | 'key-add'; interface INostrLoginModal { onClose: (type?: string) => void; + step?: Step; } -const NostrLoginModal: React.FC = ({ onClose }) => { - const [step, setStep] = useState(window.nostr ? 'extension' : 'key-add'); +const NostrLoginModal: React.FC = ({ onClose, step: firstStep }) => { + const [step, setStep] = useState(firstStep ?? (window.nostr ? 'extension' : 'key-add')); const handleClose = () => onClose('NOSTR_LOGIN'); diff --git a/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx b/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx index a95287f0d..817814dce 100644 --- a/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx @@ -19,7 +19,7 @@ const KeyStep: React.FC = ({ setStep, onClose }) => { const onAltClick = () => { onClose(); - dispatch(openModal('NOSTR_LOGIN')); + dispatch(openModal('NOSTR_LOGIN', { step: 'key-add' })); }; return ( From 6fb7c337a3b1c92dd038e293b3361018c6fd8134 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 17 Mar 2024 18:03:30 -0500 Subject: [PATCH 57/60] Fi nostr_signup accidentally capitalized i18n strings --- .../modals/nostr-login-modal/steps/extension-step.tsx | 6 +++--- .../modals/nostr-login-modal/steps/key-add-step.tsx | 2 +- .../modals/nostr-signup-modal/steps/extension-step.tsx | 6 +++--- .../components/modals/nostr-signup-modal/steps/key-step.tsx | 2 +- .../modals/nostr-signup-modal/steps/keygen-step.tsx | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx index da9406105..9f235634f 100644 --- a/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx +++ b/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx @@ -24,17 +24,17 @@ const ExtensionStep: React.FC = ({ setStep, onClose }) => { const onClickAlt = () => setStep('key-add'); return ( - } onClose={onClose}> + } onClose={onClose}> diff --git a/src/features/ui/components/modals/nostr-login-modal/steps/key-add-step.tsx b/src/features/ui/components/modals/nostr-login-modal/steps/key-add-step.tsx index 9153d4966..371efffe6 100644 --- a/src/features/ui/components/modals/nostr-login-modal/steps/key-add-step.tsx +++ b/src/features/ui/components/modals/nostr-login-modal/steps/key-add-step.tsx @@ -41,7 +41,7 @@ const KeyAddStep: React.FC = ({ onClose }) => { }; return ( - } onClose={onClose}> + } onClose={onClose}> diff --git a/src/features/ui/components/modals/nostr-signup-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-signup-modal/steps/extension-step.tsx index 94194d7f2..69419792f 100644 --- a/src/features/ui/components/modals/nostr-signup-modal/steps/extension-step.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/steps/extension-step.tsx @@ -24,17 +24,17 @@ const ExtensionStep: React.FC = ({ setStep, onClose }) => { const onClickAlt = () => setStep('key'); return ( - } onClose={onClose}> + } onClose={onClose}> diff --git a/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx b/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx index 817814dce..e66d71b69 100644 --- a/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/steps/key-step.tsx @@ -23,7 +23,7 @@ const KeyStep: React.FC = ({ setStep, onClose }) => { }; return ( - } onClose={onClose}> + } onClose={onClose}> diff --git a/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx b/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx index ab0c34d8a..4fe7869c2 100644 --- a/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx @@ -48,7 +48,7 @@ const KeygenStep: React.FC = ({ onClose }) => { }; return ( - } onClose={onClose}> + } onClose={onClose}> From 7cfbf22b272b75ea19ffdeb0c49991232aef0732 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 17 Mar 2024 18:06:27 -0500 Subject: [PATCH 58/60] ExtensionStep: combine into one component --- .../nostr-login-modal/nostr-login-modal.tsx | 2 +- .../steps/extension-step.tsx | 8 +--- .../nostr-signup-modal/nostr-signup-modal.tsx | 5 ++- .../steps/extension-step.tsx | 45 ------------------- 4 files changed, 6 insertions(+), 54 deletions(-) delete mode 100644 src/features/ui/components/modals/nostr-signup-modal/steps/extension-step.tsx diff --git a/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx b/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx index b5c1c95d7..fd81a6c9a 100644 --- a/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx +++ b/src/features/ui/components/modals/nostr-login-modal/nostr-login-modal.tsx @@ -17,7 +17,7 @@ const NostrLoginModal: React.FC = ({ onClose, step: firstStep switch (step) { case 'extension': - return ; + return setStep('key-add')} onClose={handleClose} />; case 'key-add': return ; default: diff --git a/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx index 9f235634f..2b1af1f1e 100644 --- a/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx +++ b/src/features/ui/components/modals/nostr-login-modal/steps/extension-step.tsx @@ -6,14 +6,12 @@ import EmojiGraphic from 'soapbox/components/emoji-graphic'; import { Button, Stack, Modal } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; -import { Step } from '../nostr-login-modal'; - interface IExtensionStep { - setStep: (step: Step) => void; + onClickAlt: () => void; onClose(): void; } -const ExtensionStep: React.FC = ({ setStep, onClose }) => { +const ExtensionStep: React.FC = ({ onClickAlt, onClose }) => { const dispatch = useAppDispatch(); const onClick = () => { @@ -21,8 +19,6 @@ const ExtensionStep: React.FC = ({ setStep, onClose }) => { onClose(); }; - const onClickAlt = () => setStep('key-add'); - return ( } onClose={onClose}> diff --git a/src/features/ui/components/modals/nostr-signup-modal/nostr-signup-modal.tsx b/src/features/ui/components/modals/nostr-signup-modal/nostr-signup-modal.tsx index 18f63ba10..a5d47acce 100644 --- a/src/features/ui/components/modals/nostr-signup-modal/nostr-signup-modal.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/nostr-signup-modal.tsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; -import ExtensionStep from './steps/extension-step'; +import ExtensionStep from '../nostr-login-modal/steps/extension-step'; + import KeyStep from './steps/key-step'; import KeygenStep from './steps/keygen-step'; @@ -17,7 +18,7 @@ const NostrSigninModal: React.FC = ({ onClose }) => { switch (step) { case 'extension': - return ; + return setStep('key')} onClose={handleClose} />; case 'key': return ; case 'keygen': diff --git a/src/features/ui/components/modals/nostr-signup-modal/steps/extension-step.tsx b/src/features/ui/components/modals/nostr-signup-modal/steps/extension-step.tsx deleted file mode 100644 index 69419792f..000000000 --- a/src/features/ui/components/modals/nostr-signup-modal/steps/extension-step.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import { FormattedMessage } from 'react-intl'; - -import { nostrExtensionLogIn } from 'soapbox/actions/nostr'; -import EmojiGraphic from 'soapbox/components/emoji-graphic'; -import { Button, Stack, Modal } from 'soapbox/components/ui'; -import { useAppDispatch } from 'soapbox/hooks'; - -import { Step } from '../nostr-signup-modal'; - -interface IExtensionStep { - setStep: (step: Step) => void; - onClose(): void; -} - -const ExtensionStep: React.FC = ({ setStep, onClose }) => { - const dispatch = useAppDispatch(); - - const onClick = () => { - dispatch(nostrExtensionLogIn()); - onClose(); - }; - - const onClickAlt = () => setStep('key'); - - return ( - } onClose={onClose}> - - - - - - - - - - - ); -}; - -export default ExtensionStep; From e131467af97b95f89741ea896fb7df64b085eb8c Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 18 Mar 2024 13:44:34 -0500 Subject: [PATCH 59/60] Fix up login/signing paths and buttons --- src/features/auth-login/components/login-page.tsx | 10 ++++++++-- .../auth-login/components/registration-page.tsx | 11 ++++++++++- src/features/ui/components/panels/sign-up-panel.tsx | 10 ++++++++-- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/features/auth-login/components/login-page.tsx b/src/features/auth-login/components/login-page.tsx index 8817b6d39..9ceec4a55 100644 --- a/src/features/auth-login/components/login-page.tsx +++ b/src/features/auth-login/components/login-page.tsx @@ -4,9 +4,9 @@ import { Redirect } from 'react-router-dom'; import { logIn, verifyCredentials, switchAccount } from 'soapbox/actions/auth'; import { fetchInstance } from 'soapbox/actions/instance'; -import { closeModal } from 'soapbox/actions/modals'; +import { closeModal, openModal } from 'soapbox/actions/modals'; import { BigCard } from 'soapbox/components/big-card'; -import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; import { getRedirectUrl } from 'soapbox/utils/redirect'; import { isStandalone } from 'soapbox/utils/state'; @@ -21,6 +21,7 @@ const LoginPage = () => { const me = useAppSelector((state) => state.me); const standalone = useAppSelector((state) => isStandalone(state)); + const { nostrSignup } = useFeatures(); const token = new URLSearchParams(window.location.search).get('token'); @@ -62,6 +63,11 @@ const LoginPage = () => { event.preventDefault(); }; + if (nostrSignup) { + setTimeout(() => dispatch(openModal('NOSTR_LOGIN')), 100); + return ; + } + if (standalone) return ; if (shouldRedirect) { diff --git a/src/features/auth-login/components/registration-page.tsx b/src/features/auth-login/components/registration-page.tsx index db5f53a05..589fbeed2 100644 --- a/src/features/auth-login/components/registration-page.tsx +++ b/src/features/auth-login/components/registration-page.tsx @@ -1,15 +1,24 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import { Redirect } from 'react-router-dom'; +import { openModal } from 'soapbox/actions/modals'; import { BigCard } from 'soapbox/components/big-card'; import { Text } from 'soapbox/components/ui'; -import { useInstance, useRegistrationStatus } from 'soapbox/hooks'; +import { useAppDispatch, useFeatures, useInstance, useRegistrationStatus } from 'soapbox/hooks'; import RegistrationForm from './registration-form'; const RegistrationPage: React.FC = () => { const instance = useInstance(); const { isOpen } = useRegistrationStatus(); + const { nostrSignup } = useFeatures(); + const dispatch = useAppDispatch(); + + if (nostrSignup) { + setTimeout(() => dispatch(openModal('NOSTR_SIGNUP')), 100); + return ; + } if (!isOpen) { return ( diff --git a/src/features/ui/components/panels/sign-up-panel.tsx b/src/features/ui/components/panels/sign-up-panel.tsx index 203c84987..eaf30b41c 100644 --- a/src/features/ui/components/panels/sign-up-panel.tsx +++ b/src/features/ui/components/panels/sign-up-panel.tsx @@ -3,10 +3,11 @@ import { FormattedMessage } from 'react-intl'; import { openModal } from 'soapbox/actions/modals'; import { Button, Stack, Text } from 'soapbox/components/ui'; -import { useAppDispatch, useAppSelector, useInstance, useRegistrationStatus } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector, useFeatures, useInstance, useRegistrationStatus } from 'soapbox/hooks'; const SignUpPanel = () => { const instance = useInstance(); + const { nostrSignup } = useFeatures(); const { isOpen } = useRegistrationStatus(); const me = useAppSelector((state) => state.me); const dispatch = useAppDispatch(); @@ -25,7 +26,12 @@ const SignUpPanel = () => { - From 262580811d6105af7a3eeb2c4527c2818f36f319 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 18 Mar 2024 13:45:34 -0500 Subject: [PATCH 60/60] yarn i18n --- src/locales/en.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/locales/en.json b/src/locales/en.json index ec2fd06a0..6739b3bc7 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1066,6 +1066,14 @@ "new_group_panel.action": "Create Group", "new_group_panel.subtitle": "Can't find what you're looking for? Start your own private or public group.", "new_group_panel.title": "Create Group", + "nostr_extension.found": "Sign in with browser extension.", + "nostr_extension.not_found": "Browser extension not found.", + "nostr_signup.key-add.title": "Import Key", + "nostr_signup.key.title": "You need a key to continue", + "nostr_signup.keygen.title": "Your new key", + "nostr_signup.siwe.action": "Sign in with extension", + "nostr_signup.siwe.alt": "Sign in with key", + "nostr_signup.siwe.title": "Sign in", "notification.favourite": "{name} liked your post", "notification.follow": "{name} followed you", "notification.follow_request": "{name} has requested to follow you",