Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix modals to use native form submit #75

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 29 additions & 31 deletions src/components/CreatePinModal/CreatePinModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { useIntl } from '@cookbook/solid-intl';
import { Component, createEffect, createSignal } from 'solid-js';
import Modal from '../Modal/Modal';

import { login as tLogin, pin as tPin, actions as tActions } from '../../translations';
import {
login as tLogin,
pin as tPin,
actions as tActions,
} from '../../translations';

import styles from './CreatePinModal.module.scss';
import { hookForDev } from '../../lib/devTools';
Expand All @@ -12,13 +16,12 @@ import ButtonSecondary from '../Buttons/ButtonSecondary';
import { encryptWithPin, setCurrentPin } from '../../lib/PrimalNostr';

const CreatePinModal: Component<{
id?: string,
open?: boolean,
valueToEncrypt?: string,
onPinApplied?: (encryptedValue: string) => void,
onAbort?: () => void,
id?: string;
open?: boolean;
valueToEncrypt?: string;
onPinApplied?: (encryptedValue: string) => void;
onAbort?: () => void;
}> = (props) => {

const intl = useIntl();

let pinInput: HTMLInputElement | undefined;
Expand All @@ -32,7 +35,7 @@ const CreatePinModal: Component<{
return enc;
};

const onSetPin = async() => {
const onSetPin = async () => {
if (!isValidPin || !isValidRePin()) return;

// Encrypt private key
Expand All @@ -57,29 +60,27 @@ const CreatePinModal: Component<{

const isValidPin = () => {
return pin().length > 3;
}
};

const isValidRePin = () => {
return rePin() === pin();
};

const onKeyUp = (e: KeyboardEvent) => {
if (e.code === 'Enter' && isValidPin() && isValidRePin()) {
const onSubmit = (e: Event) => {
e.preventDefault();

if (isValidPin() && isValidRePin()) {
onSetPin();
}
};



return (
<Modal open={props.open} onClose={props.onAbort}>
<div id={props.id} class={styles.modal}>
<form id={props.id} class={styles.modal} onSubmit={onSubmit}>
<button class={styles.xClose} onClick={props.onAbort}>
<div class={styles.iconClose}></div>
</button>
<div class={styles.title}>
{intl.formatMessage(tPin.title)}
</div>
<div class={styles.title}>{intl.formatMessage(tPin.title)}</div>
<div class={styles.description}>
{intl.formatMessage(tPin.description)}
</div>
Expand All @@ -88,41 +89,38 @@ const CreatePinModal: Component<{
type="password"
ref={pinInput}
value={pin()}
onKeyUp={onKeyUp}
onChange={(val: string) => setPin(val)}
validationState={pin().length === 0 || isValidPin() ? 'valid' : 'invalid'}
validationState={
pin().length === 0 || isValidPin() ? 'valid' : 'invalid'
}
errorMessage={intl.formatMessage(tPin.invalidPin)}
/>

<div class={styles.description}>
{intl.formatMessage(tPin.reEnter)}
</div>
<div class={styles.description}>{intl.formatMessage(tPin.reEnter)}</div>
<TextInput
type="password"
value={rePin()}
onKeyUp={onKeyUp}
onChange={(val: string) => setRePin(val)}
validationState={rePin().length === 0 || isValidRePin() ? 'valid' : 'invalid'}
validationState={
rePin().length === 0 || isValidRePin() ? 'valid' : 'invalid'
}
errorMessage={intl.formatMessage(tPin.invalidRePin)}
/>

<div class={styles.actions}>
<ButtonPrimary
onClick={onSetPin}
type="submit"
disabled={!isValidPin() || !isValidRePin()}
>
{intl.formatMessage(tActions.createPin)}
</ButtonPrimary>
<ButtonSecondary
onClick={onOptout}
light={true}
>
<ButtonSecondary onClick={onOptout} light={true}>
{intl.formatMessage(tActions.optoutPin)}
</ButtonSecondary>
</div>
</div>
</form>
</Modal>
);
}
};

export default hookForDev(CreatePinModal);
57 changes: 24 additions & 33 deletions src/components/EnterPinModal/EnterPinModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useToastContext } from '../Toaster/Toaster';

import { nip19 } from 'nostr-tools';


import { pin as tPin, actions as tActions } from '../../translations';

import styles from './EnterPinModal.module.scss';
Expand All @@ -18,14 +17,13 @@ import ButtonSecondary from '../Buttons/ButtonSecondary';
import { useAccountContext } from '../../contexts/AccountContext';

const EnterPinModal: Component<{
id?: string,
open?: boolean,
valueToDecrypt?: string,
onSuccess?: (decryptedValue: string) => void,
onAbort?: () => void,
onForgot?: () => void,
id?: string;
open?: boolean;
valueToDecrypt?: string;
onSuccess?: (decryptedValue: string) => void;
onAbort?: () => void;
onForgot?: () => void;
}> = (props) => {

const intl = useIntl();
const toast = useToastContext();
const account = useAccountContext();
Expand All @@ -40,7 +38,7 @@ const EnterPinModal: Component<{
return dec;
};

const onConfirm = async() => {
const onConfirm = async () => {
if (!isValidPin) return;

// Decrypt private key
Expand All @@ -50,19 +48,18 @@ const EnterPinModal: Component<{
const decoded = nip19.decode(enc);

if (decoded.type !== 'nsec' || !decoded.data) {
throw('invalid-nsec-decoded');
throw 'invalid-nsec-decoded';
}

// Save PIN for the session
setCurrentPin(pin());

// Execute callback
props.onSuccess && props.onSuccess(enc);
} catch(e) {
} catch (e) {
logError('Failed to decode nsec: ', e);
toast?.sendWarning('PIN is incorrect');
}

};

createEffect(() => {
Expand All @@ -73,54 +70,48 @@ const EnterPinModal: Component<{

const isValidPin = () => {
return pin().length > 3;
}
};

const onKeyUp = (e: KeyboardEvent) => {
if (e.code === 'Enter' && isValidPin()) {
const onSubmit = (e: Event) => {
e.preventDefault();

if (isValidPin()) {
onConfirm();
}
};

return (
<Modal open={props.open} opaqueBackdrop={true}>
<div id={props.id} class={styles.modal}>
<form id={props.id} class={styles.modal} onSubmit={onSubmit}>
<button class={styles.xClose} onClick={props.onAbort}>
<div class={styles.iconClose}></div>
</button>
<div class={styles.title}>
{intl.formatMessage(tPin.enterTitle)}
</div>
<div class={styles.description}>
{intl.formatMessage(tPin.enter)}
</div>
<div class={styles.title}>{intl.formatMessage(tPin.enterTitle)}</div>
<div class={styles.description}>{intl.formatMessage(tPin.enter)}</div>
<div class={styles.inputs}>
<TextInput
type="password"
ref={pinInput}
value={pin()}
onKeyUp={onKeyUp}
onChange={(val: string) => setPin(val)}
validationState={pin().length === 0 || isValidPin() ? 'valid' : 'invalid'}
validationState={
pin().length === 0 || isValidPin() ? 'valid' : 'invalid'
}
errorMessage={intl.formatMessage(tPin.invalidRePin)}
/>
</div>
<div class={styles.actions}>
<ButtonPrimary
onClick={onConfirm}
disabled={!isValidPin()}
>
<ButtonPrimary type="submit" disabled={!isValidPin()}>
{intl.formatMessage(tActions.login)}
</ButtonPrimary>

<ButtonSecondary
onClick={props.onForgot}
>
<ButtonSecondary onClick={props.onForgot}>
{intl.formatMessage(tActions.forgotPin)}
</ButtonSecondary>
</div>
</div>
</form>
</Modal>
);
}
};

export default hookForDev(EnterPinModal);
42 changes: 22 additions & 20 deletions src/components/LoginModal/LoginModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@ import { nip19 } from 'nostr-tools';
import { storeSec } from '../../lib/localStore';

const LoginModal: Component<{
id?: string,
open?: boolean,
onAbort?: () => void,
id?: string;
open?: boolean;
onAbort?: () => void;
}> = (props) => {

const intl = useIntl();
const account = useAccountContext();

const [step, setStep] = createSignal<'login' | 'pin' | 'none'>('login')
const [step, setStep] = createSignal<'login' | 'pin' | 'none'>('login');
const [enteredKey, setEnteredKey] = createSignal('');

let loginInput: HTMLInputElement | undefined;
Expand All @@ -39,13 +38,13 @@ const LoginModal: Component<{
const onStoreSec = (sec: string | undefined) => {
storeSec(sec);
onAbort();
}
};

const onAbort = () => {
setStep(() => 'login');
setEnteredKey('');
props.onAbort && props.onAbort();
}
};

const isValidNsec: () => boolean = () => {
const key = enteredKey();
Expand All @@ -58,8 +57,8 @@ const LoginModal: Component<{
try {
const decoded = nip19.decode(key);

return decoded.type === 'nsec' && decoded.data;
} catch(e) {
return Boolean(decoded.type === 'nsec' && decoded.data);
} catch (e) {
return false;
}
}
Expand All @@ -73,8 +72,10 @@ const LoginModal: Component<{
}
});

const onKeyUp = (e: KeyboardEvent) => {
if (e.code === 'Enter' && isValidNsec()) {
const onSubmit = (e: Event) => {
e.preventDefault();

if (isValidNsec()) {
onLogin();
}
};
Expand All @@ -83,13 +84,11 @@ const LoginModal: Component<{
<Switch>
<Match when={step() === 'login'}>
<Modal open={props.open} onClose={onAbort}>
<div id={props.id} class={styles.modal}>
<form id={props.id} class={styles.modal} onSubmit={onSubmit}>
<button class={styles.xClose} onClick={onAbort}>
<div class={styles.iconClose}></div>
</button>
<div class={styles.title}>
{intl.formatMessage(tLogin.title)}
</div>
<div class={styles.title}>{intl.formatMessage(tLogin.title)}</div>
<div class={styles.description}>
{intl.formatMessage(tLogin.description)}
</div>
Expand All @@ -98,21 +97,24 @@ const LoginModal: Component<{
ref={loginInput}
type="password"
value={enteredKey()}
onKeyUp={onKeyUp}
onChange={setEnteredKey}
validationState={enteredKey().length === 0 || isValidNsec() ? 'valid' : 'invalid'}
validationState={
enteredKey().length === 0 || isValidNsec()
? 'valid'
: 'invalid'
}
errorMessage={intl.formatMessage(tLogin.invalidNsec)}
/>
</div>
<div class={styles.actions}>
<ButtonPrimary
onClick={onLogin}
type="submit"
disabled={enteredKey().length === 0 || !isValidNsec()}
>
{intl.formatMessage(tActions.login)}
</ButtonPrimary>
</div>
</div>
</form>
</Modal>
</Match>

Expand All @@ -128,6 +130,6 @@ const LoginModal: Component<{
</Match>
</Switch>
);
}
};

export default hookForDev(LoginModal);