mirror of
https://github.com/openmhz/trunk-server.git
synced 2026-11-13 09:17:30 -05:00
First Commit
This commit is contained in:
86
account/src/client/Billing/Billing.js
Normal file
86
account/src/client/Billing/Billing.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import React, { Component } from "react";
|
||||
import { Elements, StripeProvider} from 'react-stripe-elements';
|
||||
import { Container, Header, Segment, Icon, Divider, Button, Message } from 'semantic-ui-react'
|
||||
import PaymentSource from "./PaymentSource"
|
||||
import InvoiceList from "./InvoiceList"
|
||||
// ----------------------------------------------------
|
||||
class Billing extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleShowAddSource = this.handleShowAddSource.bind(this);
|
||||
this.handleNewSource = this.handleNewSource.bind(this);
|
||||
this.state = {showAddSource: false};
|
||||
}
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchBilling();
|
||||
this.props.fetchInvoices();
|
||||
}
|
||||
handleShowAddSource() {
|
||||
this.setState({
|
||||
showAddSource: !this.state.showAddSource
|
||||
});
|
||||
}
|
||||
handleNewSource(newSource) {
|
||||
this.setState({
|
||||
showAddSource: false
|
||||
});
|
||||
this.props.updateCard(newSource);
|
||||
}
|
||||
//https://stackoverflow.com/questions/36559661/how-can-i-dispatch-from-child-components-in-react-redux
|
||||
//https://stackoverflow.com/questions/42597602/react-onclick-pass-event-with-parameter
|
||||
render() {
|
||||
var name = this.props.user.firstName + " " + this.props.user.lastName;
|
||||
var section=<div/>;
|
||||
if (!this.props.billing || this.state.showAddSource) {
|
||||
section = (<div>
|
||||
<StripeProvider apiKey="">
|
||||
<Elements>
|
||||
<PaymentSource name={name} email={this.props.user.email} onCancel={this.handleShowAddSource} onSubmit={this.handleNewSource}/>
|
||||
</Elements>
|
||||
</StripeProvider>
|
||||
</div>)
|
||||
} else {
|
||||
section = (<Segment padded='very'>
|
||||
<Divider horizontal>
|
||||
<Header as='h3'>
|
||||
<Icon name='credit card outline' />
|
||||
Card on File
|
||||
</Header>
|
||||
</Divider>
|
||||
<Header as='h3'>
|
||||
<Header.Content>
|
||||
{this.props.billing.source.name} xxxx-{this.props.billing.source.last4}
|
||||
<Header.Subheader>Expires {this.props.billing.source.exp_month} / {this.props.billing.source.exp_year}</Header.Subheader>
|
||||
</Header.Content>
|
||||
</Header>
|
||||
<Button onClick={this.handleShowAddSource}>Change Card</Button>
|
||||
</Segment>)
|
||||
}
|
||||
|
||||
return (
|
||||
<Container text>
|
||||
<Header as='h2'>
|
||||
Payment Method</Header>
|
||||
<p>It is free to use OpenMHz. Add payment in order to upgrade features.</p>
|
||||
{section}
|
||||
<Header as='h2'>
|
||||
Payment History</Header>
|
||||
{this.props.billing.invoices.length > 0 ? (
|
||||
<InvoiceList invoices={this.props.billing.invoices}/>
|
||||
) : (
|
||||
<Message>
|
||||
<Message.Header>Changes in Service</Message.Header>
|
||||
<p>
|
||||
We updated our privacy policy here to better service our customers. We recommend reviewing the
|
||||
changes.
|
||||
</p>
|
||||
</Message>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Billing;
|
||||
17
account/src/client/Billing/BillingContainer.js
Normal file
17
account/src/client/Billing/BillingContainer.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { connect } from "react-redux"
|
||||
import * as billingActions from "./billing-actions"
|
||||
import Billing from "./Billing"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
user: state.user,
|
||||
billing: state.billing
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
billingActions
|
||||
)(Billing)
|
||||
43
account/src/client/Billing/InvoiceList.js
Normal file
43
account/src/client/Billing/InvoiceList.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import React, { Component } from "react";
|
||||
|
||||
import {
|
||||
Container,
|
||||
Table
|
||||
} from "semantic-ui-react";
|
||||
|
||||
class InvoiceList extends Component {
|
||||
|
||||
|
||||
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const DATE_OPTIONS = { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' };
|
||||
return (
|
||||
<Table singleLine>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Date</Table.HeaderCell>
|
||||
<Table.HeaderCell>Amount</Table.HeaderCell>
|
||||
<Table.HeaderCell>Status</Table.HeaderCell>
|
||||
<Table.HeaderCell></Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{this.props.invoices.map((invoice, index) => {
|
||||
return( <Table.Row>
|
||||
<Table.Cell>{(new Date(invoice.date*1000)).toLocaleDateString('en-US', DATE_OPTIONS)}</Table.Cell>
|
||||
<Table.Cell>${invoice.amount_due/100}</Table.Cell>
|
||||
<Table.Cell>{invoice.status} </Table.Cell>
|
||||
<Table.Cell><a href={invoice.invoice_pdf}>View Invoice</a> </Table.Cell>
|
||||
</Table.Row>)
|
||||
} )}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default InvoiceList;
|
||||
52
account/src/client/Billing/PaymentSource.js
Normal file
52
account/src/client/Billing/PaymentSource.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import React, {Component} from 'react';
|
||||
import { Header, Button, Segment, Form, Icon, Divider } from 'semantic-ui-react'
|
||||
import {CardElement, injectStripe} from 'react-stripe-elements';
|
||||
|
||||
class PaymentSource extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.submit = this.submit.bind(this);
|
||||
this.handleInputChange = this.handleInputChange.bind(this);
|
||||
this.state = {name: this.props.name ? this.props.name : ""};
|
||||
}
|
||||
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
|
||||
async submit(ev) {
|
||||
console.log("here");
|
||||
this.props.stripe.createSource({type: 'card', owner: {
|
||||
name: this.state.name, email: this.props.email
|
||||
}}).then(({source}) => {
|
||||
this.props.onSubmit({sourceId: source.id })
|
||||
console.log('Received Stripe token:', source);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="checkout">
|
||||
<Segment padded='very'>
|
||||
<Form>
|
||||
<Divider horizontal>
|
||||
<Header as='h3'>
|
||||
<Icon name='credit card outline' />
|
||||
Credit or Debit Card
|
||||
</Header>
|
||||
</Divider>
|
||||
<Form.Input fluid label='Name on card' placeholder='Name'
|
||||
name="name"
|
||||
defaultValue={this.state.name}
|
||||
onChange={this.handleInputChange}/>
|
||||
<Form.Field>
|
||||
<CardElement />
|
||||
</Form.Field>
|
||||
<Divider hidden />
|
||||
<Button onClick={this.submit}>Submit</Button>
|
||||
</Form>
|
||||
<img src="../../resources/powered_by_stripe.png" />
|
||||
</Segment>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectStripe(PaymentSource);
|
||||
145
account/src/client/Billing/billing-actions.js
Normal file
145
account/src/client/Billing/billing-actions.js
Normal file
@@ -0,0 +1,145 @@
|
||||
import axios from "axios";
|
||||
import * as types from "./billing-constants";
|
||||
import { push } from 'connected-react-router';
|
||||
|
||||
|
||||
function beginUpdateCard() {
|
||||
return { type: types.UPDATE_CARD };
|
||||
}
|
||||
|
||||
function updateCardSuccess(data) {
|
||||
return { type: types.UPDATE_CARD_SUCCESS, data };
|
||||
}
|
||||
|
||||
function updateCardError(data) {
|
||||
return { type: types.UPDATE_CARD_ERROR, data };
|
||||
}
|
||||
|
||||
|
||||
function beginFetchBilling() {
|
||||
return { type: types.FETCH_BILLING };
|
||||
}
|
||||
|
||||
function fetchBillingSuccess(data) {
|
||||
return { type: types.FETCH_BILLING_SUCCESS,
|
||||
data };
|
||||
}
|
||||
|
||||
|
||||
function fetchBillingError(data) {
|
||||
return { type: types.FETCH_BILLING_ERROR,
|
||||
data };
|
||||
}
|
||||
|
||||
function beginFetchInvoice() {
|
||||
return { type: types.FETCH_INVOICE };
|
||||
}
|
||||
|
||||
function fetchInvoiceSuccess(data) {
|
||||
return { type: types.FETCH_INVOICE_SUCCESS,
|
||||
data };
|
||||
}
|
||||
|
||||
|
||||
function fetchInvoiceError(data) {
|
||||
return { type: types.FETCH_INVOICE_ERROR,
|
||||
data };
|
||||
}
|
||||
|
||||
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 updateCard(data) {
|
||||
return dispatch => {
|
||||
dispatch(beginUpdateCard());
|
||||
|
||||
return makeUserRequest("post", data, "/api/customer/", {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
|
||||
|
||||
dispatch(updateCardSuccess(response.data));
|
||||
} else {
|
||||
dispatch(updateCardError());
|
||||
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 fetchBilling() {
|
||||
return (dispatch, getState) => {
|
||||
|
||||
dispatch(beginFetchBilling());
|
||||
|
||||
return axios
|
||||
.get("/api/billing", {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
dispatch(fetchBillingSuccess(response.data));
|
||||
} else {
|
||||
dispatch(fetchBillingError());
|
||||
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 fetchInvoices() {
|
||||
return (dispatch, getState) => {
|
||||
|
||||
dispatch(beginFetchInvoice());
|
||||
|
||||
return axios
|
||||
.get("/api/invoices", {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
dispatch(fetchInvoiceSuccess(response.data));
|
||||
} else {
|
||||
dispatch(fetchInvoiceError());
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
11
account/src/client/Billing/billing-constants.js
Normal file
11
account/src/client/Billing/billing-constants.js
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
export const UPDATE_CARD = "UPDATE_CARD"
|
||||
export const UPDATE_CARD_SUCCESS = "UPDATE_CARD_SUCCESS"
|
||||
export const UPDATE_CARD_ERROR = "UPDATE_CARD_ERROR"
|
||||
export const FETCH_BILLING = "FETCH_BILLING"
|
||||
export const FETCH_BILLING_SUCCESS = "FETCH_BILLING_SUCCESS"
|
||||
export const FETCH_BILLING_ERROR = "FETCH_BILLING_ERROR"
|
||||
export const FETCH_INVOICE = "FETCH_INVOICE"
|
||||
export const FETCH_INVOICE_SUCCESS = "FETCH_INVOICE_SUCCESS"
|
||||
export const FETCH_INVOICE_ERROR = "FETCH_INVOICE_ERROR"
|
||||
|
||||
71
account/src/client/Billing/billing-reducer.js
Normal file
71
account/src/client/Billing/billing-reducer.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import {
|
||||
UPDATE_CARD,
|
||||
UPDATE_CARD_SUCCESS,
|
||||
UPDATE_CARD_ERROR,
|
||||
FETCH_BILLING,
|
||||
FETCH_BILLING_SUCCESS,
|
||||
FETCH_BILLING_ERROR,
|
||||
FETCH_INVOICE,
|
||||
FETCH_INVOICE_SUCCESS,
|
||||
FETCH_INVOICE_ERROR
|
||||
} from "./billing-constants";
|
||||
|
||||
function updateObject(oldObject, newValues) {
|
||||
// Encapsulate the idea of passing a new object as the first parameter
|
||||
// to Object.assign to ensure we correctly copy data instead of mutating
|
||||
return Object.assign({}, oldObject, newValues);
|
||||
}
|
||||
|
||||
function updateObjectInArray(array, shortName, updatedItem) {
|
||||
return array.map( item => {
|
||||
if(item.shortName !== shortName) {
|
||||
// This isn't the item we care about - keep it as-is
|
||||
return item;
|
||||
}
|
||||
|
||||
// Otherwise, this is the one we want - return an updated value
|
||||
return {
|
||||
...item,
|
||||
...updatedItem
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const billing = (
|
||||
state = {
|
||||
isWaiting: false,
|
||||
source: false,
|
||||
invoices: []},
|
||||
action
|
||||
) => {
|
||||
switch (action.type) {
|
||||
|
||||
case UPDATE_CARD:
|
||||
case FETCH_BILLING:
|
||||
case FETCH_INVOICE:
|
||||
return Object.assign({}, state, { isWaiting: true});
|
||||
|
||||
|
||||
case UPDATE_CARD_SUCCESS:
|
||||
return Object.assign({}, state, { source: action.data.source, isWaiting: false});
|
||||
|
||||
case FETCH_BILLING_SUCCESS:
|
||||
return Object.assign({}, state, { source: action.data.source, isWaiting: false});
|
||||
|
||||
case FETCH_INVOICE_SUCCESS:
|
||||
return Object.assign({}, state, { invoices: action.data.invoices, isWaiting: false});
|
||||
|
||||
case UPDATE_CARD_ERROR:
|
||||
case FETCH_BILLING_ERROR:
|
||||
case FETCH_INVOICE_ERROR:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false
|
||||
});
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default billing;
|
||||
140
account/src/client/Plan/ListPlans.js
Normal file
140
account/src/client/Plan/ListPlans.js
Normal file
@@ -0,0 +1,140 @@
|
||||
import React, { Component } from "react";
|
||||
|
||||
import {
|
||||
Container,
|
||||
Divider,
|
||||
Checkbox,
|
||||
Button,
|
||||
Header,
|
||||
Table, Form
|
||||
} from "semantic-ui-react";
|
||||
// ----------------------------------------------------
|
||||
|
||||
|
||||
function planPrice(planType) {
|
||||
switch(planType) {
|
||||
case freePlanValue:
|
||||
return freePlanPrice;
|
||||
case proPlanValue:
|
||||
return proPlanPrice;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ListPlans extends Component {
|
||||
|
||||
|
||||
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleCancel = this.handleCancel.bind(this);
|
||||
this.handleUpdate = this.handleUpdate.bind(this);
|
||||
this.handleInputChange = this.handleInputChange.bind(this);
|
||||
var plans = {};
|
||||
var prices = {};
|
||||
var total = 0;
|
||||
|
||||
this.props.systems.forEach(system => {
|
||||
plans[system.shortName] = system.planType;
|
||||
prices[system.shortName] = planPrice(system.planType);
|
||||
total += planPrice(system.planType);
|
||||
});
|
||||
this.state = { plans: plans, prices: prices, total: total };
|
||||
}
|
||||
|
||||
|
||||
handleCancel() {
|
||||
var obj = {}
|
||||
this.props.systems.forEach(system => {
|
||||
obj[system.shortName] = system.planType;
|
||||
});
|
||||
this.setState({ plans: obj });
|
||||
}
|
||||
handleUpdate() {
|
||||
this.props.onUpdate(this.state.plans);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
}
|
||||
|
||||
handleInputChange(e, { name, value }) {
|
||||
var plans = this.state.plans;
|
||||
var prices = this.state.prices;
|
||||
var total = 0;
|
||||
plans[name] = value;
|
||||
prices[name] = planPrice(value);
|
||||
for (var system in prices) {
|
||||
total += prices[system];
|
||||
}
|
||||
this.setState({ plans: plans, prices: prices, total: total });
|
||||
}
|
||||
//https://stackoverflow.com/questions/36559661/how-can-i-dispatch-from-child-components-in-react-redux
|
||||
//https://stackoverflow.com/questions/42597602/react-onclick-pass-event-with-parameter
|
||||
render() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table definition>
|
||||
<Table.Header>
|
||||
|
||||
<Table.Row>
|
||||
<Table.HeaderCell />
|
||||
<Table.HeaderCell textAlign='center'>Free</Table.HeaderCell>
|
||||
<Table.HeaderCell textAlign='center'>Pro</Table.HeaderCell>
|
||||
<Table.HeaderCell textAlign='center'>Monthly Price</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<tbody>
|
||||
{this.props.systems.map((system) =>
|
||||
|
||||
<Table.Row key={system.shortName}>
|
||||
<Table.Cell>
|
||||
<Header as='h4'>
|
||||
<Header.Content>
|
||||
{system.name}
|
||||
<Header.Subheader>{system.shortName}</Header.Subheader>
|
||||
</Header.Content>
|
||||
|
||||
</Header>
|
||||
</Table.Cell>
|
||||
<Table.Cell textAlign='center'>
|
||||
<Checkbox
|
||||
radio
|
||||
value={0}
|
||||
name={system.shortName}
|
||||
checked={this.state.plans[system.shortName] === 0}
|
||||
onChange={this.handleInputChange}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell textAlign='center'>
|
||||
<Checkbox
|
||||
radio
|
||||
value={10}
|
||||
name={system.shortName}
|
||||
checked={this.state.plans[system.shortName] === 10}
|
||||
onChange={this.handleInputChange}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell textAlign='center'>${this.state.prices[system.shortName]}</Table.Cell>
|
||||
</Table.Row>
|
||||
|
||||
)}
|
||||
<Table.Row key="total">
|
||||
<Table.Cell></Table.Cell>
|
||||
<Table.Cell></Table.Cell>
|
||||
<Table.Cell textAlign='right'>Estimated Monthly Total:</Table.Cell>
|
||||
<Table.Cell textAlign='center'>${this.state.total}</Table.Cell>
|
||||
</Table.Row>
|
||||
</tbody>
|
||||
</Table>
|
||||
<Button.Group floated="right">
|
||||
<Button content='Cancel' onClick={this.handleCancel} />
|
||||
<Button content='Update' onClick={this.handleUpdate} primary />
|
||||
</Button.Group>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ListPlans;
|
||||
131
account/src/client/Plan/UpdatePlans.js
Normal file
131
account/src/client/Plan/UpdatePlans.js
Normal file
@@ -0,0 +1,131 @@
|
||||
import React, { Component } from "react";
|
||||
import {
|
||||
Container,
|
||||
Divider,
|
||||
Modal,
|
||||
Button,
|
||||
Segment,
|
||||
Icon,
|
||||
Link,
|
||||
Header, Form, List} from "semantic-ui-react";
|
||||
import ListPlans from "./ListPlans"
|
||||
|
||||
|
||||
function planName(planType) {
|
||||
switch(planType) {
|
||||
case freePlanValue:
|
||||
return "Free";
|
||||
case proPlanValue:
|
||||
return "Pro";
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
class UpdatePlans extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleUpdate = this.handleUpdate.bind(this);
|
||||
this.handleOk = this.handleOk.bind(this);
|
||||
this.handleCancel = this.handleCancel.bind(this);
|
||||
this.findPlanChanges = this.findPlanChanges.bind(this);
|
||||
}
|
||||
|
||||
state = { modalOpen: false, updatedPlans: {}, changes: [] }
|
||||
|
||||
showModal = () => this.setState({ modalOpen: true })
|
||||
closeModal = () => this.setState({ modalOpen: false })
|
||||
|
||||
findPlanChanges(plans) {
|
||||
var changes = [];
|
||||
const systems = this.props.system.items;
|
||||
systems.forEach(system => {
|
||||
const newPlan = plans[system.shortName];
|
||||
if (newPlan != system.planType) {
|
||||
changes.push( system.name + " - " + planName(newPlan));
|
||||
}
|
||||
});
|
||||
return changes;
|
||||
}
|
||||
|
||||
|
||||
handleOk(){
|
||||
this.props.updatePlan(this.state.updatedPlans);
|
||||
this.closeModal();
|
||||
}
|
||||
|
||||
handleCancel() {
|
||||
this.setState({updatedPlans: {}});
|
||||
this.closeModal();
|
||||
}
|
||||
|
||||
handleUpdate(plans) {
|
||||
const changes = this.findPlanChanges(plans);
|
||||
|
||||
if (Array.isArray(changes) && changes.length) {
|
||||
var result = Object.keys(plans).map(function(key) {
|
||||
return {shortName: key, planType: plans[key]};
|
||||
});
|
||||
this.setState({updatedPlans: result, changes: changes});
|
||||
this.showModal()
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchSystems();
|
||||
}
|
||||
|
||||
//https://stackoverflow.com/questions/36559661/how-can-i-dispatch-from-child-components-in-react-redux
|
||||
//https://stackoverflow.com/questions/42597602/react-onclick-pass-event-with-parameter
|
||||
render() {
|
||||
const systems = this.props.system.items;
|
||||
const billingDefined = this.props.user.billing;
|
||||
return (
|
||||
<div>
|
||||
<Container text>
|
||||
<Modal size="tiny" open={this.state.modalOpen} onClose={this.closeModal}>
|
||||
<Modal.Header>Update Plans</Modal.Header>
|
||||
<Modal.Content>
|
||||
|
||||
<Header as='h4'>The following changes will be made:</Header>
|
||||
<List as='ul'>
|
||||
{this.state.changes.map((change) =>
|
||||
<List.Item as='li' content={change}/>
|
||||
)}
|
||||
</List>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button onClick={this.handleCancel} >
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={this.handleOk} primary>
|
||||
OK
|
||||
</Button>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
<Form>
|
||||
<Header as="h1">System Plans</Header>
|
||||
|
||||
<Divider />
|
||||
{billingDefined? (
|
||||
<ListPlans systems={systems} onUpdate={this.handleUpdate} key={systems.length}/>
|
||||
) : (
|
||||
<Segment>
|
||||
<Header icon>
|
||||
<Icon name='credit card outline' />
|
||||
You need to add a Payment Method before you can upgrade a Plan
|
||||
</Header>
|
||||
<Button primary
|
||||
to='/billing'>Add Payment Method</Button>
|
||||
</Segment>
|
||||
)}
|
||||
</Form>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UpdatePlans;
|
||||
19
account/src/client/Plan/UpdatePlansContainer.js
Normal file
19
account/src/client/Plan/UpdatePlansContainer.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { connect } from "react-redux"
|
||||
import * as systemActions from "../System/system-actions"
|
||||
import * as billingActions from "../Billing/billing-actions"
|
||||
import UpdatePlans from "./UpdatePlans"
|
||||
import { bindActionCreators } from 'redux'
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
system: state.system,
|
||||
user: state.user
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
systemActions
|
||||
)(UpdatePlans)
|
||||
156
account/src/client/System/system-actions.js
Normal file
156
account/src/client/System/system-actions.js
Normal file
@@ -0,0 +1,156 @@
|
||||
import axios from "axios";
|
||||
//import { browserHistory } from "react-router"
|
||||
import * as types from "./system-constants";
|
||||
import { push } from 'connected-react-router';
|
||||
|
||||
|
||||
function beginUpdateSystem() {
|
||||
return { type: types.UPDATE_SYSTEM };
|
||||
}
|
||||
|
||||
function updateSystemSuccess(data) {
|
||||
return { type: types.UPDATE_SUCCESS_SYSTEM, data };
|
||||
}
|
||||
|
||||
function updateSystemError(data) {
|
||||
return { type: types.UPDATE_ERROR_SYSTEM, data };
|
||||
}
|
||||
|
||||
|
||||
function beginFetchSystem() {
|
||||
return { type: types.FETCH_SYSTEM };
|
||||
}
|
||||
|
||||
function fetchSystemSuccess(data) {
|
||||
return { type: types.FETCH_SUCCESS_SYSTEM,
|
||||
data };
|
||||
}
|
||||
|
||||
|
||||
function fetchSystemError(data) {
|
||||
return { type: types.FETCH_ERROR_SYSTEM,
|
||||
data };
|
||||
}
|
||||
|
||||
function beginUpdatePlan() {
|
||||
return { type: types.UPDATE_PLAN };
|
||||
}
|
||||
|
||||
function updatePlanSuccess(data) {
|
||||
return { type: types.UPDATE_PLAN_SUCCESS,
|
||||
data };
|
||||
}
|
||||
|
||||
|
||||
function updatePlanError(data) {
|
||||
return { type: types.UPDATE_PLAN_ERROR,
|
||||
data };
|
||||
}
|
||||
|
||||
|
||||
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));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function updatePlan(data) {
|
||||
return dispatch => {
|
||||
dispatch(beginUpdatePlan());
|
||||
|
||||
return makeUserRequest("post", {updatePlans:data}, account_server + "/api/plans/", {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
dispatch(updatePlanSuccess(response.data.systems));
|
||||
} else {
|
||||
dispatch(updatePlanError());
|
||||
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);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Example of an Async Action Creator
|
||||
// http://redux.js.org/docs/advanced/AsyncActions.html
|
||||
|
||||
export function updateSystem(data) {
|
||||
return dispatch => {
|
||||
dispatch(beginUpdateSystem());
|
||||
|
||||
return makeUserRequest("post", data, admin_server + "/systems/" + data.shortName, {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
const user = response.data.user
|
||||
|
||||
dispatch(updateSystemSuccess(response.data.system));
|
||||
dispatch(push("/system/"+data.shortName));
|
||||
} 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);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function shouldFetchSystems(state, subreddit) {
|
||||
if ((!state.system.items) || (state.system.items.length <1)) {
|
||||
return true
|
||||
} else if (state.system.isWaiting) {
|
||||
return false
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function fetchSystems() {
|
||||
return (dispatch, getState) => {
|
||||
if (shouldFetchSystems(getState())) {
|
||||
dispatch(beginFetchSystem());
|
||||
|
||||
return axios
|
||||
.get(admin_server + "/systems", {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
dispatch(fetchSystemSuccess(response.data.systems));
|
||||
} else {
|
||||
dispatch(fetchSystemError());
|
||||
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);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
10
account/src/client/System/system-constants.js
Normal file
10
account/src/client/System/system-constants.js
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
export const UPDATE_SYSTEM = "UPDATE_SYSTEM"
|
||||
export const UPDATE_SUCCESS_SYSTEM = "UPDATE_SUCCESS_SYSTEM"
|
||||
export const UPDATE_ERROR_SYSTEM = "UPDATE_ERROR_SYSTEM"
|
||||
export const FETCH_SYSTEM = "FETCH_SYSTEM"
|
||||
export const FETCH_SUCCESS_SYSTEM = "FETCH_SUCCESS_SYSTEM"
|
||||
export const FETCH_ERROR_SYSTEM = "FETCH_ERROR_SYSTEM"
|
||||
export const UPDATE_PLAN = "UPDATE_PLAN"
|
||||
export const UPDATE_PLAN_SUCCESS = "UPDATE_PLAN_SUCCESS"
|
||||
export const UPDATE_PLAN_ERROR = "UPDATE_PLAN_ERROR"
|
||||
78
account/src/client/System/system-reducer.js
Normal file
78
account/src/client/System/system-reducer.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
UPDATE_SYSTEM,
|
||||
UPDATE_SUCCESS_SYSTEM,
|
||||
UPDATE_ERROR_SYSTEM,
|
||||
FETCH_SYSTEM,
|
||||
FETCH_SUCCESS_SYSTEM,
|
||||
FETCH_ERROR_SYSTEM,
|
||||
UPDATE_PLAN,
|
||||
UPDATE_PLAN_SUCCESS,
|
||||
UPDATE_PLAN_ERROR
|
||||
} from "./system-constants";
|
||||
|
||||
function updateObject(oldObject, newValues) {
|
||||
// Encapsulate the idea of passing a new object as the first parameter
|
||||
// to Object.assign to ensure we correctly copy data instead of mutating
|
||||
return Object.assign({}, oldObject, newValues);
|
||||
}
|
||||
|
||||
function updateObjectInArray(array, shortName, updatedItem) {
|
||||
return array.map( item => {
|
||||
if(item.shortName !== shortName) {
|
||||
// This isn't the item we care about - keep it as-is
|
||||
return item;
|
||||
}
|
||||
|
||||
// Otherwise, this is the one we want - return an updated value
|
||||
return {
|
||||
...item,
|
||||
...updatedItem
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const system = (
|
||||
state = {
|
||||
isWaiting: false,
|
||||
items: []},
|
||||
action
|
||||
) => {
|
||||
switch (action.type) {
|
||||
|
||||
case UPDATE_SYSTEM:
|
||||
case FETCH_SYSTEM:
|
||||
case UPDATE_PLAN:
|
||||
|
||||
return Object.assign({}, state, { isWaiting: true});
|
||||
|
||||
|
||||
case UPDATE_PLAN_SUCCESS:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false,
|
||||
items: action.data
|
||||
});
|
||||
|
||||
case UPDATE_SUCCESS_SYSTEM:
|
||||
return Object.assign({}, state, {
|
||||
items: updateObjectInArray(state.items, action.data.shortName, action.data)
|
||||
});
|
||||
case FETCH_SUCCESS_SYSTEM:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false,
|
||||
items: action.data
|
||||
});
|
||||
|
||||
case UPDATE_PLAN_ERROR:
|
||||
case UPDATE_ERROR_SYSTEM:
|
||||
case FETCH_ERROR_SYSTEM:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false
|
||||
});
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default system;
|
||||
81
account/src/client/User/ConfirmEmail.js
Normal file
81
account/src/client/User/ConfirmEmail.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import React, { Component } from "react";
|
||||
import {
|
||||
Container,
|
||||
Header,
|
||||
Dimmer,
|
||||
Loader,
|
||||
Button,
|
||||
Message,
|
||||
Icon
|
||||
} from "semantic-ui-react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
class ConfirmEmail extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
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;
|
||||
14
account/src/client/User/ConfirmEmailContainer.js
Normal file
14
account/src/client/User/ConfirmEmailContainer.js
Normal 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)
|
||||
196
account/src/client/User/Login.js
Normal file
196
account/src/client/User/Login.js
Normal file
@@ -0,0 +1,196 @@
|
||||
import React, { PropTypes, Component } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import {
|
||||
Container,
|
||||
Header,
|
||||
Form,
|
||||
Grid,
|
||||
Segment,
|
||||
Input,
|
||||
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 "admin":
|
||||
success.nextLocation = admin_server;
|
||||
break;
|
||||
case "frontend":
|
||||
success.nextLocation = frontend_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 = "";
|
||||
const { name, email } = this.state;
|
||||
|
||||
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">{site_name}</Header>
|
||||
<Grid stackable >
|
||||
<Grid.Row>
|
||||
<Grid.Column width={9}>
|
||||
|
||||
<Header size="large">Let's get this started!</Header>
|
||||
<Header size="small">
|
||||
{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:{admin_email}?Subject={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;
|
||||
36
account/src/client/User/LoginContainer.js
Normal file
36
account/src/client/User/LoginContainer.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { connect } from "react-redux"
|
||||
import { push } from 'react-router-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)
|
||||
45
account/src/client/User/Profile.js
Normal file
45
account/src/client/User/Profile.js
Normal 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
|
||||
15
account/src/client/User/ProfileContainer.js
Normal file
15
account/src/client/User/ProfileContainer.js
Normal 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)
|
||||
71
account/src/client/User/Register.js
Normal file
71
account/src/client/User/Register.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import React, { Component } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import {
|
||||
Container,
|
||||
Header,
|
||||
Form,
|
||||
Grid,
|
||||
Segment,
|
||||
Input,
|
||||
Button,
|
||||
Message,
|
||||
Icon
|
||||
} from "semantic-ui-react";
|
||||
|
||||
import UserForm from "./UserForm";
|
||||
|
||||
// ----------------------------------------------------
|
||||
const requestMessageStyle = {
|
||||
color: "red"
|
||||
};
|
||||
|
||||
// ----------------------------------------------------
|
||||
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;
|
||||
14
account/src/client/User/RegisterContainer.js
Normal file
14
account/src/client/User/RegisterContainer.js
Normal 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)
|
||||
113
account/src/client/User/ResetPassword.js
Normal file
113
account/src/client/User/ResetPassword.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import React, { Component } from "react";
|
||||
import {
|
||||
Container,
|
||||
Header,
|
||||
Form,
|
||||
Grid,
|
||||
Segment,
|
||||
Input,
|
||||
Button,
|
||||
Message,
|
||||
Icon
|
||||
} 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
|
||||
type="text"
|
||||
name="password"
|
||||
type="password"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.passwordError}
|
||||
label="Password"
|
||||
placeholder="Password..."
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
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;
|
||||
14
account/src/client/User/ResetPasswordContainer.js
Normal file
14
account/src/client/User/ResetPasswordContainer.js
Normal 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)
|
||||
115
account/src/client/User/SendResetPassword.js
Normal file
115
account/src/client/User/SendResetPassword.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import React, { Component } from "react";
|
||||
import {
|
||||
Container,
|
||||
Header,
|
||||
Form,
|
||||
Grid,
|
||||
Segment,
|
||||
Input,
|
||||
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;
|
||||
14
account/src/client/User/SendResetPasswordContainer.js
Normal file
14
account/src/client/User/SendResetPasswordContainer.js
Normal 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)
|
||||
36
account/src/client/User/SentConfirmEmail.js
Normal file
36
account/src/client/User/SentConfirmEmail.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React, { Component } from "react"
|
||||
import {
|
||||
Container,
|
||||
Header,
|
||||
Form,
|
||||
Grid,
|
||||
Segment,
|
||||
Input,
|
||||
Button,
|
||||
Message,
|
||||
Icon
|
||||
} from "semantic-ui-react";
|
||||
|
||||
class SentConfirmEmail extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
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
|
||||
14
account/src/client/User/SentConfirmEmailContainer.js
Normal file
14
account/src/client/User/SentConfirmEmailContainer.js
Normal 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)
|
||||
83
account/src/client/User/Terms.js
Normal file
83
account/src/client/User/Terms.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import React, { Component } from "react";
|
||||
import { Segment,
|
||||
TextArea,
|
||||
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;
|
||||
14
account/src/client/User/TermsContainer.js
Normal file
14
account/src/client/User/TermsContainer.js
Normal 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)
|
||||
292
account/src/client/User/UserForm.js
Normal file
292
account/src/client/User/UserForm.js
Normal file
@@ -0,0 +1,292 @@
|
||||
import React, { Component } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import {
|
||||
Container,
|
||||
Header,
|
||||
Form,
|
||||
Grid,
|
||||
Dimmer,
|
||||
Loader,
|
||||
Segment,
|
||||
Input,
|
||||
Button,
|
||||
Message,
|
||||
Icon
|
||||
} from "semantic-ui-react";
|
||||
|
||||
// ----------------------------------------------------
|
||||
const checkInputMessagestyle = {
|
||||
color: "red"
|
||||
};
|
||||
|
||||
// ----------------------------------------------------
|
||||
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 { name, email } = this.state;
|
||||
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
|
||||
type="text"
|
||||
name="password"
|
||||
type="password"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.passwordError}
|
||||
disabled={isEditing}
|
||||
label="Password"
|
||||
placeholder="Password..."
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
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;
|
||||
53
account/src/client/User/WaitConfirmEmail.js
Normal file
53
account/src/client/User/WaitConfirmEmail.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import React, { Component } from "react"
|
||||
import { Link } from 'react-router-dom'
|
||||
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
|
||||
14
account/src/client/User/WaitConfirmEmailContainer.js
Normal file
14
account/src/client/User/WaitConfirmEmailContainer.js
Normal 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)
|
||||
391
account/src/client/User/user-actions.js
Normal file
391
account/src/client/User/user-actions.js
Normal file
@@ -0,0 +1,391 @@
|
||||
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 => {
|
||||
dispatch(beginAuthenticated());
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
31
account/src/client/User/user-constants.js
Normal file
31
account/src/client/User/user-constants.js
Normal 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"
|
||||
139
account/src/client/User/user-reducer.js
Normal file
139
account/src/client/User/user-reducer.js
Normal 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;
|
||||
95
account/src/client/client.js
Normal file
95
account/src/client/client.js
Normal file
@@ -0,0 +1,95 @@
|
||||
import React from "react"
|
||||
import { render } from "react-dom"
|
||||
import { Provider } from "react-redux"
|
||||
import { Switch, Route} from 'react-router-dom'
|
||||
import { createStore, combineReducers, applyMiddleware } from 'redux'
|
||||
import thunk from "redux-thunk"
|
||||
import {createLogger} from "redux-logger"
|
||||
//import { ConnectedRouter, routerReducer, routerMiddleware, push } from 'react-router-redux'
|
||||
import { routerMiddleware } from 'connected-react-router'
|
||||
import { ConnectedRouter } from 'connected-react-router'
|
||||
import isNode from "detect-node"
|
||||
//import rootReducer from "./reducers"
|
||||
import createRootReducer from './reducers'
|
||||
import { createHashHistory } from 'history'
|
||||
import createHistory from 'history/createBrowserHistory'
|
||||
|
||||
import App from "./components/container/App"
|
||||
import LoginContainer from "./User/LoginContainer"
|
||||
import RegisterContainer from "./User/RegisterContainer"
|
||||
import ProfileContainer from "./User/ProfileContainer"
|
||||
import BillingContainer from "./Billing/BillingContainer"
|
||||
import Default from "./components/pure/Default"
|
||||
import NavigationContainer from "./components/container/NavigationContainer"
|
||||
import ConfirmEmailContainer from "./User/ConfirmEmailContainer"
|
||||
import WaitConfirmEmailContainer from "./User/WaitConfirmEmailContainer"
|
||||
import SentConfirmEmailContainer from "./User/SentConfirmEmailContainer"
|
||||
import SendResetPasswordContainer from "./User/SendResetPasswordContainer"
|
||||
import ResetPasswordContainer from "./User/ResetPasswordContainer"
|
||||
import Restricted from "./components/container/Restricted"
|
||||
import TermsContainer from "./User/TermsContainer"
|
||||
import UpdatePlans from "./Plan/UpdatePlansContainer"
|
||||
|
||||
// Create a history of your choosing (we're using a browser history in this case)
|
||||
const history = createHistory()
|
||||
|
||||
// Build the middleware for intercepting and dispatching navigation actions
|
||||
const historyMiddleware = routerMiddleware(history)
|
||||
|
||||
let middleware = [
|
||||
thunk,
|
||||
historyMiddleware
|
||||
|
||||
]
|
||||
|
||||
// only add Redux-Logger on the client-side because it causes problems server-side.
|
||||
if (!isNode) {
|
||||
middleware.push(createLogger())
|
||||
}
|
||||
|
||||
|
||||
// Add the reducer to your store on the `router` key
|
||||
// Also apply our middleware for navigating
|
||||
const store = createStore(
|
||||
createRootReducer(history),
|
||||
applyMiddleware(thunk,
|
||||
historyMiddleware, createLogger())
|
||||
)
|
||||
|
||||
/*
|
||||
<Route path="/system/:shortName" component={Restricted(SystemContainer, store)} />
|
||||
<Route path="/update-system/:shortName" component={Restricted(EditSystemContainer, store)} />
|
||||
<Route path="/create-system" component={Restricted(CreateSystemContainer, store)} />
|
||||
<Route path="/list-systems" component={Restricted(ListSystemsContainer, store)} />
|
||||
|
||||
<Route path="/system/:shortName" component={Restricted(SystemContainer, store)} />
|
||||
<Route path="/update-system/:shortName" component={EditSystemContainer} />
|
||||
<Route path="/create-system" component={CreateSystemContainer} />
|
||||
<Route path="/list-systems" component={ListSystemsContainer} />
|
||||
*/
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<div>
|
||||
<App>
|
||||
<Switch>
|
||||
|
||||
<Route exact path="/" component={Restricted(ProfileContainer, store)} />
|
||||
<Route path="/login" component={LoginContainer} />
|
||||
<Route path="/register" component={RegisterContainer} />
|
||||
<Route path="/wait-confirm-email" component={WaitConfirmEmailContainer} />
|
||||
<Route path="/sent-confirm-email" component={SentConfirmEmailContainer} />
|
||||
<Route path="/send-reset-password" component={SendResetPasswordContainer} />
|
||||
<Route path="/reset-password/:userId/:token" component={ResetPasswordContainer} />
|
||||
<Route path="/confirm-email/:userId/:token" component={ConfirmEmailContainer} />
|
||||
<Route path="/terms" component={Restricted(TermsContainer, store)} />
|
||||
|
||||
<Route path="/billing" component={Restricted(BillingContainer, store)} />
|
||||
<Route path="/profile" component={Restricted(ProfileContainer, store)} />
|
||||
<Route path="/update-plans" component={Restricted(UpdatePlans, store)} />
|
||||
</Switch>
|
||||
</App>
|
||||
</div>
|
||||
</ConnectedRouter>
|
||||
</Provider>
|
||||
, document.getElementById("app"))
|
||||
19
account/src/client/components/container/App.js
Normal file
19
account/src/client/components/container/App.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import React, { Component } from "react"
|
||||
import NavigationContainer from "./NavigationContainer"
|
||||
|
||||
class App extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<div>
|
||||
<NavigationContainer />
|
||||
{this.props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default App
|
||||
@@ -0,0 +1,21 @@
|
||||
import { connect } from "react-redux"
|
||||
import * as userActions from "../../User/user-actions"
|
||||
import Navigation from "../pure/Navigation"
|
||||
|
||||
|
||||
// Function passed in to `connect` to subscribe to Redux store updates.
|
||||
// Any time it updates, mapStateToProps is called.
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
user: state.user
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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
|
||||
)(Navigation)
|
||||
55
account/src/client/components/container/Restricted.js
Normal file
55
account/src/client/components/container/Restricted.js
Normal file
@@ -0,0 +1,55 @@
|
||||
// in src/restricted.js
|
||||
import React, { Component } from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import * as userActions from "../../User/user-actions"
|
||||
import * as types from "../../User/user-constants";
|
||||
import axios from "axios";
|
||||
/**
|
||||
* Higher-order component (HOC) to wrap restricted pages
|
||||
*/
|
||||
export default (BaseComponent, store) => {
|
||||
class Restricted extends Component {
|
||||
componentWillMount() {
|
||||
this.checkAuthentication(this.props);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.location !== this.props.location) {
|
||||
this.checkAuthentication(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
checkAuthentication(params) {
|
||||
const { history } = params;
|
||||
const authenticated = store.getState().user.authenticated;
|
||||
if (!authenticated) {
|
||||
axios
|
||||
.get("/authenticated")
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
const user = response.data.user
|
||||
store.dispatch({
|
||||
type: types.LOGIN_SUCCESS_USER,
|
||||
data: user
|
||||
});
|
||||
} else {
|
||||
history.replace({ pathname: '/login',
|
||||
state: { nextPathname: params.location.pathname } });
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened during logout that triggered an Error
|
||||
history.replace({ pathname: '/login',
|
||||
state: { nextPathname: params.location.pathname } });
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return <BaseComponent {...this.props} />;
|
||||
}
|
||||
}
|
||||
return withRouter(Restricted);
|
||||
}
|
||||
31
account/src/client/components/pure/Default.js
Normal file
31
account/src/client/components/pure/Default.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import React, { Component } from "react"
|
||||
|
||||
class Default extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<div>
|
||||
<h2>React-Passport-Redux-Example</h2>
|
||||
<p>Demonstration of PassportJS authentication in React w/ Redux. Use the links above to register a new user and login/logout. You will only be able to view the MyProfile page when you are logged in. Logging in will redirect you to MyProfile if you had clicked that link beforehand.</p>
|
||||
<p>I'll be doing my best to document code with useful comments so look for more detail there. Note: you should be able to implement the authentication functionality using less packages. I was working on another project before deciding to make this example repo so some bloat may have travelled over from that.</p>
|
||||
<p>In full, this repo demonstrates:</p>
|
||||
<ul>
|
||||
<li><strong>React</strong> for the view layer </li>
|
||||
<li><strong>PassportJS</strong> for authentication (using a Local Strategy)</li>
|
||||
<li><strong>Redux</strong> to handle our state</li>
|
||||
<li><strong>React Router</strong> for routing client-side</li>
|
||||
<li><strong>Express</strong> for handling server requests (REST and page requests)</li>
|
||||
<li><strong>MongoDB</strong> for our database, with <strong>Mongoose</strong> handling our schema</li>
|
||||
<li><strong>Webpack</strong> to bundle our client-side code</li>
|
||||
<li>Hot reloading using <strong>webpack-dev-middleware</strong> and <strong>webpack-hot-middleware</strong></li>
|
||||
</ul>
|
||||
<p>Shoutout to GitHub member <b>choonkending</b> whose <a href="https://github.com/choonkending/react-webpack-node" target="_blank">repo</a> was a big help. Pull requests and comments / issue reports are most welcome!</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Default
|
||||
31
account/src/client/components/pure/Message.js
Normal file
31
account/src/client/components/pure/Message.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import React, { Component } from "react";
|
||||
import { Modal, Icon, Button, Header } from "semantic-ui-react";
|
||||
|
||||
class Message extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
_logout = event => {
|
||||
event.preventDefault();
|
||||
this.props.manualLogout();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal open={this.props.open} onClose={this.props.onClose} basic size="small">
|
||||
<Header icon="info" content={this.props.title} />
|
||||
<Modal.Content>
|
||||
<p>{this.props.message}</p>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button color='green' onClick={this.props.onClose} inverted>
|
||||
<Icon name='checkmark' /> Got it
|
||||
</Button>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Message;
|
||||
57
account/src/client/components/pure/Navigation.js
Normal file
57
account/src/client/components/pure/Navigation.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import React, { Component } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Menu, Dropdown } from "semantic-ui-react";
|
||||
|
||||
const navStyle = {
|
||||
marginBottom: "30px"
|
||||
};
|
||||
const blankStyle = {
|
||||
marginBottom: "50px"
|
||||
};
|
||||
class Navigation extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._logout = this._logout.bind(this);
|
||||
}
|
||||
|
||||
_logout = event => {
|
||||
event.preventDefault();
|
||||
this.props.manualLogout();
|
||||
};
|
||||
|
||||
render() {
|
||||
var profileLink = account_server + "/profile";
|
||||
var billingLink = account_server + "/billing";
|
||||
return (
|
||||
<div>
|
||||
{this.props.user.authenticated ? (
|
||||
<Menu style={navStyle}>
|
||||
<Menu.Item name="systems" header>
|
||||
OpenMHz
|
||||
</Menu.Item>
|
||||
<Menu.Menu position="right">
|
||||
{/* <Link to="/update-plans">
|
||||
<Menu.Item name="systems">Update Plans</Menu.Item>
|
||||
</Link> */}
|
||||
<Dropdown item text={this.props.user.email}>
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item href={profileLink}>Profile</Dropdown.Item>
|
||||
{/* <Dropdown.Item href={billingLink}>Billing</Dropdown.Item> */}
|
||||
<Dropdown.Item onClick={this._logout}>Logout</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</Menu.Menu>
|
||||
</Menu>
|
||||
) : (
|
||||
<Menu style={navStyle}>
|
||||
<Menu.Item name="systems" header>
|
||||
OpenMHz
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Navigation;
|
||||
5
account/src/client/components/style/style.css
Normal file
5
account/src/client/components/style/style.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.field {
|
||||
padding-top: 10px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
11
account/src/client/query-string.js
Normal file
11
account/src/client/query-string.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export default {
|
||||
parse: function parse(queryString) {
|
||||
var query = {};
|
||||
var pairs = (queryString[0] === '?' ? queryString.substr(1) : queryString).split('&');
|
||||
for (var i = 0; i < pairs.length; i++) {
|
||||
var pair = pairs[i].split('=');
|
||||
query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || '');
|
||||
}
|
||||
return query;
|
||||
}
|
||||
}
|
||||
12
account/src/client/reducers/index.js
Normal file
12
account/src/client/reducers/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { combineReducers } from "redux"
|
||||
import { connectRouter } from 'connected-react-router'
|
||||
import user from "../User/user-reducer"
|
||||
import system from "../System/system-reducer"
|
||||
import billing from "../Billing/billing-reducer"
|
||||
|
||||
export default (history) => combineReducers({
|
||||
router: connectRouter(history),
|
||||
user,
|
||||
system,
|
||||
billing
|
||||
})
|
||||
21
account/src/client/stories/index.js
Normal file
21
account/src/client/stories/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { Button } from '@storybook/react/demo';
|
||||
import InvoiceList from '../Billing/InvoiceList';
|
||||
import 'semantic-ui-css/semantic.min.css'
|
||||
|
||||
storiesOf('Button', module)
|
||||
.add('with text', () => (
|
||||
<Button>Hello Button</Button>
|
||||
))
|
||||
.add('with some emoji', () => (
|
||||
<Button><span role="img" aria-label="so cool">😀 😎 👍 💯</span></Button>
|
||||
));
|
||||
|
||||
|
||||
export const invoices = [{"amount_due":1500,"amount_paid":1500,"date":1548819212,"invoice_pdf":"https://pay.stripe.com/invoice/invst_BYlZ3MlhYMHEStbvdVJ6vddxgp/pdf","paid":true,"status":"paid","description":null},{"amount_due":1500,"amount_paid":1500,"date":1548819083,"invoice_pdf":"https://pay.stripe.com/invoice/invst_ts0VnXKuxHWHce02yjhu0Cz455/pdf","paid":true,"status":"paid","description":null},{"amount_due":1500,"amount_paid":1500,"date":1548818978,"invoice_pdf":"https://pay.stripe.com/invoice/invst_HSjke6BJAWGlZL8ggn3Jgb8pnT/pdf","paid":true,"status":"paid","description":null},{"amount_due":6500,"amount_paid":6500,"date":1548816924,"invoice_pdf":"https://pay.stripe.com/invoice/invst_joLNjLFGU3PIondKAAop6kJaSZ/pdf","paid":true,"status":"paid","description":null},{"amount_due":1500,"amount_paid":1500,"date":1548772157,"invoice_pdf":"https://pay.stripe.com/invoice/invst_cSWcbq3UKUHxPQChSjkxRKx6gO/pdf","paid":true,"status":"paid","description":null},{"amount_due":1500,"amount_paid":1500,"date":1548772077,"invoice_pdf":"https://pay.stripe.com/invoice/invst_1aMYlwnPOdXKbebGuCjQfUrsg2/pdf","paid":true,"status":"paid","description":null}];
|
||||
|
||||
storiesOf('InvoiceList', module).add("standard", () => {
|
||||
return (<InvoiceList invoices={invoices}/>)
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user