adding Admin server

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

View File

@@ -1,12 +0,0 @@
{
"presets":
["@babel/preset-env",
"@babel/react"],
"plugins": [
"@babel/plugin-proposal-class-properties",
["@babel/plugin-transform-runtime", {
"regenerator": true
}],
["lodash", { "id": ["lodash", "semantic-ui-react"] }]
]
}

View File

@@ -1,3 +1 @@
node_modules
lib
public
/node_modules

12
account/.env Normal file
View File

@@ -0,0 +1,12 @@
REACT_APP_BACKEND_SERVER=https://api.openmhz.com
REACT_APP_FRONTEND_SERVER=https://openmhz.com
REACT_APP_SOCKET_SERVER=wss://api.openmhz.com
REACT_APP_ACCOUNT_SERVER=https://account.openmhz.com
REACT_APP_ADMIN_SERVER=localhost:3000
REACT_APP_ADMIN_EMAIL=luke@robotastic.com
REACT_APP_SITE_NAME=OpenMHz
REACT_APP_ARCHIVE_DAYS=30
REACT_APP_PRO_PLAN=1
REACT_APP_FREE_PLAN=0
REACT_APP_PRO_PLAN_ARCHIVE=30
REACT_APP_FREE_PLAN_ARCHIVE=7

1
account/.eslintcache Normal file

File diff suppressed because one or more lines are too long

60
account/.gitignore vendored
View File

@@ -1,37 +1,23 @@
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
lib/
uploads/
# Dependency directory
node_modules
assets
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@@ -1,8 +0,0 @@
import { configure } from '@storybook/react';
function loadStories() {
require('../src/client/stories/index.js');
// You can require as many stories as you need.
}
configure(loadStories, module);

View File

@@ -1,21 +1,20 @@
# docker build -t trunk-server-frontend .
# do a `git pull` in trunk-server-frontend to update
FROM node:8.11
ENV HOME=/home/app
RUN mkdir -p /home/app
COPY ./package.json /tmp
RUN cd /tmp && npm install
RUN cp -a /tmp/node_modules /home/app
# add `/usr/src/app/node_modules/.bin` to $PATH
ENV PATH /usr/app/node_modules/.bin:$PATH
WORKDIR $HOME
COPY . $HOME/
# build environment
FROM node:13.12.0-alpine as build
WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH
COPY package.json ./
RUN npm install
RUN npm install react-scripts@3.4.1 -g --silent
COPY . ./
RUN npm run build
CMD ["npm", "run", "start"]
# production environment
FROM node:13.12.0-alpine
RUN mkdir -p /app/public
COPY ./package.json /tmp
RUN cd /tmp && npm install
RUN cp -a /tmp/node_modules /app
WORKDIR /app
COPY server /app
COPY --from=build /app/build /app/public
CMD ["node", "index.js"]

68
account/README.md Normal file
View File

@@ -0,0 +1,68 @@
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app), using the [Redux](https://redux.js.org/) and [Redux Toolkit](https://redux-toolkit.js.org/) template.
## Available Scripts
In the project directory, you can run:
### `yarn start`
Runs the app in the development mode.<br />
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br />
You will also see any lint errors in the console.
### `yarn test`
Launches the test runner in the interactive watch mode.<br />
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `yarn build`
Builds the app for production to the `build` folder.<br />
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br />
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `yarn eject`
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
### Analyzing the Bundle Size
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
### Making a Progressive Web App
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
### Advanced Configuration
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
### Deployment
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
### `yarn build` fails to minify
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify

View File

@@ -1,10 +0,0 @@
/*
Called during development only. Allows us to use ES6 and JSX in our server code.
*/
require('@babel/register')({
presets: ["@babel/preset-env",
"@babel/react"]
});
require("./src/server/index")

10
account/notes.md Normal file
View File

@@ -0,0 +1,10 @@
## Base
start using `npx create-react-app account --template redux`
## Packages used:
- [connected-react-router](https://github.com/supasate/connected-react-router): Synchronize router state with redux store through
- [Redux Thunk](https://github.com/reduxjs/redux-thunk): lets you do AJAX calls
- [axios](https://github.com/axios/axios) - library for doing HTTP requests
- [react-router-dom](https://reactrouter.com/web/guides/quick-start) - Adds routes to React
- [Semantic UI](https://react.semantic-ui.com/usage) - The UI frontend

30600
account/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,90 +1,52 @@
{
"name": "react-passport-redux-example",
"version": "1.0.0",
"description": "Demonstration of PassportJS authentication in React w/ Redux.",
"main": "devStart.js",
"scripts": {
"build:lib": "babel src/server --out-dir lib/server",
"build:umd": "webpack --config webpack/webpack.config.dev.js",
"build:umd:min": "webpack --config webpack/webpack.config.prod.js",
"build": "npm run clean && npm run build:lib && npm run build:umd && npm run build:umd:min",
"postinstall": "npm run build",
"clean": "rimraf lib public/assets",
"server": "better-npm-run server",
"server:dev": "better-npm-run server:dev",
"start": "npm run server",
"start:dev": "npm run server:dev",
"storybook": "start-storybook -p 9001 -c .storybook"
},
"betterScripts": {
"server": {
"command": "node lib/server/index.js",
"env": {
"NODE_ENV": "production",
"MIN_EXT": ".min"
}
},
"server:dev": {
"command": "node devStart.js",
"env": {
"NODE_ENV": "development"
}
}
},
"dependencies": {
"@babel/runtime": "7.3.1",
"axios": "0.18.0",
"bcrypt-nodejs": "0.0.3",
"better-npm-run": "0.1.1",
"body-parser": "1.18.3",
"connect-mongo": "2.0.3",
"connected-react-router": "6.3.1",
"csv-parse": "4.3.3",
"d3-time-format": "2.1.3",
"express": "4.16.4",
"express-session": "1.15.6",
"fs-extra": "7.0.1",
"lodash": "4.17.11",
"mongoose": "5.4.15",
"multer": "1.4.1",
"node-mailjet": "3.3.1",
"node-uuid": "1.4.8",
"passport": "0.4.0",
"passport-local": "1.0.0",
"react": "16.8.3",
"react-dom": "16.8.3",
"react-redux": "6.0.1",
"react-router": "4.3.1",
"react-router-dom": "4.3.1",
"react-router-redux": "5.0.0-alpha.9",
"react-stripe-elements": "2.0.3",
"redux": "4.0.1",
"name": "account",
"version": "0.1.0",
"private": true,
"devDependencies": {
"@reduxjs/toolkit": "^1.1.0",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"axios": "0.21.1",
"connected-react-router": "6.8.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-redux": "^7.1.3",
"react-router-dom": "5.2.0",
"react-scripts": "4.0.1",
"redux-thunk": "2.3.0",
"semantic-ui-css": "2.4.1",
"semantic-ui-react": "0.85.0",
"storybook": "1.0.0",
"stripe": "6.25.1"
"semantic-ui-react": "2.0.1"
},
"devDependencies": {
"@babel/cli": "7.2.3",
"@babel/core": "7.3.3",
"@babel/plugin-proposal-class-properties": "^7.3.0",
"@babel/plugin-proposal-export-default-from": "7.2.0",
"@babel/plugin-proposal-object-rest-spread": "^7.3.1",
"@babel/plugin-transform-runtime": "7.2.0",
"@babel/preset-env": "^7.3.1",
"@babel/preset-react": "^7.0.0",
"@babel/preset-stage-1": "^7.0.0",
"@babel/register": "^7.0.0",
"@storybook/react": "4.1.13",
"babel-loader": "8.0.5",
"babel-plugin-lodash": "3.3.4",
"detect-node": "^2.0.4",
"redux-logger": "3.0.6",
"rimraf": "2.6.3",
"webpack": "4.29.5",
"webpack-cli": "3.2.3",
"webpack-dev-middleware": "^3.5.1",
"webpack-hot-middleware": "^2.24.3"
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"dependencies": {
"bcrypt": "5.0.0",
"connect-mongo": "3.2.0",
"express": "4.17.1",
"express-session": "1.17.1",
"mongoose": "5.11.10",
"node-mailjet": "3.3.1",
"passport": "0.4.1",
"passport-local": "1.0.0"
}
}

BIN
account/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

43
account/public/index.html Normal file
View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React Redux App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
account/public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
account/public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -0,0 +1,2 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *

View File

@@ -1,15 +1,15 @@
import path from "path"
import express from "express"
import session from "express-session"
import bodyParser from "body-parser"
import connectMongo from "connect-mongo"
import secrets from "./secrets"
const path = require("path");
const express = require("express");
const session = require("express-session");
const bodyParser = require("body-parser");
const connectMongo = require("connect-mongo");
const secrets = require("./secrets");
const MongoStore = connectMongo(session)
var cookie_domain = process.env['COOKIE_DOMAIN'] != null ? process.env['COOKIE_DOMAIN'] : '.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
export default function(app, passport) {
module.exports = function(app, passport) {
app.set("port", 3009)
// X-Powered-By header has no functional value.

View File

@@ -3,15 +3,13 @@
Code modified from : https://github.com/madhums/node-express-mongoose-demo/blob/master/config/passport/local.js
*/
import mongoose from "mongoose"
import { Strategy as LocalStrategy } from "passport-local"
import User from "../../models/user"
const LocalStrategy = require("passport-local").Strategy;
const User = require("../../models/user");
/*
By default, LocalStrategy expects to find credentials in parameters named username and password.
If your site prefers to name these fields differently, options are available to change the defaults.
*/
export default new LocalStrategy({
const local = new LocalStrategy({
usernameField: "email"
}, (email, password, done) => {
User.findOne({
@@ -33,3 +31,4 @@
})
})
})
module.exports = local;

View File

@@ -1,8 +1,8 @@
/* Initializing PassportJS */
import User from "../models/user"
import local from "./passport-strategies/local"
const User = require("../models/user");
const local = require("./passport-strategies/local");
export default function (app, passport) {
module.exports = function (app, passport) {
// Configure Passport authenticated session persistence.
//
// In order to restore authentication state across HTTP requests, Passport needs
@@ -23,3 +23,4 @@ export default function (app, passport) {
// use the following strategies
passport.use(local)
}

View File

@@ -12,4 +12,4 @@ const secrets = {
sessionSecret: "letthisbeyoursecret"
}
export default secrets
module.exports = secrets

View File

@@ -1,6 +1,6 @@
import mongoose from "mongoose";
import Plan from "../models/plan";
import System from "../models/system";
const mongoose = require("mongoose");
const Plan = require("../models/plan");
const System = require("../models/system");
const stripeKeyPublishable = process.env.STRIPE_PUBLISHABLE_KEY;
const stripeKeySecret = process.env.STRIPE_SECRET_KEY;

View File

@@ -1,5 +1,5 @@
import mongoose from "mongoose";
import System from "../models/system";
const mongoose = require("mongoose");
const System = require("../models/system");

View File

@@ -1,7 +1,6 @@
import mongoose from "mongoose";
import passport from "passport";
import User from "../models/user";
import secrets from "../config/secrets";
const passport = require("passport");
const User = require("../models/user");
const mailjet = require("node-mailjet").connect(
process.env['MAILJET_KEY'],
process.env['MAILJET_SECRET']

95
account/server/index.js Normal file
View File

@@ -0,0 +1,95 @@
const fs = require("fs");
const express = require("express");
const mongoose = require("mongoose");
const passport = require("passport");
const path = require("path");
const secrets = require("./config/secrets");
const configurePassport = require("./config/passport");
const configureExpress = require("./config/express");
const users = require("./controllers/users");
require("./models/user");
// -------------------------------------------
const app = express()
// -------------------------------------------
const connect = () => {
mongoose.connect(secrets.db, (err, res) => {
if (err) {
console.log(`Error connecting to ${secrets.db}. ${err}`)
} else {
console.log(`Successfully connected to ${secrets.db}.`)
}
})
}
connect()
mongoose.connection.on("error", console.error)
mongoose.connection.on("disconnected", connect)
// -------------------------------------------
const isDev = process.env.NODE_ENV === "development"
var backend_server = process.env['BACKEND_SERVER'] != null ? process.env['BACKEND_SERVER'] : 'https://api.openmhz.com';
var frontend_server = process.env['FRONTEND_SERVER'] != null ? process.env['FRONTEND_SERVER'] : 'https://openmhz.com';
var media_server = process.env['MEDIA_SERVER'] != null ? process.env['MEDIA_SERVER'] : 'https://media.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
var socket_server = process.env['SOCKET_SERVER'] != null ? process.env['SOCKET_SERVER'] : 'wss://api.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
var admin_server = process.env['ADMIN_SERVER'] != null ? process.env['ADMIN_SERVER'] : 'https://admin.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
var account_server = process.env['ACCOUNT_SERVER'] != null ? process.env['ACCOUNT_SERVER'] : 'https://account.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
var site_name = process.env['SITE_NAME'] != null ? process.env['SITE_NAME'] : "OpenMHz";
const proPlanValue = process.env['PRO_PLAN'] != null ? process.env['PRO_PLAN'] : 10;
const freePlanValue = process.env['FREE_PLAN'] != null ? process.env['FREE_PLAN'] : 0;
const proPlanPrice = process.env['PRO_PLAN_PRICE'] != null ? process.env['PRO_PLAN_PRICE'] : 15;
const freePlanPrice = process.env['FREE_PLAN_PRICE'] != null ? process.env['FREE_PLAN_PRICE'] : 0;
// -------------------------------------------
configurePassport(app, passport)
configureExpress(app, passport)
app.all('*', function(req, res, next) {
var allowedOrigins = [account_server, admin_server, frontend_server];
var origin = req.headers.origin;
if(allowedOrigins.indexOf(origin) > -1){
res.setHeader('Access-Control-Allow-Origin', origin);
}
res.header("Access-Control-Allow-Credentials", "true");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header('Access-Control-Allow-Headers', 'Content-Type');
next();
});
// -------------------------------------------
app.use(express.static(path.join(__dirname, "public")));
app.post("/login", users.login)
app.get("/logout", users.logout)
app.get("/authenticated", users.authenticated)
app.post("/register", users.validateProfile, users.register)
app.post("/users/:userId/reset-password/:token", users.resetPassword)
app.post("/api/send-reset-password", users.sendResetPassword)
app.post("/users/:userId", users.isLoggedIn, users.validateProfile, users.updateProfile)
app.post("/users/:userId/terms", users.isLoggedIn, users.terms)
app.post("/users/:userId/send-confirm", users.sendConfirmEmail)
app.post("/users/:userId/confirm/:token", users.confirmEmail)
app.get("*", (req, res, next) => {
res.sendFile('public/index.html');
});
// start listening to incoming requests
app.listen(app.get("port"), app.get("host"), (err) => {
if (err) {
console.err(err.stack)
} else {
console.log(`App listening on port ${app.get("port")} [${process.env.NODE_ENV} mode]`)
}
})

View File

@@ -1,7 +1,7 @@
// Defining a User Model in mongoose
// Code modified from https://github.com/sahat/hackathon-starter
import bcrypt from "bcrypt-nodejs"
import mongoose from "mongoose"
const mongoose = require("mongoose");
var SubscriptionSchema = mongoose.Schema({ subscriptionItemId: String, quantity: {type: Number, default: 0}, systems: [String] });
@@ -20,4 +20,4 @@ const PlanSchema = new mongoose.Schema({
export default mongoose.model("Plan", PlanSchema)
module.exports = mongoose.model("Plan", PlanSchema)

View File

@@ -1,5 +1,5 @@
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
require('mongoose');
require('bcrypt-nodejs');
var systemSchema = mongoose.Schema({
name: String,

View File

@@ -1,8 +1,8 @@
// Defining a User Model in mongoose
// Code modified from https://github.com/sahat/hackathon-starter
import bcrypt from "bcrypt-nodejs"
import mongoose from "mongoose"
import crypto from "crypto"
const bcrypt = require("bcrypt");
const mongoose = require("mongoose");
@@ -89,4 +89,4 @@ UserSchema.pre("save", function(next) {
*/
UserSchema.statics = {}
export default mongoose.model("User", UserSchema)
module.exports = mongoose.model("User", UserSchema)

39
account/src/App.css Normal file
View File

@@ -0,0 +1,39 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-float infinite 3s ease-in-out;
}
}
.App-header {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
}
.App-link {
color: rgb(112, 76, 182);
}
@keyframes App-logo-float {
0% {
transform: translateY(0);
}
50% {
transform: translateY(10px)
}
100% {
transform: translateY(0px)
}
}

58
account/src/App.js Normal file
View File

@@ -0,0 +1,58 @@
import React from 'react';
import logo from './logo.svg';
import { Counter } from './features/counter/Counter';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Counter />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<span>
<span>Learn </span>
<a
className="App-link"
href="https://reactjs.org/"
target="_blank"
rel="noopener noreferrer"
>
React
</a>
<span>, </span>
<a
className="App-link"
href="https://redux.js.org/"
target="_blank"
rel="noopener noreferrer"
>
Redux
</a>
<span>, </span>
<a
className="App-link"
href="https://redux-toolkit.js.org/"
target="_blank"
rel="noopener noreferrer"
>
Redux Toolkit
</a>
,<span> and </span>
<a
className="App-link"
href="https://react-redux.js.org/"
target="_blank"
rel="noopener noreferrer"
>
React Redux
</a>
</span>
</header>
</div>
);
}
export default App;

15
account/src/App.test.js Normal file
View File

@@ -0,0 +1,15 @@
import React from 'react';
import { render } from '@testing-library/react';
import { Provider } from 'react-redux';
import store from './app/store';
import App from './App';
test('renders learn react link', () => {
const { getByText } = render(
<Provider store={store}>
<App />
</Provider>
);
expect(getByText(/learn/i)).toBeInTheDocument();
});

View File

@@ -2,10 +2,6 @@ import React, { Component } from "react"
import NavigationContainer from "./NavigationContainer"
class App extends Component {
constructor(props) {
super(props);
}
render() {
return(
<div>

View File

@@ -1,13 +1,10 @@
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);
@@ -20,8 +17,7 @@ class Navigation extends Component {
};
render() {
var profileLink = account_server + "/profile";
var billingLink = account_server + "/billing";
var profileLink = process.env.REACT_APP_ACCOUNT_SERVER + "/profile";
return (
<div>
{this.props.user.authenticated ? (

View File

@@ -1,6 +1,6 @@
import { connect } from "react-redux"
import * as userActions from "../../User/user-actions"
import Navigation from "../pure/Navigation"
import * as userActions from "../User/user-actions"
import Navigation from "./Navigation"
// Function passed in to `connect` to subscribe to Redux store updates.

View File

@@ -1,13 +1,12 @@
// 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 * as types from "../User/user-constants";
import axios from "axios";
/**
* Higher-order component (HOC) to wrap restricted pages
*/
export default (BaseComponent, store) => {
const restricted = (BaseComponent, store) => {
class Restricted extends Component {
componentWillMount() {
this.checkAuthentication(this.props);
@@ -53,3 +52,4 @@ export default (BaseComponent, store) => {
}
return withRouter(Restricted);
}
export default restricted

View File

@@ -1,7 +1,6 @@
import React, { Component } from "react";
import {
Container,
Header,
Dimmer,
Loader,
Button,
@@ -11,9 +10,7 @@ import {
import { Link } from "react-router-dom";
class ConfirmEmail extends Component {
constructor(props) {
super(props);
}
state = {
loading: true,
confirmMessage: { success: false, message:""}

View File

@@ -1,4 +1,4 @@
import React, { PropTypes, Component } from "react";
import React, { Component } from "react";
import { Link } from "react-router-dom";
import {
@@ -7,7 +7,6 @@ import {
Form,
Grid,
Segment,
Input,
Button,
Message,
Icon,
@@ -47,20 +46,21 @@ class Login extends Component {
_onLoginSubmit(event) {
event.preventDefault();
const { email, password } = this.state;
var success = {type: "path", nextPathname: "/" };
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) {
success.type = "location";
switch (this.props.nextLocation) {
case "frontend":
success.nextLocation = process.env.REACT_APP_FRONTEND_SERVER;
break;
default:
case "admin":
success.nextLocation = admin_server;
break;
case "frontend":
success.nextLocation = frontend_server;
break;
}
success.nextLocation = process.env.REACT_APP_ADMIN_SERVER;
break;
}
}
// Passed in via react-redux. Returns a promise.
@@ -86,7 +86,6 @@ class Login extends Component {
render() {
var loginMessage = "";
const { name, email } = this.state;
if (this.state.loginMessage) {
loginMessage = (
@@ -102,46 +101,46 @@ class Login extends Component {
return (
<Container>
<Header as="h1">{site_name}</Header>
<Header as="h1">{process.env.REACT_APP_SITE_NAME}</Header>
<Grid stackable >
<Grid.Row>
<Grid.Column width={9}>
<Header size="large">Let's get this started!</Header>
<Header size="small">
{site_name} makes it easy to share recordings from{" "}
<a href="https://github.com/robotastic/trunk-recorder">
Trunk Recorder
<Header size="large">Let's get this started!</Header>
<Header size="small">
{process.env.REACT_APP_SITE_NAME} makes it easy to share recordings from{" "}
<a href="https://github.com/robotastic/trunk-recorder">
Trunk Recorder
</a>.</Header>
<p>
Trunk Recorder makes it easy to record the radio systems
used by your local police and fire department. Trunk Recorder
is sort of like an awesome version of those Scanners from
Radio Shack... 10-4!
<p>
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>
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.
<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>
{" "}
<p>
{" "}
- Luke{" "}
<a
href="mailto:{admin_email}?Subject={site_name}"
target="_top"
>
admin@email.com
<a
href="mailto:{process.env.REACT_APP_ADMIN_EMAIL}?Subject={process.env.REACT_APP_SITE_NAME}"
target="_top"
>
admin@email.com
</a>{" "}
</p>
</p>
</Grid.Column>
<Grid.Column floated="right" width={6}>
<Header as="h3">Need an account?</Header>
@@ -151,8 +150,8 @@ class Login extends Component {
onClick={this.handleRegisterClick}
fluid
/>
<Divider style={dividerStyle} horizontal>Or</Divider>
<Header as="h3">Login</Header>
<Divider style={dividerStyle} horizontal>Or</Divider>
<Header as="h3">Login</Header>
<Form onSubmit={this._onLoginSubmit}>
<Segment padded>
<Form.Field>
@@ -183,7 +182,7 @@ class Login extends Component {
</Segment>
</Form>
<div style={forgotStyle} >
<Link to="/send-reset-password">Forgot Password</Link>
<Link to="/send-reset-password">Forgot Password</Link>
</div>
</Grid.Column>
</Grid.Row>

View File

@@ -1,5 +1,4 @@
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';

View File

@@ -1,24 +1,13 @@
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) {

View File

@@ -1,14 +1,9 @@
import React, { Component } from "react";
import {
Container,
Header,
Form,
Grid,
Segment,
Input,
Button,
Message,
Icon
} from "semantic-ui-react";
class ResetPassword extends Component {
@@ -75,7 +70,6 @@ class ResetPassword extends Component {
<Form.Group widths="equal">
<Form.Field>
<Form.Input
type="text"
name="password"
type="password"
onChange={this.handleInputChange}
@@ -86,7 +80,6 @@ class ResetPassword extends Component {
</Form.Field>
<Form.Field>
<Form.Input
type="text"
name="confirmPassword"
type="password"
onChange={this.handleInputChange}

View File

@@ -1,11 +1,8 @@
import React, { Component } from "react";
import {
Container,
Header,
Form,
Grid,
Segment,
Input,
Button,
Message,
Icon

View File

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

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

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

View File

@@ -1,23 +1,13 @@
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 {
@@ -60,7 +50,7 @@ class UserForm extends Component {
checkInputs() {
let error = false;
var checkInputMessages= [];
var checkInputMessages = [];
if (this.state.firstName === "") {
this.setState({ firstNameError: true });
@@ -115,7 +105,7 @@ class UserForm extends Component {
this.setState({ passwordError: true });
checkInputMessages.push("Password must be 7 charecters or more");
error = true;
}else {
} else {
this.setState({ passwordError: false });
}
@@ -138,7 +128,7 @@ class UserForm extends Component {
let inputError = this.checkInputs();
if (!inputError) {
const user = (({ firstName, lastName, screenName, location, email, password }) => ({ firstName, lastName, screenName, location, email, password }))(this.state);
const user = (({ firstName, lastName, screenName, location, email, password }) => ({ firstName, lastName, screenName, location, email, password }))(this.state);
this.props.onSubmit(user);
}
}
@@ -148,12 +138,11 @@ class UserForm extends Component {
var floatStyle = {
overflow: 'auto'
}
var dimmerProps = { };
var dimmerProps = {};
if (this.props.isWaiting) {
dimmerProps["active"] = true;
}
const { name, email } = this.state;
const { isEditing} = this.props;
const { isEditing } = this.props;
if (this.state.checkInputMessages.length || this.props.requestMessage.length) {
var counter = 0;
registerMessage = (
@@ -162,11 +151,11 @@ class UserForm extends Component {
<Message.Content>
<Message.Header>Problems...</Message.Header>
<ul>
{this.state.checkInputMessages.map(function(listValue) {
{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>
{this.props.requestMessage.length > 0 &&
<li key="req-mess">{this.props.requestMessage}</li>
}
</ul>
</Message.Content>
@@ -175,85 +164,84 @@ class UserForm extends Component {
}
return (
<div>
<Form
className="raised padding segment"
onSubmit={this.handleSubmit}
>
<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.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
</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.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="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}
@@ -265,7 +253,6 @@ class UserForm extends Component {
</Form.Field>
<Form.Field>
<Form.Input
type="text"
name="confirmPassword"
type="password"
onChange={this.handleInputChange}
@@ -276,14 +263,14 @@ class UserForm extends Component {
/>
</Form.Field>
</Form.Group>
)}
<div style={floatStyle}>
<Button type="submit" size="large" floated="right" value="Login" color='blue'>
{isEditing!=true? 'Register' : 'Update' }
)}
<div style={floatStyle}>
<Button type="submit" size="large" floated="right" value="Login" color='blue'>
{isEditing !== true ? 'Register' : 'Update'}
</Button>
</div>
</Form>
{registerMessage}
</Form>
{registerMessage}
</div>
);
}

View File

@@ -1,5 +1,4 @@
import React, { Component } from "react"
import { Link } from 'react-router-dom'
import {
Container,
Header,

View File

@@ -156,7 +156,7 @@ export function manualLogin(
return dispatch => {
dispatch(beginLogin());
return makeUserRequest("post", data, "/login", {withCredentials: true})
return makeUserRequest("post", data, "/login", { withCredentials: true })
.then(response => {
if (response.data.success) {
const user = response.data.user
@@ -164,17 +164,17 @@ export function manualLogin(
// 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")) {
if (success && (success.type === "path")) {
dispatch(push(success.nextPathname));
} else if (success && (success.type=="location")) {
window.location=success.nextLocation;
} 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") {
if (loginMessage === "unconfirmed email") {
data.userId = response.data.userId;
dispatch(loginEmailError(data));
dispatch(push("/wait-confirm-email"));
@@ -184,7 +184,7 @@ export function manualLogin(
return loginMessage;
}
})
.catch(function(response) {
.catch(function (response) {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
@@ -197,7 +197,6 @@ export function manualLogin(
// http://redux.js.org/docs/advanced/AsyncActions.html
export function isAuthenticated() {
return dispatch => {
dispatch(beginAuthenticated());
return axios
.get("/authenticated")
@@ -257,11 +256,11 @@ export function confirmEmail(userId, token) {
response => {
if (response.data.success) {
dispatch(confirmSuccess());
let requestMessage = {success: true, message: response.data.message};
let requestMessage = { success: true, message: response.data.message };
return requestMessage;
} else {
dispatch(confirmError());
let requestMessage = {success: false, message: response.data.message};
let requestMessage = { success: false, message: response.data.message };
return requestMessage;
}
}
@@ -269,10 +268,10 @@ export function confirmEmail(userId, token) {
};
}
export function resetPassword(userId, token,password) {
export function resetPassword(userId, token, password) {
return dispatch => {
dispatch(beginResetPassword());
return makeUserRequest("post", {password: password}, "/users/" + userId + "/reset-password/" + token).then(
return makeUserRequest("post", { password: password }, "/users/" + userId + "/reset-password/" + token).then(
response => {
if (response.data.success) {
dispatch(resetPasswordSuccess());

View File

@@ -1,86 +0,0 @@
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;

View File

@@ -1,17 +0,0 @@
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)

View File

@@ -1,43 +0,0 @@
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;

View File

@@ -1,52 +0,0 @@
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);

View File

@@ -1,145 +0,0 @@
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);
}
});
};
}

View File

@@ -1,11 +0,0 @@
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"

View File

@@ -1,71 +0,0 @@
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;

View File

@@ -1,140 +0,0 @@
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;

View File

@@ -1,131 +0,0 @@
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;

View File

@@ -1,19 +0,0 @@
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)

View File

@@ -1,156 +0,0 @@
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);
}
});
};
};
}

View File

@@ -1,10 +0,0 @@
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"

View File

@@ -1,78 +0,0 @@
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;

View File

@@ -1,36 +0,0 @@
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

View File

@@ -1,83 +0,0 @@
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;

View File

@@ -1,95 +0,0 @@
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"))

View File

@@ -1,31 +0,0 @@
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

View File

@@ -1,31 +0,0 @@
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;

View File

@@ -1,5 +0,0 @@
.field {
padding-top: 10px;
padding-left: 10px;
padding-right: 10px;
}

View File

@@ -1,12 +0,0 @@
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
})

View File

@@ -1,21 +0,0 @@
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}/>)
});

13
account/src/index.css Normal file
View File

@@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

45
account/src/index.js Normal file
View File

@@ -0,0 +1,45 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import { Route, Switch } from 'react-router' // react-router v4/v5
import { ConnectedRouter } from 'connected-react-router'
import configureStore, { history } from './redux-router/configureStore'
import 'semantic-ui-css/semantic.min.css'
import App from "./App/App"
import Restricted from "./App/Restricted"
import LoginContainer from "./User/LoginContainer"
import RegisterContainer from "./User/RegisterContainer"
import ProfileContainer from "./User/ProfileContainer"
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 TermsContainer from "./User/TermsContainer"
const store = configureStore(/* provide initial state if any */)
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}> { /* place ConnectedRouter under Provider */ }
<>
<App>
<Switch>
<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="/profile" component={Restricted(ProfileContainer, store)} />
<Route render={() => (<div>Miss</div>)} />
</Switch>
</App>
</>
</ConnectedRouter>
</Provider>,
document.getElementById('root')
)

1
account/src/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><g fill="#764ABC"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,26 @@
// https://github.com/supasate/connected-react-router
// configureStore.js
import { createBrowserHistory } from 'history'
import { applyMiddleware, compose, createStore } from 'redux'
import { routerMiddleware } from 'connected-react-router'
import createRootReducer from './reducers'
import thunk from 'redux-thunk';
export const history = createBrowserHistory()
export default function configureStore(preloadedState) {
const store = createStore(
createRootReducer(history), // root reducer with router state
preloadedState,
compose(
applyMiddleware(
routerMiddleware(history), // for dispatching history actions
thunk,
// ... other middlewares ...
),
),
)
return store
}

View File

@@ -0,0 +1,13 @@
// from https://github.com/supasate/connected-react-router
import { combineReducers } from 'redux'
import { connectRouter } from 'connected-react-router'
import user from "../User/user-reducer"
const createRootReducer = (history) => combineReducers({
router: connectRouter(history),
user,
})
export default createRootReducer

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,282 +0,0 @@
import fs from "fs"
import express from "express"
import mongoose from "mongoose"
import passport from "passport"
import webpack from "webpack"
import path from "path"
import config from "../../webpack/webpack.config.dev.js"
import secrets from "./config/secrets"
import configurePassport from "./config/passport"
import configureExpress from "./config/express"
import users from "./controllers/users"
import plans from "./controllers/plans"
const stripeKeyPublishable = process.env.STRIPE_PUBLISHABLE_KEY;
const stripeKeySecret = process.env.STRIPE_SECRET_KEY;
const stripe = require("stripe")(stripeKeySecret);
import "./models/user"
// -------------------------------------------
const app = express()
// -------------------------------------------
const connect = () => {
mongoose.connect(secrets.db, (err, res) => {
if (err) {
console.log(`Error connecting to ${secrets.db}. ${err}`)
} else {
console.log(`Successfully connected to ${secrets.db}.`)
}
})
}
connect()
mongoose.connection.on("error", console.error)
mongoose.connection.on("disconnected", connect)
// -------------------------------------------
const isDev = process.env.NODE_ENV === "development"
// if in development mode set up the middleware required for hot reloading and rebundling
if(isDev) {
const compiler = webpack(config)
app.use(require("webpack-dev-middleware")(compiler, {
noInfo: true,
publicPath: config.output.publicPath
}))
app.use(require("webpack-hot-middleware")(compiler))
}
var backend_server = process.env['BACKEND_SERVER'] != null ? process.env['BACKEND_SERVER'] : 'https://api.openmhz.com';
var frontend_server = process.env['FRONTEND_SERVER'] != null ? process.env['FRONTEND_SERVER'] : 'https://openmhz.com';
var media_server = process.env['MEDIA_SERVER'] != null ? process.env['MEDIA_SERVER'] : 'https://media.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
var socket_server = process.env['SOCKET_SERVER'] != null ? process.env['SOCKET_SERVER'] : 'wss://api.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
var admin_server = process.env['ADMIN_SERVER'] != null ? process.env['ADMIN_SERVER'] : 'https://admin.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
var account_server = process.env['ACCOUNT_SERVER'] != null ? process.env['ACCOUNT_SERVER'] : 'https://account.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
var site_name = process.env['SITE_NAME'] != null ? process.env['SITE_NAME'] : "OpenMHz";
const proPlanValue = process.env['PRO_PLAN'] != null ? process.env['PRO_PLAN'] : 10;
const freePlanValue = process.env['FREE_PLAN'] != null ? process.env['FREE_PLAN'] : 0;
const proPlanPrice = process.env['PRO_PLAN_PRICE'] != null ? process.env['PRO_PLAN_PRICE'] : 15;
const freePlanPrice = process.env['FREE_PLAN_PRICE'] != null ? process.env['FREE_PLAN_PRICE'] : 0;
// -------------------------------------------
configurePassport(app, passport)
configureExpress(app, passport)
app.all('*', function(req, res, next) {
var allowedOrigins = [account_server, admin_server, frontend_server];
var origin = req.headers.origin;
if(allowedOrigins.indexOf(origin) > -1){
res.setHeader('Access-Control-Allow-Origin', origin);
}
res.header("Access-Control-Allow-Credentials", "true");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header('Access-Control-Allow-Headers', 'Content-Type');
next();
});
// -------------------------------------------
app.use(express.static(path.join(__dirname, "public")));
app.post("/login", users.login)
app.get("/logout", users.logout)
app.get("/authenticated", users.authenticated)
app.post("/register", users.validateProfile, users.register)
app.post("/users/:userId/reset-password/:token", users.resetPassword)
app.post("/api/send-reset-password", users.sendResetPassword)
app.post("/users/:userId", users.isLoggedIn, users.validateProfile, users.updateProfile)
app.post("/users/:userId/terms", users.isLoggedIn, users.terms)
app.post("/users/:userId/send-confirm", users.sendConfirmEmail)
app.post("/users/:userId/confirm/:token", users.confirmEmail)
/*app.get("/api/invoices", users.isLoggedIn, plans.getInvoices)
app.post("/api/plans", users.isLoggedIn, plans.updatePlans)
app.get("/api/billing", users.isLoggedIn, (req,res) => {
if (req.user.customerId) {
stripe.customers.retrieve(
req.user.customerId,
function(err, customer) {
if (err) {
return res.json({
success: false,
message: err
});
}
if (customer) {
if (customer.sources.total_count < 1) {
return res.json({
success: false,
message: "Payment sources not defined"
});
}
for (var i=0; i < customer.sources.data.length; i++) {
var source = customer.sources.data[i];
if (source.id == customer.default_source) {
console.log("match on: " + i);
var obj = {
name: source.owner.name,
brand: source.card.brand,
country: source.card.country,
exp_month: source.card.exp_month,
exp_year: source.card.exp_year,
last4: source.card.last4
}
return res.json({
success: true,
source: obj
});
}
}
}
return res.json({
success: false,
message: "Default payment source not found"
});
}
);
} else {
return res.json({
success: false,
message: "User does not have a Customer ID"
});
}
});*/
function updateCustomerId(req, res, err, customer) {
if (err){
return res.json({
success: false,
message: err
});
}
req.user.customerId = customer.id;
req.user.save(err => {
if (err) {
console.error("Error saving customer ID");
console.error(err);
res.json({
success: false,
message: err
});
return;
}
});
console.log(customer);
if (customer.sources.total_count < 1) {
return res.json({
success: false,
message: "Payment sources not defined"
});
}
for (var i=0; i < customer.sources.data.length; i++) {
console.log("--------Customer-----------");
console.log(customer);
var source = customer.sources.data[i];
if (source.id == customer.default_source) {
console.log("--------Source-----------");
console.log(source);
var obj = {
name: source.owner.name,
brand: source.card.brand,
country: source.card.country,
exp_month: source.card.exp_month,
exp_year: source.card.exp_year,
last4: source.card.last4
}
return res.json({
success: true,
source: obj
});
}
}
}
app.post("/api/customer", users.isLoggedIn, (req, res) => {
//
if (req.user.customerId) {
stripe.customers.update(req.user.customerId, {
source: req.body.sourceId
}, function(err, customer) {
return updateCustomerId(req, res, err, customer);
});
} else {
stripe.customers.create({
email: req.user.email,
source: req.body.sourceId
}, function(err, customer) {
return updateCustomerId(req, res, err, customer);
});
}
});
app.get("*", (req, res, next) => {
// if we are in production mode then an extension will be provided, usually ".min"
const minified = process.env.MIN_EXT || ""
// this is the HTML we will send to the client when they request any page. React and React Router
// will take over once the scripts are loaded client-side
const appHTML =
`<!doctype html>
<html lang="">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<title>OpenMHz - Account</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://js.stripe.com/v3/"></script>
<link rel="stylesheet" type="text/css" href="/style/style.css"></link>
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/semantic-ui@2.4.1/dist/semantic.min.css" />
<script>
const frontend_server = "${frontend_server}";
const admin_server = "${admin_server}";
const backend_server = "${backend_server}";
const media_server = "${media_server}";
const socket_server = "${socket_server}";
const account_server = "${account_server}";
const site_name = "${site_name}";
const proPlanValue = ${proPlanValue};
const freePlanValue = ${freePlanValue};
const proPlanPrice = ${proPlanPrice};
const freePlanPrice = ${freePlanPrice};
</script>
<style>
body {
font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;
}
</style>
</head>
<body>
<div id="app"></div>
<script src="/assets/app${minified}.js"></script>
</body>
</html>`
res.status(200).end(appHTML)
})
// start listening to incoming requests
app.listen(app.get("port"), app.get("host"), (err) => {
if (err) {
console.err(err.stack)
} else {
console.log(`App listening on port ${app.get("port")} [${process.env.NODE_ENV} mode]`)
}
})

View File

@@ -0,0 +1,137 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';

View File

@@ -1,18 +0,0 @@
db = db.getMongo().getDB( "scanner" );
db.users.find().forEach(
function(user){
db.systems.updateMany(
{ "user": { $eq: user.email } },
{
$set: { "userId": user._id, "showScreenName": false },
}
);
print("Upgrading: " + user.email);
});

View File

@@ -1,56 +0,0 @@
var path = require("path")
var webpack = require("webpack")
var assetsPath = path.join(__dirname, "..", "public", "assets")
var publicPath = "/assets/"
module.exports = {
name: "browser",
mode: 'development',
devtool: "eval-source-map",
// The base directory (absolute path!) for resolving the entry option.
context: path.join(__dirname, "..", "src", "client"),
entry: {
app: [
"./client",
"webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000&reload=true"
]
},
optimization: {
minimize: false
},
output: {
path: assetsPath,
filename: "[name].js",
publicPath: publicPath
},
module: {
rules: [{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader',
}],
},
resolve: {
extensions: [".js", ".jsx"],
modules: [
path.resolve('src'),
'node_modules',
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify("development")
}
})
]
}

View File

@@ -1,51 +0,0 @@
var path = require("path")
var webpack = require("webpack")
var assetsPath = path.join(__dirname, "..", "public", "assets")
var publicPath = "/assets/"
module.exports = {
// The configuration for the client
name: "browser",
mode: 'production',
devtool: "source-map",
// The base directory (absolute path!) for resolving the entry option.
context: path.join(__dirname, "..", "src", "client"),
optimization: {
minimize: true
},
entry: {
app: "./client"
},
output: {
path: assetsPath,
filename: "[name].min.js",
publicPath: publicPath
},
resolve: {
extensions: [".js", ".jsx"],
modules: [
path.resolve('src'),
'node_modules',
],
},
module: {
rules: [{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader',
}],
},
plugins: [
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify("production")
}
})
]
}

File diff suppressed because it is too large Load Diff