add password entry form

This commit is contained in:
or-else
2018-09-30 21:28:28 +03:00
parent 871a1e5b5b
commit 114bfc409f
2 changed files with 165 additions and 83 deletions
+11 -1
View File
@@ -207,6 +207,12 @@ textarea::placeholder {
color: #bbb; color: #bbb;
} }
input[type="text"].with-visibility,
input[type="password"].with-visibility {
margin-right:-2rem;
padding-right:2rem;
}
/* Scrollbar styling for Chrome. Firefox does not support scrollbar styling. */ /* Scrollbar styling for Chrome. Firefox does not support scrollbar styling. */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 0.6rem; width: 0.6rem;
@@ -300,6 +306,10 @@ li.right:hover .menuTrigger {
color: #757575; color: #757575;
} }
.light-gray {
color: #bbb;
}
.small { .small {
font-size: 90%; font-size: 90%;
} }
@@ -415,7 +425,7 @@ li.right:hover .menuTrigger {
font-family: inherit; font-family: inherit;
font-size: 1.1rem; font-size: 1.1rem;
height: 2.5rem; height: 2.5rem;
line-height: 1.5; line-height: 2.0;
margin: 0.25rem 0.125rem; margin: 0.25rem 0.125rem;
width: 100%; width: 100%;
vertical-align: baseline; vertical-align: baseline;
+154 -82
View File
@@ -743,9 +743,9 @@ class InPlaceEdit extends React.Component {
super(props); super(props);
this.state = { this.state = {
active: props.state, active: props.active,
initialText: props.text || "", initialValue: props.value || "",
text: props.text || "" value: props.value || ""
}; };
this.handeTextChange = this.handeTextChange.bind(this); this.handeTextChange = this.handeTextChange.bind(this);
@@ -757,23 +757,23 @@ class InPlaceEdit extends React.Component {
static getDerivedStateFromProps(nextProps, prevState) { static getDerivedStateFromProps(nextProps, prevState) {
// If text has changed while in read mode, update text and discard changes. // If text has changed while in read mode, update text and discard changes.
// Ignore update if in edit mode. // Ignore update if in edit mode.
if (prevState.initialText != nextProps.text && !prevState.active) { if (prevState.initialValue != nextProps.value && !prevState.active) {
return { return {
initialText: nextProps.text || "", initialValue: nextProps.value || "",
text: nextProps.text || "" value: nextProps.value || ""
}; };
} }
return null; return null;
} }
handeTextChange(e) { handeTextChange(e) {
this.setState({text: e.target.value}); this.setState({value: e.target.value});
} }
handleKeyDown(e) { handleKeyDown(e) {
if (e.keyCode === 27) { if (e.keyCode === 27) {
// Escape pressed // Escape pressed
this.setState({text: this.props.text, active: false}); this.setState({value: this.props.value, active: false});
} else if (e.keyCode === 13) { } else if (e.keyCode === 13) {
// Enter pressed // Enter pressed
this.handleEditingFinished(); this.handleEditingFinished();
@@ -789,33 +789,36 @@ class InPlaceEdit extends React.Component {
handleEditingFinished() { handleEditingFinished() {
this.setState({active: false}); this.setState({active: false});
var text = this.state.text.trim(); let value = this.state.value.trim();
if ((text || this.props.text) && (text !== this.props.text)) { if ((value || this.props.value) && (value !== this.props.value)) {
this.props.onFinished(text); this.props.onFinished(value);
} }
} }
render() { render() {
var spanText = this.props.type === "password" ? if (!this.state.active) {
"••••••••" : this.state.text; var spanText = this.props.type == "password" ? "••••••••" : this.state.value;
var spanClass = "in-place-edit" + var spanClass = "in-place-edit" +
(this.props.readOnly ? " disabled" : ""); (this.props.readOnly ? " disabled" : "");
if (!spanText) { if (!spanText) {
spanText = this.props.placeholder; spanText = this.props.placeholder;
spanClass += " placeholder"; spanClass += " placeholder";
} }
if (spanText.length > 20) { if (spanText.length > 20) {
spanText = spanText.substring(0, 19) + "..."; spanText = spanText.substring(0, 19) + "...";
}
} }
return ( return (
this.state.active ? this.state.active ?
<input type={this.props.type || "text"} <input type={this.props.type || "text"}
value={this.state.text} value={this.state.value}
placeholder={this.props.placeholder} placeholder={this.props.placeholder}
required={this.props.required ? 'required' : ''}
autoComplete={this.props.autoComplete}
autoFocus={this.props.autoFocus ? 'autoFocus' : ''}
onChange={this.handeTextChange} onChange={this.handeTextChange}
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
onBlur={this.handleEditingFinished} onBlur={this.handleEditingFinished} />
autoFocus />
: :
<span className={spanClass} onClick={this.handleStartEditing}> <span className={spanClass} onClick={this.handleStartEditing}>
<span className="content">{spanText}</span> <span className="content">{spanText}</span>
@@ -823,6 +826,73 @@ class InPlaceEdit extends React.Component {
); );
} }
}; };
class VisiblePassword extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
value: this.props.value,
visible: false
};
this.handleVisibility = this.handleVisibility.bind(this);
this.handeTextChange = this.handeTextChange.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleEditingFinished = this.handleEditingFinished.bind(this);
}
handeTextChange(e) {
this.setState({value: e.target.value});
if (this.props.onChange) {
this.props.onChange(e);
}
}
handleVisibility(e) {
e.preventDefault();
this.setState({visible: !this.state.visible});
}
handleKeyDown(e) {
if (e.keyCode === 27) {
// Escape pressed
this.setState({value: this.props.value, active: false});
} else if (e.keyCode === 13) {
// Enter pressed
this.handleEditingFinished();
}
}
handleEditingFinished() {
if (this.props.onFinished) {
this.props.onFinished(this.state.value);
}
}
render() {
return (
<div>
<input className="with-visibility"
type={this.state.visible ? "text" : "password"}
value={this.state.value}
placeholder={this.props.placeholder}
required={this.props.required ? 'required' : ''}
autoFocus={this.props.autoFocus ? 'autoFocus' : ''}
autoComplete={this.props.autoComplete}
onChange={this.handeTextChange}
onKeyDown={this.handleKeyDown}
onBlur={this.handleEditingFinished} />
<span onClick={this.handleVisibility}>
<i className="material-icons clickable light-gray">
{this.state.visible ? 'visibility' : 'visibility_off'}
</i>
</span>
</div>
);
}
}
/* END InPlaceEdit */ /* END InPlaceEdit */
/* BEGIN Combobox for selecting host name */ /* BEGIN Combobox for selecting host name */
@@ -1261,12 +1331,12 @@ class LoginView extends React.Component {
value={this.state.login} value={this.state.login}
onChange={this.handleLoginChange} onChange={this.handleLoginChange}
required autoFocus /> required autoFocus />
<input type="password" id="inputPassword" <VisiblePassword type="password" id="inputPassword"
placeholder="Password" placeholder="Password"
autoComplete="current-password" autoComplete="current-password"
value={this.state.password} value={this.state.password}
onChange={this.handlePasswordChange} onChange={this.handlePasswordChange}
required /> required={true} />
<div className="panel-form-row"> <div className="panel-form-row">
<CheckBox id="save-token" name="save-token" checked={this.state.saveToken} <CheckBox id="save-token" name="save-token" checked={this.state.saveToken}
onChange={this.handleToggleSaveToken} /> onChange={this.handleToggleSaveToken} />
@@ -1290,7 +1360,6 @@ class CreateAccountView extends React.PureComponent {
this.state = { this.state = {
login: '', login: '',
password: '', password: '',
password2: '',
email: '', email: '',
fn: '', // full/formatted name fn: '', // full/formatted name
imageDataUrl: null, imageDataUrl: null,
@@ -1300,7 +1369,6 @@ class CreateAccountView extends React.PureComponent {
this.handleLoginChange = this.handleLoginChange.bind(this); this.handleLoginChange = this.handleLoginChange.bind(this);
this.handlePasswordChange = this.handlePasswordChange.bind(this); this.handlePasswordChange = this.handlePasswordChange.bind(this);
this.handlePassword2Change = this.handlePassword2Change.bind(this);
this.handleEmailChange = this.handleEmailChange.bind(this); this.handleEmailChange = this.handleEmailChange.bind(this);
this.handleFnChange = this.handleFnChange.bind(this); this.handleFnChange = this.handleFnChange.bind(this);
this.handleImageChanged = this.handleImageChanged.bind(this); this.handleImageChanged = this.handleImageChanged.bind(this);
@@ -1312,12 +1380,8 @@ class CreateAccountView extends React.PureComponent {
this.setState({login: e.target.value}); this.setState({login: e.target.value});
} }
handlePasswordChange(e) { handlePasswordChange(password) {
this.setState({password: e.target.value}); this.setState({password: password});
}
handlePassword2Change(e) {
this.setState({password2: e.target.value});
} }
handleEmailChange(e) { handleEmailChange(e) {
@@ -1339,17 +1403,13 @@ class CreateAccountView extends React.PureComponent {
handleSubmit(e) { handleSubmit(e) {
e.preventDefault(); e.preventDefault();
if (this.state.password.trim() != this.state.password2.trim()) { // TODO: check email for validity
this.props.onError("Passwords don't match", "warn"); this.setState({errorCleared: false});
} else { this.props.onCreateAccount(
// TODO: check email for validity this.state.login.trim(),
this.setState({errorCleared: false}); this.state.password.trim(),
this.props.onCreateAccount( vcard(this.state.fn, this.state.imageDataUrl),
this.state.login.trim(), {"meth": "email", "val": this.state.email});
this.state.password.trim(),
vcard(this.state.fn, this.state.imageDataUrl),
{"meth": "email", "val": this.state.email});
}
} }
render() { render() {
@@ -1363,23 +1423,22 @@ class CreateAccountView extends React.PureComponent {
<div className="panel-form-column"> <div className="panel-form-column">
<input type="text" placeholder="Login" autoComplete="user-name" <input type="text" placeholder="Login" autoComplete="user-name"
value={this.state.login} onChange={this.handleLoginChange} required autoFocus /> value={this.state.login} onChange={this.handleLoginChange} required autoFocus />
<input type="password" placeholder="Password" autoComplete="new-password" <VisiblePassword placeholder="Password" autoComplete="new-password"
value={this.state.password} onChange={this.handlePasswordChange} required /> value={this.state.password} onFinished={this.handlePasswordChange}
<input type="password" placeholder="Repeat password" autoComplete="new-password" required={true} />
value={this.state.password2} onChange={this.handlePassword2Change} required />
</div> </div>
<AvatarUpload <AvatarUpload
onImageChanged={this.handleImageChanged} onImageChanged={this.handleImageChanged}
onError={this.props.onError} /> onError={this.props.onError} />
</div> </div>
<div className="panel-form-row">
<input type="email" placeholder="Email, e.g john.doe@example.com"
autoComplete="email" value={this.state.email} onChange={this.handleEmailChange} required/>
</div>
<div className="panel-form-row"> <div className="panel-form-row">
<input type="text" placeholder="Full name, e.g. John Doe" autoComplete="name" <input type="text" placeholder="Full name, e.g. John Doe" autoComplete="name"
value={this.state.fn} onChange={this.handleFnChange} required/> value={this.state.fn} onChange={this.handleFnChange} required/>
</div> </div>
<div className="panel-form-row">
<input type="email" placeholder="Email, e.g john.doe@example.com"
autoComplete="email" value={this.state.email} onChange={this.handleEmailChange} required/>
</div>
<div className="panel-form-row"> <div className="panel-form-row">
<CheckBox id="save-token" name="save-token" checked={this.state.saveToken} <CheckBox id="save-token" name="save-token" checked={this.state.saveToken}
onChange={this.handleToggleSaveToken} /> onChange={this.handleToggleSaveToken} />
@@ -1467,6 +1526,7 @@ class PasswordResetView extends React.PureComponent {
this.state = { this.state = {
email: '', email: '',
password: ''
}; };
this.handleSubmit = this.handleSubmit.bind(this); this.handleSubmit = this.handleSubmit.bind(this);
@@ -1475,32 +1535,50 @@ class PasswordResetView extends React.PureComponent {
handleSubmit(e) { handleSubmit(e) {
e.preventDefault(); e.preventDefault();
this.props.onSubmit("email", this.state.email.trim()); if (this.props.token) {
this.props.onSubmit(null, this.state.password.trim());
} else {
this.props.onSubmit("email", this.state.email.trim());
}
} }
handleEmailChange(e) { handleEmailChange(e) {
this.setState({email: e.target.value}); this.setState({email: e.target.value});
} }
handlePasswordChange(e) {
this.setState({password: e.target.value});
}
render() { render() {
let buttonText = this.props.token ? "Reset" : "Send request";
return ( return (
<form id="password-reset-form" onSubmit={this.handleSubmit}> <form id="password-reset-form" onSubmit={this.handleSubmit}>
<p>Email with instructions for resetting your password will be sent {!this.props.token ?
to the following email address:</p> <React.Fragment>
<input type="email" id="inputEmail" <label htmlFor="inputEmail">Send a password reset email:</label>
placeholder="Your registration email" <input type="email" id="inputEmail"
autoComplete="email" placeholder="Your registration email"
value={this.state.email} autoComplete="email"
onChange={this.handleEmailChange} value={this.state.email}
required autoFocus /> onChange={this.handleEmailChange}
required autoFocus />
</React.Fragment>
:
<VisiblePassword
placeholder="Enter new password"
autoComplete="new-password"
value={this.state.password}
required={true} autoFocus={true}
onChange={this.handlePasswordChange} />
}
<div className="dialog-buttons"> <div className="dialog-buttons">
<button className="blue" type="submit">Send request</button> <button className="blue" type="submit">{buttonText}</button>
</div> </div>
</form> </form>
); );
} }
} }
/* END PasswordResetView */ /* END PasswordResetView */
@@ -1891,7 +1969,7 @@ class EditAccountView extends React.Component {
<div><label className="small">Your name</label></div> <div><label className="small">Your name</label></div>
<div><InPlaceEdit <div><InPlaceEdit
placeholder="Full name, e.g. John Doe" placeholder="Full name, e.g. John Doe"
text={this.state.fullName} value={this.state.fullName}
onFinished={this.handleFullNameUpdate} /></div> onFinished={this.handleFullNameUpdate} /></div>
<div><label className="small">Password</label></div> <div><label className="small">Password</label></div>
<div><InPlaceEdit <div><InPlaceEdit
@@ -3213,12 +3291,12 @@ class InfoView extends React.Component {
<div><InPlaceEdit <div><InPlaceEdit
placeholder="Group name" placeholder="Group name"
readOnly={!this.state.owner} readOnly={!this.state.owner}
text={this.state.fullName} value={this.state.fullName}
onFinished={this.handleFullNameUpdate} /></div> onFinished={this.handleFullNameUpdate} /></div>
<div><label className="small">Private comment</label></div> <div><label className="small">Private comment</label></div>
<div><InPlaceEdit <div><InPlaceEdit
placeholder="Visible to you only" placeholder="Visible to you only"
text={this.state.private} value={this.state.private}
onFinished={this.handlePrivateUpdate} /></div> onFinished={this.handlePrivateUpdate} /></div>
</div> </div>
<AvatarUpload <AvatarUpload
@@ -5284,21 +5362,15 @@ class TinodeWeb extends React.Component {
} }
handlePasswordResetRequest(method, value) { handlePasswordResetRequest(method, value) {
if (this.tinode.isConnected()) { // If already connected, connnect() will return a resolved promise.
this.tinode.resetAuthSecret("basic", method, value) this.tinode.connect()
.catch((err) => { .then(() => {
this.handleError(err.message, "err"); this.tinode.requestResetAuthSecret("basic", method, value)
}); })
} else { .catch((err) => {
this.tinode.connect() // Socket error
.then(() => { this.handleError(err.message, "err");
this.tinode.resetAuthSecret("basic", method, value) });
})
.catch((err) => {
// Socket error
this.handleError(err.message, "err");
});
}
} }
render() { render() {