revamped registration infrastructure (#143)

This commit is contained in:
Michael Quigley 2023-01-03 17:33:00 -05:00
parent 63f2049c2c
commit 0033021139
No known key found for this signature in database
GPG Key ID: 9B60314A9DD20A62
7 changed files with 175 additions and 109 deletions

View File

@ -21,7 +21,7 @@
-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="bootstrap.min.css">
<link rel="stylesheet" href="/bootstrap.min.css">
<link href="https://fonts.googleapis.com/css2?family=Russo+One&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap" rel="stylesheet">
<title>zrok</title>

View File

@ -50,7 +50,7 @@ const Login = (props) => {
</Row>
<Row>
<Form onSubmit={handleSubmit}>
<Form.Group controlId={"email"} >
<Form.Group controlId={"email"}>
<Form.Control
type={"email"}
placeholder={"Email Address"}
@ -71,9 +71,11 @@ const Login = (props) => {
<Button variant={"light"} type={"submit"}>Log In</Button>
</Form>
</Row>
<Row>
{message}
</Row>
</Container>
</Row>
</Container>
</div>
)

View File

@ -68,6 +68,7 @@ code, pre {
color: white;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}
@ -77,7 +78,7 @@ code, pre {
}
.fullscreen h1 {
font-size: 96pt;
font-size: 64pt;
}
.fullscreen-body {
@ -93,6 +94,14 @@ code, pre {
width: 400px;
}
.fullscreen a {
color: gray;
}
.fullscreen pre {
color: white;
}
.errorMessage {
color: deeppink;
}

View File

@ -0,0 +1,16 @@
import {Button, Container, Row} from "react-bootstrap";
const InvalidRequest = () => {
return (
<Container fluid>
<Row>
<img alt="ziggy" src={"/ziggy.svg"} width={200}/>
</Row>
<Row>
<h1>Invitation not found!</h1>
</Row>
</Container>
);
};
export default InvalidRequest;

View File

@ -1,105 +1,8 @@
import Icon from "@mdi/react";
import { useParams } from 'react-router-dom';
import {useEffect, useState} from "react";
import {mdiContentCopy} from "@mdi/js";
import * as account from "../api/account";
const RegistrationForm = (props) => {
const [password, setPassword] = useState('');
const [confirm, setConfirm] = useState('');
const [message, setMessage] = useState();
const [authToken, setAuthToken] = useState('');
const [complete, setComplete] = useState(false);
const passwordMismatchMessage = <h2 className={"errorMessage"}>Entered passwords do not match!</h2>
const registerFailed = <h2 className={"errorMessage"}>Account creation failed!</h2>
const handleSubmit = async e => {
e.preventDefault();
console.log("handleSubmit");
if(confirm !== password) {
setMessage(passwordMismatchMessage);
} else {
account.register({body: {"token": props.token, "password": password}})
.then(resp => {
if(!resp.error) {
console.log("resp", resp)
setMessage(undefined);
setAuthToken(resp.data.token);
setComplete(true);
} else {
setMessage(registerFailed);
}
})
.catch(resp => {
console.log("resp", resp);
setMessage(registerFailed);
});
}
};
if(!complete) {
return (
<div className={"fullscreen"}>
<img src={"/ziggy.svg"} width={200}/>
<h1>A new zrok user!</h1>
<h2>{props.email}</h2>
<form onSubmit={handleSubmit}>
<fieldset>
<legend>Set A Password</legend>
<p><label htmlFor={"password"}>password: </label><input type={"password"} value={password} placeholder={"Password"} onChange={({target}) => setPassword(target.value)}/></p>
<p>
<label htmlFor={"confirm"}>confirm: </label><input type={"password"} value={confirm} placeholder={"Confirm Password"} onChange={({target}) => setConfirm(target.value)}/>
<button type={"submit"}>Register</button>
</p>
</fieldset>
</form>
{message}
</div>
)
} else {
return <Success email={props.email} token={authToken}/>
}
}
const NoAccountRequest = () => {
return (
<div className={"fullscreen"}>
<img src={"/ziggy.svg"} width={200}/>
<h1>No such account request!</h1>
</div>
)
}
const Success = (props) => {
const handleCopy = async () => {
let copiedText = document.getElementById("zrok-enable-command").innerHTML;
try {
await navigator.clipboard.writeText(copiedText);
console.log("copied enable command");
} catch(err) {
console.error("failed to copy", err);
}
}
// clear local storage on new account registration success.
localStorage.clear();
return (
<div className={"fullscreen"}>
<img src={"/ziggy.svg"} width={200}/>
<h1>Welcome to zrok!</h1>
<p>You can proceed to the <a href={"/"}>zrok web portal</a> and log in with your email and password.</p>
<p>To enable your shell for zrok, use this command:</p>
<pre>
$ <span id={"zrok-enable-command"}>zrok enable {props.token}</span> <Icon path={mdiContentCopy} size={0.7} onClick={handleCopy}/>
</pre>
</div>
)
}
import InvalidRequest from "./InvalidRequest";
import SetPasswordForm from "./SetPasswordForm";
let step;
@ -130,16 +33,13 @@ const Register = () => {
}, []);
if(activeRequest) {
step = <RegistrationForm
email={email}
token={token}
/>
step = <SetPasswordForm email={email} token={token}/>
} else {
step = <NoAccountRequest />
step = <InvalidRequest />
}
return (
<div>{step}</div>
<div className={"fullscreen"}>{step}</div>
)
}

View File

@ -0,0 +1,92 @@
import React, {useState} from "react";
import * as account from "../api/account";
import Success from "./Success";
import {Button, Container, Form, Row} from "react-bootstrap";
const SetPasswordForm = (props) => {
const [password, setPassword] = useState('');
const [confirm, setConfirm] = useState('');
const [message, setMessage] = useState();
const [authToken, setAuthToken] = useState('');
const [complete, setComplete] = useState(false);
const passwordMismatchMessage = <h2 className={"errorMessage"}>Entered passwords do not match!</h2>
const passwordTooShortMessage = <h2 className={"errorMessage"}>Entered password too short! (4 characters, minimum)</h2>
const registerFailed = <h2 className={"errorMessage"}>Account creation failed!</h2>
const handleSubmit = async e => {
e.preventDefault();
if(confirm.length < 4) {
setMessage(passwordTooShortMessage);
return;
}
if(confirm !== password) {
setMessage(passwordMismatchMessage);
return;
}
account.register({body: {"token": props.token, "password": password}})
.then(resp => {
if(!resp.error) {
console.log("resp", resp)
setMessage(undefined);
setAuthToken(resp.data.token);
setComplete(true);
} else {
setMessage(registerFailed);
}
})
.catch(resp => {
console.log("resp", resp);
setMessage(registerFailed);
});
};
if(!complete) {
return (
<Container fluid>
<Row>
<img alt="ziggy" src={"/ziggy.svg"} width={200}/>
</Row>
<Row>
<h1>Welcome new zrok user!</h1>
</Row>
<Row>
<h2>{props.email}</h2>
</Row>
<Row className={"fullscreen-body"}>
<Container className={"fullscreen-form"}>
<Row>
<Form onSubmit={handleSubmit}>
<Form.Group controlId={"password"}>
<Form.Control
type={"password"}
placeholder={"Set Password"}
onChange={t => { setMessage(null); setPassword(t.target.value); }}
value={password}
/>
</Form.Group>
<Form.Group controlId={"confirm"}>
<Form.Control
type={"password"}
placeholder={"Confirm Password"}
onChange={t => { setMessage(null); setConfirm(t.target.value); }}
value={confirm}
/>
</Form.Group>
<Button variant={"light"} type={"submit"}>Register Account</Button>
</Form>
</Row>
<Row>
{message}
</Row>
</Container>
</Row>
</Container>
);
}
return <Success email={props.email} token={authToken}/>;
};
export default SetPasswordForm;

View File

@ -0,0 +1,47 @@
import Icon from "@mdi/react";
import {mdiContentCopy} from "@mdi/js";
import {Container, Row} from "react-bootstrap";
import React from "react";
const Success = (props) => {
const handleCopy = async () => {
let copiedText = document.getElementById("zrok-enable-command").innerHTML;
try {
await navigator.clipboard.writeText(copiedText);
console.log("copied enable command");
} catch(err) {
console.error("failed to copy", err);
}
}
// clear local storage on new account registration success.
localStorage.clear();
return (
<Container fluid>
<Row>
<img alt="ziggy" src={"/ziggy.svg"} width={200}/>
</Row>
<Row>
<h1>Welcome to zrok!</h1>
</Row>
<Row className={"fullscreen-body"}>
<Container className={"fullscreen-form"}>
<Row>
<p>You can proceed to the <a href={"/"}>zrok web portal</a> and log in with your email and password.</p>
</Row>
<Row>
<p>To enable your shell for zrok, use this command:</p>
</Row>
<Row>
<pre>
$ <span id={"zrok-enable-command"}>zrok enable {props.token}</span> <Icon path={mdiContentCopy} size={0.7} onClick={handleCopy}/>
</pre>
</Row>
</Container>
</Row>
</Container>
)
};
export default Success;