adding Admin server

This commit is contained in:
Luke Berndt
2021-01-10 22:23:09 -05:00
parent c8b4f02d06
commit 86eb854848
156 changed files with 53167 additions and 15978 deletions

View File

@@ -0,0 +1,78 @@
import React, { Component } from "react";
import {
Container,
Dimmer,
Loader,
Button,
Message,
Icon
} from "semantic-ui-react";
import { Link } from "react-router-dom";
class ConfirmEmail extends Component {
state = {
loading: true,
confirmMessage: { success: false, message:""}
};
componentDidMount() {
const { match: { params } } = this.props;
this.props
.confirmEmail(params.userId, params.token)
.then(confirmMessage => {
if (confirmMessage) {
// report to the user is there was a problem during login
this.setState({
confirmMessage: confirmMessage, loading: false
});
}
});
}
render() {
var dimmerProps = { };
if (this.state.loading) {
dimmerProps["active"] = true;
}
return (
<Container text>
<Dimmer {...dimmerProps}>
<Loader indeterminate>Confirming Email Address</Loader>
</Dimmer>
{this.state.confirmMessage.success ? (
<Message icon>
<Icon name='check'/>
<Message.Content>
<Message.Header>Success</Message.Header>
<p>You have successfully confirmed you email address.</p>
<Link to="/">
<Button
size="large"
content="Continue to Login"
fluid
/></Link>
</Message.Content>
</Message>
) : (
<Message icon>
<Icon name='exclamation'/>
<Message.Content>
<Message.Header>Error</Message.Header>
<p>{this.state.confirmMessage.message}</p>
</Message.Content>
</Message>
)}
</Container>
);
}
}
export default ConfirmEmail;

View File

@@ -0,0 +1,14 @@
import { connect } from "react-redux"
import ConfirmEmail from "./ConfirmEmail"
import * as userActions from "./user-actions"
const mapStateToProps = (state) => {
return {
user: state.user
}
}
export default connect(
mapStateToProps,
userActions
)(ConfirmEmail)

195
account/src/User/Login.js Normal file
View File

@@ -0,0 +1,195 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import {
Container,
Header,
Form,
Grid,
Segment,
Button,
Message,
Icon,
Divider
} from "semantic-ui-react";
// ----------------------------------------------------
const dividerStyle = {
marginTop: "40px"
};
const forgotStyle = {
marginTop: "40px",
textAlign: "right"
};
// ----------------------------------------------------
class Login extends Component {
constructor(props) {
super(props);
this._onLoginSubmit = this._onLoginSubmit.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleRegisterClick = this.handleRegisterClick.bind(this);
}
state = {
loginMessage: "",
email: "",
password: ""
};
handleRegisterClick = () => {
this.props.changeUrl("/register");
};
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
_onLoginSubmit(event) {
event.preventDefault();
const { email, password } = this.state;
var success = { type: "path", nextPathname: "/" };
if (this.props.nextPathname) {
success.nextPathname = this.props.nextPathname;
}
if (this.props.nextLocation) {
success.type = "location";
switch (this.props.nextLocation) {
case "frontend":
success.nextLocation = process.env.REACT_APP_FRONTEND_SERVER;
break;
default:
case "admin":
success.nextLocation = process.env.REACT_APP_ADMIN_SERVER;
break;
}
}
// Passed in via react-redux. Returns a promise.
this.props
.manualLogin(
{
// this function is passed in via react-redux
email,
password
},
success
) // holds the path to redirect to after login (if any)
.then(loginMessage => {
if (loginMessage) {
// report to the user is there was a problem during login
this.setState({
loginMessage
});
}
});
}
render() {
var loginMessage = "";
if (this.state.loginMessage) {
loginMessage = (
<Message icon>
<Icon name="lemon" />
<Message.Content>
<Message.Header>Problems...</Message.Header>
{this.state.loginMessage}
</Message.Content>
</Message>
);
}
return (
<Container>
<Header as="h1">{process.env.REACT_APP_SITE_NAME}</Header>
<Grid stackable >
<Grid.Row>
<Grid.Column width={9}>
<Header size="large">Let's get this started!</Header>
<Header size="small">
{process.env.REACT_APP_SITE_NAME} makes it easy to share recordings from{" "}
<a href="https://github.com/robotastic/trunk-recorder">
Trunk Recorder
</a>.</Header>
<p>
Trunk Recorder makes it easy to record the radio systems
used by your local police and fire department. Trunk Recorder
is sort of like an awesome version of those Scanners from
Radio Shack... 10-4!
</p>
<p>
This site is a work in progress. I plan on adding more
features over time, but right now it can:
</p>
<ul>
<li>3 day archive</li>
<li>Filter by talkgroup</li>
</ul>
<p>
All of this goodness is free! In the future, I might offer
paid accounts with additional features and a longer archive
period, but I will always try to have a free version.
</p>
<p>
{" "}
- Luke{" "}
<a
href="mailto:{process.env.REACT_APP_ADMIN_EMAIL}?Subject={process.env.REACT_APP_SITE_NAME}"
target="_top"
>
admin@email.com
</a>{" "}
</p>
</Grid.Column>
<Grid.Column floated="right" width={6}>
<Header as="h3">Need an account?</Header>
<Button
size="large"
content="Register"
onClick={this.handleRegisterClick}
fluid
/>
<Divider style={dividerStyle} horizontal>Or</Divider>
<Header as="h3">Login</Header>
<Form onSubmit={this._onLoginSubmit}>
<Segment padded>
<Form.Field>
<Form.Input
icon="user"
iconPosition="left"
type="text"
name="email"
onChange={this.handleInputChange}
placeholder="E-mail address"
/>
</Form.Field>
<Form.Field>
<Form.Input
icon="lock"
iconPosition="left"
type="password"
name="password"
onChange={this.handleInputChange}
placeholder="Password"
/>
</Form.Field>
<Button type="submit" size="large" value="Login" fluid>
Login
</Button>
{loginMessage}
</Segment>
</Form>
<div style={forgotStyle} >
<Link to="/send-reset-password">Forgot Password</Link>
</div>
</Grid.Column>
</Grid.Row>
</Grid>
</Container>
);
}
}
export default Login;

View File

@@ -0,0 +1,35 @@
import { connect } from "react-redux"
import * as userActions from "./user-actions"
import Login from "./Login"
import queryString from '../query-string';
// Function passed in to `connect` to subscribe to Redux store updates.
// Any time it updates, mapStateToProps is called.
// The second argument "ownProps" contains props passed to the component
const mapStateToProps = (state, props) => {
let nextPathname = false;
try {nextPathname = props.location.state.nextPathname}
catch(err) {}
const uri = queryString.parse(props.location.search);
let nextLocation = uri['nextLocation'];
return {
user: state.user,
nextPathname, // this prop passed in by React Router
nextLocation
}
}
// Connects React component to the redux store
// It does not modify the component class passed to it
// Instead, it returns a new, connected component class, for you to use.
export default connect(
mapStateToProps,
userActions
)(Login)

View File

@@ -0,0 +1,45 @@
import React, { Component } from "react"
import UserForm from "./UserForm";
import {
Container,
Header
} from "semantic-ui-react";
class Profile extends Component {
constructor(props) {
super(props);
this.handleProfileSubmit = this.handleProfileSubmit.bind(this);
}
state = {
requestMessage: ""
}
handleProfileSubmit(user) {
user.userId = this.props.user.userId
this.props
.updateProfile(user)
.then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during registration
this.setState({
requestMessage
});
}
});
}
render() {
// The Key on UserForm get updated when the user is loaded. This forces the Component to be completely redrawn, otherwise the changes in the Prop are not copied to State.
return(
<Container text>
<Header as="h1">Update Profile</Header>
<UserForm onSubmit={this.handleProfileSubmit} user={this.props.user} isEditing={true} requestMessage={this.state.requestMessage} isWaiting={this.props.isWaiting} key={this.props.user.userId}/>
</Container>
)
}
}
export default Profile

View File

@@ -0,0 +1,15 @@
import { connect } from "react-redux"
import Profile from "./Profile"
import * as userActions from "./user-actions"
const mapStateToProps = (state) => {
return {
user: state.user,
isWaiting: state.user.isWaiting
}
}
export default connect(
mapStateToProps,
userActions
)(Profile)

View File

@@ -0,0 +1,60 @@
import React, { Component } from "react";
import {
Container,
Header,
Message,
Icon
} from "semantic-ui-react";
import UserForm from "./UserForm";
// ----------------------------------------------------
class Register extends Component {
constructor(props) {
super(props);
this._onRegisterSubmit = this._onRegisterSubmit.bind(this);
}
state = {
requestMessage: ""
}
_onRegisterSubmit(user) {
this.props
.manualRegister(user)
.then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during registration
this.setState({
requestMessage
});
}
});
}
render() {
return (
<div>
<Container text>
<Message icon>
<Icon name='trophy'/>
<Message.Content>
<Message.Header>Welcome!</Message.Header>
<p>Creating an account on OpenMHz is the first step in sharing radio system recordings using Trunk Recorder.</p>
<p>After creating an account, head over <a href="https://github.com/robotastic/trunk-recorder">here</a> to learn more about Trunk Recorder</p>
<p> - Luke</p>
</Message.Content>
</Message>
</Container>
<Container text>
<Header style={{ paddingTop: '20px' }} as="h1">Register</Header>
<UserForm onSubmit={this._onRegisterSubmit} requestMessage={this.state.requestMessage} isWaiting={this.props.isWaiting}/>
</Container>
</div>
);
}
}
export default Register;

View File

@@ -0,0 +1,14 @@
import { connect } from "react-redux"
import * as userActions from "./user-actions"
import Register from "./Register"
const mapStateToProps = (state) => {
return {
isWaiting: state.user.isWaiting
}
}
export default connect(
mapStateToProps,
userActions
)(Register)

View File

@@ -0,0 +1,106 @@
import React, { Component } from "react";
import {
Container,
Form,
Segment,
Button,
} from "semantic-ui-react";
class ResetPassword extends Component {
constructor(props) {
super(props);
this.handleResetPassword = this.handleResetPassword.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
}
state = {
checkInputMessages: [],
password: "",
confirmPassword: "",
passwordError: false,
confirmPasswordError: false,
sentReset: false
};
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
handleResetPassword(event) {
let error = false;
var checkInputMessages = [];
if (this.state.password === "") {
this.setState({ passwordError: true });
checkInputMessages.push("Password is required");
error = true;
} if (this.state.password.length < 7) {
this.setState({ passwordError: true });
checkInputMessages.push("Password must be 7 charecters or more");
error = true;
}else {
this.setState({ passwordError: false });
}
if (this.state.password !== this.state.confirmPassword) {
this.setState({ confirmPasswordError: true });
checkInputMessages.push("The passwords did not match");
error = true;
} else {
this.setState({ confirmPasswordError: false });
}
const { match: { params } } = this.props;
this.setState({ checkInputMessages: checkInputMessages });
if (!error) {
this.props.resetPassword(params.userId, params.token, this.state.password).then(resetSent => {
this.setState({resetSent: true});
});
}
}
render() {
return (
<div>
<Container text>
<Segment>
<Form
className="raised padding segment"
onSubmit={this.handleSubmit}
>
<Form.Group widths="equal">
<Form.Field>
<Form.Input
name="password"
type="password"
onChange={this.handleInputChange}
error={this.state.passwordError}
label="Password"
placeholder="Password..."
/>
</Form.Field>
<Form.Field>
<Form.Input
name="confirmPassword"
type="password"
onChange={this.handleInputChange}
error={this.state.confirmPasswordError}
label="Confirm Password"
placeholder="Confirm Password..."
/>
</Form.Field>
</Form.Group>
<Button
size="large"
content="Reset Password"
onClick={this.handleResetPassword}
fluid
/>
</Form>
</Segment>
</Container>
</div>
);
}
}
export default ResetPassword;

View File

@@ -0,0 +1,14 @@
import { connect } from "react-redux"
import ResetPassword from "./ResetPassword"
import * as userActions from "./user-actions"
const mapStateToProps = (state) => {
return {
user: state.user
}
}
export default connect(
mapStateToProps,
userActions
)(ResetPassword)

View File

@@ -0,0 +1,112 @@
import React, { Component } from "react";
import {
Form,
Grid,
Segment,
Button,
Message,
Icon
} from "semantic-ui-react";
import { Link } from "react-router-dom";
const returnStyle = {
marginTop: "30px"
};
class SendResetPassword extends Component {
constructor(props) {
super(props);
this.handleSendResetPassword = this.handleSendResetPassword.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
}
state = {
checkInputMessages: "",
email: "",
emailError: false,
sentReset: false
};
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
handleSendResetPassword(event) {
if (this.state.email === "") {
this.setState({ emailError: true, checkInputMessages: "Email is required"});
} else {
this.setState({ emailError: false });
this.props.sendResetPassword({email: this.state.email}).then(resetMessage => {
if (resetMessage) {
// report to the user is there was a problem during login
this.setState({resetSent: false, checkInputMessages: resetMessage, emailError: true});
} else {
this.setState({resetSent: true});
}
});
}
}
render() {
var resetMessage = "";
if (this.state.checkInputMessages.length) {
resetMessage = (
<Message compact warning icon>
<Icon name="lightning" />
<Message.Content>
<Message.Header>Problems...</Message.Header>
{this.state.checkInputMessages}
</Message.Content>
</Message>
);
}
return (
<Grid verticalAlign='middle' centered>
<Grid.Row>
<Grid.Column width="6">
{ !this.state.resetSent ? (
<div>
<h2>Forgot your password?</h2>
<Form className="raised padding segment" onSubmit={this.handleSubmit}>
Enter the email address for your account.
<Form.Field>
<Form.Input
type="text"
name="email"
onChange={this.handleInputChange}
error={this.state.emailError}
label="Email"
placeholder="Email..."
/>
</Form.Field>
<Button
size="large"
content="Reset Password"
onClick={this.handleSendResetPassword}
fluid
/>
</Form>
{resetMessage}
</div>
)
:( <Segment raised padded='very'>
<h2>Reset Link Sent </h2>
<div>An email with a link to reset your password was sent to: {this.state.email}</div>
<Link to="/" style={returnStyle}><Icon name="left arrow"/>Back to OpenMHz</Link>
</Segment>
)}
</Grid.Column>
</Grid.Row>
</Grid>
);
}
}
export default SendResetPassword;

View File

@@ -0,0 +1,14 @@
import { connect } from "react-redux"
import SendResetPassword from "./SendResetPassword"
import * as userActions from "./user-actions"
const mapStateToProps = (state) => {
return {
user: state.user
}
}
export default connect(
mapStateToProps,
userActions
)(SendResetPassword)

View File

@@ -0,0 +1,28 @@
import React, { Component } from "react"
import {
Container,
Message,
Icon
} from "semantic-ui-react";
class SentConfirmEmail extends Component {
render() {
return (
<div>
<Container text>
<Message icon>
<Icon name='envelope outline' />
<Message.Content>
<Message.Header>Confirmation email sent!</Message.Header>
<p>We sent an email to the address you gave us: {this.props.user.email}<br />
Please click on the link in the email to verify the email address.</p>
</Message.Content>
</Message>
</Container>
</div>
)
}
}
export default SentConfirmEmail

View File

@@ -0,0 +1,14 @@
import { connect } from "react-redux"
import SentConfirmEmail from "./SentConfirmEmail"
import * as userActions from "./user-actions"
const mapStateToProps = (state) => {
return {
user: state.user
}
}
export default connect(
mapStateToProps,
userActions
)(SentConfirmEmail)

84
account/src/User/Terms.js Normal file
View File

@@ -0,0 +1,84 @@
import React, { Component } from "react";
import {
Segment,
Container,
Button
} from "semantic-ui-react";
class Terms extends Component {
constructor(props) {
super(props);
this.handleAccept = this.handleAccept.bind(this);
this.handleDecline = this.handleDecline.bind(this);
}
handleAccept() {
this.props
.acceptTerms(this.props.user.userId)
.then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during registration
this.setState({
requestMessage
});
}
});
}
handleDecline() {
}
render() {
return (
<div>
<Container text>
<Segment attached>
The agreement is useful to The Guardian to set the rules and guidelines for visitors, users and customers:
Registration: inform users that they must agree to a set of rules when they register on your website or mobile app
Disable or terminate accounts: inform users that their accounts can be disabled or terminated if abuses happen on your website or mobile app, e.g. spamming, swearing etc.
Owner of content: notify users that you, the company, is the owner of the content appearing on your website or mobile app – except in cases where other users can post content (upload, share, create etc.) where the users are the owners of such content
Changes to the agreement: users should be informed about upcoming changes to the agreement before the changes are applied.
DMCA: The Guardian is also informing owners of copyright content that they can submit a DMCA notice to the website if they found infringing content in their DMCA clause.
And more
These are only a few examples of what kind of clauses and disclosures you can add in this kind of agreement.
What is a Terms of Use
While a Terms of Use is recommended to have, it’s not mandatory by law as the Privacy Policy agreement is required.
This agreement serves a single purpose: to inform users of rules that they must agree to. A Privacy Policy must inform users about your privacy practices (what data you collect, why, how, etc.)
The definition of a Terms of Use agreement is simple:
Terms of service (also known as terms of use and terms and conditions, commonly abbreviated as ToS or TOS and TOU) are rules which one must agree to abide by in order to use a service.
Wikipedia
Because this agreement simply acts as a contract between you, the company, and the users using or accessing your website or mobile app, the agreement can be named as you’d like:
Terms of Use (ToU)
Terms of Service (ToS)
Terms and Conditions (T&C)
User Agreement
Conditions of Use
And so on
The “Conditions of Use” of Amazon could be named “Terms of Use”:
</Segment>
<Button.Group attached="bottom">
<Button
onClick={this.handleDecline}
>Decline</Button>
<Button.Or />
<Button positive
onClick={this.handleAccept}
>Accept</Button>
</Button.Group>
</Container>
</div>
);
}
}
export default Terms;

View File

@@ -0,0 +1,14 @@
import { connect } from "react-redux"
import * as userActions from "./user-actions"
import Terms from "./Terms"
function mapStateToProps(state) {
return {
user: state.user
}
}
export default connect(
mapStateToProps,
userActions
)(Terms)

View File

@@ -0,0 +1,279 @@
import React, { Component } from "react";
import {
Form,
Dimmer,
Loader,
Button,
Message,
Icon
} from "semantic-ui-react";
// ----------------------------------------------------
class UserForm extends Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.checkInputs = this.checkInputs.bind(this);
this.render = this.render.bind(this)
this.state = {
checkInputMessages: [],
registerMessages: "",
firstName: "",
lastName: "",
screenName: "",
email: "",
location: "",
password: "",
confirmPassword: "",
firstNameError: false,
lastNameError: false,
screenNameError: false,
emailError: false,
locationError: false,
passwordError: false,
confirmPasswordError: false,
isEditing: this.props.isEditing
};
if (this.props.isEditing) {
this.state.firstName = this.props.user.firstName;
this.state.lastName = this.props.user.lastName;
this.state.screenName = this.props.user.screenName;
this.state.email = this.props.user.email;
this.state.location = this.props.user.location;
}
}
checkInputs() {
let error = false;
var checkInputMessages = [];
if (this.state.firstName === "") {
this.setState({ firstNameError: true });
checkInputMessages.push("First Name is required");
error = true;
} else {
this.setState({ firstNameError: false });
}
if (this.state.lastName === "") {
this.setState({ lastNameError: true });
checkInputMessages.push("Last Name is required");
error = true;
} else {
this.setState({ lastNameError: false });
}
if (this.state.email === "") {
this.setState({ emailError: true });
checkInputMessages.push("Email is required");
error = true;
} else {
this.setState({ emailError: false });
}
if (this.state.location === "") {
this.setState({ locationError: true });
checkInputMessages.push("Location is required");
error = true;
} else {
this.setState({ locationError: false });
}
if (this.state.screenName === "") {
this.setState({ screenNameError: true });
checkInputMessages.push("Screen Name is required");
error = true;
} else {
this.setState({ screenNameError: false });
}
if (this.props.isEditing) {
this.setState({ checkInputMessages: checkInputMessages });
return error;
}
if (this.state.password === "") {
this.setState({ passwordError: true });
checkInputMessages.push("Password is required");
error = true;
} if (this.state.password.length < 7) {
this.setState({ passwordError: true });
checkInputMessages.push("Password must be 7 charecters or more");
error = true;
} else {
this.setState({ passwordError: false });
}
if (this.state.password !== this.state.confirmPassword) {
this.setState({ confirmPasswordError: true });
checkInputMessages.push("The passwords did not match");
error = true;
} else {
this.setState({ confirmPasswordError: false });
}
this.setState({ checkInputMessages: checkInputMessages });
return error;
}
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
handleSubmit(event) {
event.preventDefault();
let inputError = this.checkInputs();
if (!inputError) {
const user = (({ firstName, lastName, screenName, location, email, password }) => ({ firstName, lastName, screenName, location, email, password }))(this.state);
this.props.onSubmit(user);
}
}
render() {
var registerMessage = "";
var floatStyle = {
overflow: 'auto'
}
var dimmerProps = {};
if (this.props.isWaiting) {
dimmerProps["active"] = true;
}
const { isEditing } = this.props;
if (this.state.checkInputMessages.length || this.props.requestMessage.length) {
var counter = 0;
registerMessage = (
<Message compact warning icon>
<Icon name="lightning" />
<Message.Content>
<Message.Header>Problems...</Message.Header>
<ul>
{this.state.checkInputMessages.map(function (listValue) {
return <li key={`ListItem_${counter++}`}>{listValue}</li>;
})}
{this.props.requestMessage.length > 0 &&
<li key="req-mess">{this.props.requestMessage}</li>
}
</ul>
</Message.Content>
</Message>
);
}
return (
<div>
<Form
className="raised padding segment"
onSubmit={this.handleSubmit}
>
<Dimmer {...dimmerProps}>
<Loader indeterminate>Updating Profile</Loader>
</Dimmer>
<Form.Group widths="equal">
<Form.Field>
<Form.Input
type="text"
name="firstName"
onChange={this.handleInputChange}
error={this.state.firstNameError}
defaultValue={this.state.firstName}
label="First name"
placeholder="First Name..."
/>
</Form.Field>
<Form.Field>
<Form.Input
type="text"
name="lastName"
onChange={this.handleInputChange}
error={this.state.lastNameError}
defaultValue={this.state.lastName}
label="Last name"
placeholder="Last Name..."
/>
</Form.Field>
</Form.Group>
<Form.Group widths="equal">
<Form.Field>
<Form.Input
type="text"
name="email"
onChange={this.handleInputChange}
error={this.state.emailError}
defaultValue={this.state.email}
disabled={isEditing}
label="Email"
placeholder="Email..."
/>
</Form.Field>
<Form.Field>
<Form.Input
type="text"
name="screenName"
onChange={this.handleInputChange}
error={this.state.screenNameError}
defaultValue={this.state.screenName}
label="Screen Name"
placeholder="Screen Name..."
/>
<p style={{ fontStyle: "italic" }}>
Shown on OpenMHz for each of your systems
</p>
</Form.Field>
</Form.Group>
<Form.Group widths="equal">
<Form.Field>
<Form.Input
type="text"
name="location"
onChange={this.handleInputChange}
error={this.state.locationError}
defaultValue={this.state.location}
label="General Location"
placeholder="City, State..."
/>
</Form.Field>
</Form.Group>
{isEditing !== true && (
<Form.Group widths="equal">
<Form.Field>
<Form.Input
name="password"
type="password"
onChange={this.handleInputChange}
error={this.state.passwordError}
disabled={isEditing}
label="Password"
placeholder="Password..."
/>
</Form.Field>
<Form.Field>
<Form.Input
name="confirmPassword"
type="password"
onChange={this.handleInputChange}
error={this.state.confirmPasswordError}
disabled={isEditing}
label="Confirm Password"
placeholder="Confirm Password..."
/>
</Form.Field>
</Form.Group>
)}
<div style={floatStyle}>
<Button type="submit" size="large" floated="right" value="Login" color='blue'>
{isEditing !== true ? 'Register' : 'Update'}
</Button>
</div>
</Form>
{registerMessage}
</div>
);
}
}
export default UserForm;

View File

@@ -0,0 +1,52 @@
import React, { Component } from "react"
import {
Container,
Header,
Button,
Message,
Icon
} from "semantic-ui-react";
class WaitConfirmEmail extends Component {
constructor(props) {
super(props);
this.handleSendConfirmEmail = this.handleSendConfirmEmail.bind(this);
}
handleSendConfirmEmail(event) {
this.props.sendConfirmEmail(this.props.user.userId).then(loginMessage => {
if (loginMessage) {
// report to the user is there was a problem during login
this.setState({
loginMessage
});
}
});
};
render() {
return(
<Container text>
<Message icon>
<Icon name='envelope outline'/>
<Message.Content>
<Message.Header>Confirm your email address</Message.Header>
<p>We sent an email to the address you gave us: {this.props.user.email}<br/>
Please click on the link in the email to verify the email address.</p>
<Header as='h3'>Didn't get an email?</Header>
<p>So, you have waited a bit and haven't gotten anything?</p>
<p>...and you checked your spam folder?</p>
<p>Well, click below.</p>
<Button
size="large"
content="Resend Email Confirmation"
onClick={this.handleSendConfirmEmail}
/>
</Message.Content>
</Message>
</Container>
)
}
}
export default WaitConfirmEmail

View File

@@ -0,0 +1,14 @@
import { connect } from "react-redux"
import WaitConfirmEmail from "./WaitConfirmEmail"
import * as userActions from "./user-actions"
const mapStateToProps = (state) => {
return {
user: state.user
}
}
export default connect(
mapStateToProps,
userActions
)(WaitConfirmEmail)

View File

@@ -0,0 +1,390 @@
import axios from "axios";
//import { browserHistory } from "react-router"
import * as types from "./user-constants";
import { push } from 'connected-react-router';
// "Log In" action creators
function beginLogin() {
return { type: types.MANUAL_LOGIN_USER };
}
function loginSuccess(data) {
return {
type: types.LOGIN_SUCCESS_USER,
data
};
}
function loginEmailError(data) {
return {
type: types.LOGIN_EMAIL_ERROR_USER,
data
};
}
function loginError() {
return { type: types.LOGIN_ERROR_USER };
}
// "update profile" action creators
function beginUpdateProfile() {
return { type: types.UPDATE_PROFILE_USER };
}
function updateProfileSuccess(data) {
return { type: types.UPDATE_PROFILE_SUCCESS_USER, data };
}
function updateProfileError() {
return { type: types.UPDATE_PROFILE_ERROR_USER };
}
// "Reset Password" action creators
function beginResetPassword() {
return { type: types.RESET_PASSWORD_USER };
}
function resetPasswordSuccess() {
return { type: types.RESET_PASSWORD_SUCCESS_USER };
}
function resetPasswordError() {
return { type: types.RESET_PASSWORD_ERROR_USER };
}
// "Send Reset Password" action creators
function beginSendResetPassword() {
return { type: types.SEND_RESET_PASSWORD_USER };
}
function sendResetPasswordSuccess() {
return { type: types.SEND_RESET_PASSWORD_SUCCESS_USER };
}
function sendResetPasswordError() {
return { type: types.SEND_RESET_PASSWORD_ERROR_USER };
}
// "Confirm" action creators
function beginConfirm() {
return { type: types.CONFIRM_USER };
}
function confirmSuccess() {
return { type: types.CONFIRM_SUCCESS_USER };
}
function confirmError() {
return { type: types.CONFIRM_ERROR_USER };
}
// "Send Confirm" action creators
function beginSendConfirm() {
return { type: types.SEND_CONFIRM_USER };
}
function sendConfirmSuccess() {
return { type: types.SEND_CONFIRM_SUCCESS_USER };
}
function sendConfirmError() {
return { type: types.SEND_CONFIRM_ERROR_USER };
}
// "Log Out" action creators
function beginLogout() {
return { type: types.LOGOUT_USER };
}
function logoutSuccess() {
return { type: types.LOGOUT_SUCCESS_USER };
}
function logoutError() {
return { type: types.LOGOUT_ERROR_USER };
}
// "Register" action creators
function beginRegister() {
return { type: types.REGISTER_USER };
}
function registerSuccess(data) {
return {
type: types.REGISTER_SUCCESS_USER,
data
};
}
function registerError() {
return { type: types.REGISTER_ERROR_USER };
}
// "Terms" action creators
function beginAgreeTerms() {
return { type: types.AGREE_TERMS_USER };
}
function agreeTermsSuccess() {
return { type: types.AGREE_TERMS_SUCCESS_USER };
}
function agreeTermsError() {
return { type: types.AGREE_TERMS_ERROR_USER };
}
function makeUserRequest(method, data, api = "/login") {
// returns a Promise
return axios({
method: method,
url: api,
data: data
});
}
export function changeUrl(url) {
return dispatch => {
dispatch(push(url));
};
}
// Example of an Async Action Creator
// http://redux.js.org/docs/advanced/AsyncActions.html
export function manualLogin(
data,
success // path to redirect to upon successful log in
) {
return dispatch => {
dispatch(beginLogin());
return makeUserRequest("post", data, "/login", { withCredentials: true })
.then(response => {
if (response.data.success) {
const user = response.data.user
dispatch(loginSuccess(user));
// use browserHistory singleton to control navigation. Will generate a
// state change for time-traveling as we are using the react-router-redux package
//browserHistory.push(successPath)
if (success && (success.type === "path")) {
dispatch(push(success.nextPathname));
} else if (success && (success.type === "location")) {
window.location = success.nextLocation;
} else {
dispatch(push("/"));
}
} else {
let loginMessage = response.data.message;
console.log("loginMessage");
if (loginMessage === "unconfirmed email") {
data.userId = response.data.userId;
dispatch(loginEmailError(data));
dispatch(push("/wait-confirm-email"));
} else {
dispatch(loginError());
}
return loginMessage;
}
})
.catch(function (response) {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}
// Example of an Async Action Creator
// http://redux.js.org/docs/advanced/AsyncActions.html
export function isAuthenticated() {
return dispatch => {
return axios
.get("/authenticated")
.then(response => {
if (response.data.success) {
dispatch(loginSuccess());
return true;
} else {
dispatch(loginError());
return false;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened during logout that triggered an Error
console.log("Error", response.message);
}
});
};
}
// Example of an Async Action Creator
// http://redux.js.org/docs/advanced/AsyncActions.html
export function manualLogout() {
return dispatch => {
dispatch(beginLogout());
return axios
.get("/logout")
.then(response => {
if (response.data.success) {
dispatch(logoutSuccess());
// use browserHistory singleton to control navigation. Will generate a
// state change for time-traveling as we are using the react-router-redux package
//browserHistory.push("/") // logout to home page
dispatch(push("/"));
} else {
dispatch(logoutError());
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened during logout that triggered an Error
console.log("Error", response.message);
}
});
};
}
export function confirmEmail(userId, token) {
return dispatch => {
dispatch(beginConfirm());
return makeUserRequest("post", {}, "/users/" + userId + "/confirm/" + token).then(
response => {
if (response.data.success) {
dispatch(confirmSuccess());
let requestMessage = { success: true, message: response.data.message };
return requestMessage;
} else {
dispatch(confirmError());
let requestMessage = { success: false, message: response.data.message };
return requestMessage;
}
}
);
};
}
export function resetPassword(userId, token, password) {
return dispatch => {
dispatch(beginResetPassword());
return makeUserRequest("post", { password: password }, "/users/" + userId + "/reset-password/" + token).then(
response => {
if (response.data.success) {
dispatch(resetPasswordSuccess());
dispatch(push("/login"));
} else {
dispatch(resetPasswordError());
let requestMessage = response.data.message;
return requestMessage;
}
}
);
};
}
export function sendResetPassword(data) {
return dispatch => {
dispatch(beginSendResetPassword());
return makeUserRequest("post", data, "/api/send-reset-password").then(
response => {
if (response.data.success) {
dispatch(sendResetPasswordSuccess());
} else {
dispatch(sendResetPasswordError());
return response.data.message;
}
}
);
};
}
export function sendConfirmEmail(data) {
return dispatch => {
dispatch(beginSendConfirm());
return makeUserRequest("post", data, "/users/" + data + "/send-confirm").then(
response => {
if (response.data.success) {
dispatch(sendConfirmSuccess());
dispatch(push("/sent-confirm-email"));
} else {
dispatch(sendConfirmError());
let requestMessage = response.data.Message;
return requestMessage;
}
}
);
};
}
export function acceptTerms(data) {
return dispatch => {
dispatch(beginAgreeTerms());
return makeUserRequest("post", data, "/users/" + data + "/terms").then(
response => {
if (response.data.success) {
dispatch(agreeTermsSuccess());
} else {
dispatch(agreeTermsError());
let requestMessage = response.data.Message;
return requestMessage;
}
}
);
};
}
export function updateProfile(data) {
return dispatch => {
dispatch(beginUpdateProfile());
return makeUserRequest("post", data, "/users/" + data.userId)
.then(response => {
if (response.data.success) {
const user = response.data.user
dispatch(updateProfileSuccess(user));
dispatch(push("/"));
} else {
dispatch(updateProfileError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}
export function manualRegister(data) {
return dispatch => {
dispatch(beginRegister());
return makeUserRequest("post", data, "/register")
.then(response => {
if (response.data.success) {
data.userId = response.data.userId;
dispatch(registerSuccess(data));
dispatch(push("/sent-confirm-email"));
} else {
dispatch(registerError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}

View File

@@ -0,0 +1,31 @@
export const MANUAL_LOGIN_USER = "MANUAL_LOGIN_USER"
export const LOGIN_SUCCESS_USER = "LOGIN_SUCCESS_USER"
export const LOGIN_EMAIL_ERROR_USER = "LOGIN_EMAIL_ERROR_USER"
export const LOGIN_ERROR_USER = "LOGIN_ERROR_USER"
export const CONFIRM_USER = "CONFIRM_USER"
export const CONFIRM_ERROR_USER = "CONFIRM_ERROR_USER"
export const CONFIRM_SUCCESS_USER = "CONFIRM_SUCCESS_USER"
export const RESET_PASSWORD_USER = "RESET_PASSWORD_USER"
export const RESET_PASSWORD_ERROR_USER = "RESET_PASSWORD_ERROR_USER"
export const RESET_PASSWORD_SUCCESS_USER = "RESET_PASSWORD_SUCCESS_USER"
export const SEND_RESET_PASSWORD_USER = "SEND_RESET_PASSWORD_USER"
export const SEND_RESET_PASSWORD_ERROR_USER = "SEND_RESET_PASSWORD_ERROR_USER"
export const SEND_RESET_PASSWORD_SUCCESS_USER = "SEND_RESET_PASSWORD_SUCCESS_USER"
export const SEND_CONFIRM_USER = "SEND_CONFIRM_USER"
export const SEND_CONFIRM_ERROR_USER = "SEND_CONFIRM_ERROR_USER"
export const SEND_CONFIRM_SUCCESS_USER = "SEND_CONFIRM_SUCCESS_USER"
export const SIGNUP_USER = "SIGNUP_USER"
export const SIGNUP_SUCCESS_USER = "SIGNUP_SUCCESS_USER"
export const SIGNUP_ERROR_USER = "SIGNUP_ERROR_USER"
export const LOGOUT_USER = "LOGOUT_USER"
export const LOGOUT_SUCCESS_USER = "LOGOUT_SUCCESS_USER"
export const LOGOUT_ERROR_USER = "LOGOUT_ERROR_USER"
export const UPDATE_PROFILE_USER = "UPDATE_PROFILE_USER"
export const UPDATE_PROFILE_SUCCESS_USER = "UPDATE_PROFILE_SUCCESS_USER"
export const UPDATE_PROFILE_ERROR_USER = "UPDATE_PROFILE_ERROR_USER"
export const REGISTER_USER = "REGISTER_USER"
export const REGISTER_SUCCESS_USER = "REGISTER_SUCCESS_USER"
export const REGISTER_ERROR_USER = "REGISTER_ERROR_USER"
export const AGREE_TERMS_USER = "AGREE_TERMS_USER"
export const AGREE_TERMS_SUCCESS_USER = "AGREE_TERMS_SUCCESS_USER"
export const AGREE_TERMS_ERROR_USER = "AGREE_TERMS_ERROR_USER"

View File

@@ -0,0 +1,139 @@
import {
MANUAL_LOGIN_USER,
LOGIN_SUCCESS_USER,
LOGIN_ERROR_USER,
LOGIN_EMAIL_ERROR_USER,
SEND_RESET_PASSWORD_USER,
SEND_RESET_PASSWORD_ERROR_USER,
SEND_RESET_PASSWORD_SUCCESS_USER,
RESET_PASSWORD_USER,
RESET_PASSWORD_ERROR_USER,
RESET_PASSWORD_SUCCESS_USER,
CONFIRM_USER,
CONFIRM_ERROR_USER,
CONFIRM_SUCCESS_USER,
UPDATE_PROFILE_USER,
UPDATE_PROFILE_ERROR_USER,
UPDATE_PROFILE_SUCCESS_USER,
SEND_CONFIRM_USER,
SEND_CONFIRM_ERROR_USER,
SEND_CONFIRM_SUCCESS_USER,
LOGOUT_USER,
LOGOUT_SUCCESS_USER,
LOGOUT_ERROR_USER,
REGISTER_USER,
REGISTER_SUCCESS_USER,
REGISTER_ERROR_USER
} from "./user-constants";
const user = (
state = {
isWaiting: false,
authenticated: false,
userId: ""
},
action
) => {
switch (action.type) {
case MANUAL_LOGIN_USER:
return Object.assign({}, state, { isWaiting: true });
case LOGIN_SUCCESS_USER:
return Object.assign({}, state, {
isWaiting: false,
authenticated: true,
...action.data
});
case LOGIN_EMAIL_ERROR_USER:
return Object.assign({}, state, {
isWaiting: false,
authenticated: false,
userId: action.data.userId
});
case LOGIN_ERROR_USER:
return Object.assign({}, state, {
isWaiting: false,
authenticated: false
});
case UPDATE_PROFILE_USER:
return Object.assign({}, state, {
isWaiting: true
});
case UPDATE_PROFILE_SUCCESS_USER:
return Object.assign({}, state, {
isWaiting: false,
...action.data
});
case UPDATE_PROFILE_ERROR_USER:
return Object.assign({}, state, {
isWaiting: false
});
case CONFIRM_USER:
return Object.assign({}, state, {
isWaiting: true,
authenticated: false
});
case CONFIRM_SUCCESS_USER:
return Object.assign({}, state, {
isWaiting: false,
authenticated: false
});
case CONFIRM_ERROR_USER:
return Object.assign({}, state, {
isWaiting: false,
authenticated: false
});
case RESET_PASSWORD_USER:
case SEND_RESET_PASSWORD_USER:
case SEND_CONFIRM_USER:
return Object.assign({}, state, {
isWaiting: true,
authenticated: false
});
case RESET_PASSWORD_ERROR_USER:
case RESET_PASSWORD_SUCCESS_USER:
case SEND_RESET_PASSWORD_ERROR_USER:
case SEND_RESET_PASSWORD_SUCCESS_USER:
case SEND_CONFIRM_ERROR_USER:
case SEND_CONFIRM_SUCCESS_USER:
return Object.assign({}, state, {
isWaiting: false,
authenticated: false
});
case LOGOUT_USER:
return Object.assign({}, state, { isWaiting: true });
case LOGOUT_SUCCESS_USER:
return Object.assign({}, state, {
isWaiting: false,
authenticated: false,
email: "",
userId: ""
});
case LOGOUT_ERROR_USER:
return Object.assign({}, state, {
isWaiting: false,
authenticated: true
});
case REGISTER_USER:
return Object.assign({}, state, { isWaiting: true });
case REGISTER_SUCCESS_USER:
return Object.assign({}, state, { isWaiting: false,
userId: action.data.userId });
case REGISTER_ERROR_USER:
return Object.assign({}, state, { isWaiting: false });
default:
return state;
}
};
export default user;