Browse Source

Added a settings page and improved CustomInput.

KrisVos130 7 years ago
parent
commit
09ced035be

+ 8 - 0
frontend/app/js/views/App/index.jsx

@@ -93,6 +93,14 @@ class App extends Component { // eslint-disable-line react/no-multi-comp
 							) }
 							authRequired={ false }
 						/>
+						<AuthRoute
+							exact
+							path="/settings"
+							component={ asyncComponent(() =>
+								System.import("views/Auth/Settings").then(module => module.default)
+							) }
+							authRequired={ true }
+						/>
 						<Route
 							exact
 							path="/template"

+ 20 - 4
frontend/app/js/views/Auth/CustomInput.jsx

@@ -1,5 +1,6 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
+import reactTriggerChange from "react-trigger-change";
 
 const regex = {
 	azAZ09_: /^[A-Za-z0-9_]+$/,
@@ -44,6 +45,7 @@ export default class CustomInput extends Component {
 		placeholder: PropTypes.string,
 		customInputEvents: PropTypes.object,
 		validationCallback: PropTypes.func,
+		onRef: PropTypes.func,
 	};
 
 	static defaultProps = {
@@ -55,6 +57,7 @@ export default class CustomInput extends Component {
 		placeholder: "",
 		customInputEvents: {},
 		validationCallback: () => {},
+		onRef: () => {},
 	};
 
 	static validationCallback = (ctx) => {
@@ -65,10 +68,10 @@ export default class CustomInput extends Component {
 		};
 	};
 
-	static hasInvalidInput = (inputInvalid) => {
+	static hasInvalidInput = (inputInvalid, properties) => {
 		let invalid = false;
-		Object.values(inputInvalid).forEach((value) => {
-			if (value) invalid = true;
+		properties.forEach((property) => {
+			if (inputInvalid[property]) invalid = true;
 		});
 		return invalid;
 	};
@@ -99,6 +102,13 @@ export default class CustomInput extends Component {
 		} else this.state.customInputEvents.onChange = this.onChangeHandler;
 	}
 
+	componentDidMount() {
+		this.props.onRef(this);
+	}
+	componentWillUnmount() {
+		this.props.onRef(null);
+	}
+
 	onBlurHandler = () => {
 		this.validateInput();
 	};
@@ -133,6 +143,11 @@ export default class CustomInput extends Component {
 		this.props.validationCallback(this.props.name, errors.length > 0);
 	};
 
+	triggerChangeEvent = () => {
+		reactTriggerChange(this.inputElement);
+		this.validateInput();
+	};
+
 	render() {
 		return (
 			<label htmlFor={ this.props.name }>
@@ -141,9 +156,10 @@ export default class CustomInput extends Component {
 					placeholder={ this.props.placeholder }
 					type={ this.props.inputType }
 					name={ this.props.name }
-					value={ this.state.value }
+					value={ this.props.value }
 					className={ (this.state.errors.length > 0) ? "has-validation-errors" : "" }
 					{ ...this.state.customInputEvents }
+					ref={ (input) => this.inputElement = input }
 				/>
 				{ this.listErrors() }
 			</label>

+ 178 - 0
frontend/app/js/views/Auth/Settings.jsx

@@ -0,0 +1,178 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import CustomInput from "./CustomInput.jsx";
+import CustomErrors from "./CustomErrors.jsx";
+
+import io from "../../io";
+
+@connect(state => ({
+	user: {
+		userId: state.user.get("userId"),
+	},
+}))
+
+export default class Settings extends Component {
+	static propTypes = {
+		user: PropTypes.object,
+	};
+
+	static defaultProps = {
+		user: {
+			userId: "",
+		},
+	};
+
+	constructor(props) {
+		super(props);
+
+		this.state = {
+			email: "",
+			username: "",
+			currentPassword: "",
+			newPassword: "",
+			newPasswordAgain: "",
+			passwordLinked: false,
+			gitHubLinked: false,
+			inputInvalid: {
+				email: true,
+				username: true,
+				currentPassword: true,
+				newPassword: true,
+				newPasswordAgain: true,
+			},
+			errors: [],
+		};
+
+		this.customInputs = {};
+
+		io.getSocket(socket => {
+			socket.emit("users.findBySession", res => {
+				if (res.status === "success") {
+					this.setState({
+						email: res.data.email.address,
+						username: res.data.username,
+						passwordLinked: res.data.password,
+						gitHubLinked: res.data.github,
+					}, () => {
+						this.customInputs.email.triggerChangeEvent(true);
+						this.customInputs.username.triggerChangeEvent(true);
+					});
+				} else {
+					this.state.errors = ["You are currently not logged in."];
+				}
+			});
+
+			socket.on("event:user.username.changed", username => {
+				this.setState({
+					username,
+				}, () => {
+					this.customInputs.username.triggerChangeEvent(true);
+				});
+			});
+
+			// TODO Email changed event?
+
+			socket.on("event:user.linkPassword", () => {
+				this.setState({
+					passwordLinked: true,
+				});
+			});
+
+			socket.on("event:user.linkGitHub", () => {
+				this.setState({
+					gitHubLinked: true,
+				});
+			});
+
+			socket.on("event:user.unlinkPassword", () => {
+				this.setState({
+					passwordLinked: false,
+				});
+			});
+
+			socket.on("event:user.unlinkGitHub", () => {
+				this.setState({
+					gitHubLinked: false,
+				});
+			});
+		});
+	}
+
+	updateField(field, event) {
+		this.setState({
+			[field]: event.target.value,
+		});
+	}
+
+	/* register() {
+		if (CustomInput.hasInvalidInput(this.state.inputInvalid)) {
+			alert("Input invalid. Fix before continuing.");
+		} else {
+			this.setState({ errors: [] });
+			io.getSocs.setState({ errors: [res.message] });
+					}
+				});
+			});
+		}
+	} */
+
+	/* githubRedirect() {
+		localStorage.setItem("github_redirect", window.location.pathname);
+	} */
+
+	saveChanges = () => {
+		if (CustomInput.hasInvalidInput(this.state.inputInvalid, ["username", "email"])) {
+			alert("Input invalid. Fix before continuing.");
+		} else {
+			this.setState({ errors: [] });
+			io.getSocket(socket => {
+				socket.emit("users.updateEmail", this.props.user.userId, this.state.email, res => {
+					if (res.status === "success") {
+						alert("Success!");
+					} else {
+						this.setState({
+							errors: this.state.errors.concat([res.message]),
+						});
+					}
+				});
+
+				socket.emit("users.updateUsername", this.props.user.userId, this.state.username, res => {
+					if (res.status === "success") {
+						alert("Success!");
+					} else {
+						this.setState({
+							errors: this.state.errors.concat([res.message]),
+						});
+					}
+				});
+			});
+		}
+	};
+
+	validationCallback = CustomInput.validationCallback(this);
+
+	render() {
+		return (
+			<div>
+				<CustomErrors errors={ this.state.errors } />
+				<div>
+					<h2>General</h2>
+					<CustomInput label="Email" placeholder="Email" inputType="email" type="email" name="email" value={ this.state.email } customInputEvents={ { onChange: event => this.updateField("email", event) } } validationCallback={ this.validationCallback } onRef={ ref => (this.customInputs.email = ref) } />
+					<CustomInput label="Username" placeholder="Username" inputType="text" type="username" name="username" value={ this.state.username } customInputEvents={ { onChange: event => this.updateField("username", event) } } validationCallback={ this.validationCallback } onRef={ ref => (this.customInputs.username = ref) } />
+					<button onClick={ this.saveChanges }>Save changes</button>
+				</div>
+				<div>
+					<h2>Security</h2>
+					<CustomInput label="Current password" placeholder="Current password" inputType="password" type="password" name="currentPassword" value={ this.state.currentPassword } customInputEvents={ { onChange: event => this.updateField("currentPassword", event) } } validationCallback={ this.validationCallback } />
+					<CustomInput label="New password" placeholder="New password" inputType="password" type="password" name="newPassword" value={ this.state.newPassword } customInputEvents={ { onChange: event => this.updateField("newPassword", event) } } validationCallback={ this.validationCallback } />
+					<CustomInput label="New password again" placeholder="New password again" inputType="password" type="password" name="newPasswordAgain" value={ this.state.newPasswordAgain } customInputEvents={ { onChange: event => this.updateField("newPasswordAgain", event) } } validationCallback={ this.validationCallback } />
+					<button>Change password</button>
+					<button>Link GitHub account</button>
+					<button>Log out everywhere</button>
+				</div>
+			</div>
+		);
+	}
+}

+ 6 - 0
frontend/package-lock.json

@@ -5893,6 +5893,12 @@
         "react-proxy": "1.1.8"
       }
     },
+    "react-trigger-change": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/react-trigger-change/-/react-trigger-change-1.0.2.tgz",
+      "integrity": "sha1-r1czmOzvJHU2K4T4wIwH/qI5FMM=",
+      "dev": true
+    },
     "read-file": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/read-file/-/read-file-0.2.0.tgz",

+ 1 - 0
frontend/package.json

@@ -37,6 +37,7 @@
     "node-sass": "^3.13.0",
     "postcss-loader": "^1.1.1",
     "prepush": "^3.1.11",
+    "react-trigger-change": "^1.0.2",
     "redux-devtools": "^3.3.1",
     "redux-devtools-dock-monitor": "^1.1.1",
     "redux-devtools-log-monitor": "^1.1.1",