mirror of
https://github.com/naturalcrit/naturalcrit.git
synced 2025-10-27 07:29:54 +00:00
basic renaming
This commit is contained in:
parent
837125195d
commit
81aa84e22d
@ -2,6 +2,7 @@ const request = require('superagent');
|
||||
|
||||
const AccountActions = {
|
||||
login: (user, pass) => {
|
||||
console.log('login');
|
||||
return new Promise((resolve, reject) => {
|
||||
request
|
||||
.post('/login')
|
||||
@ -51,12 +52,28 @@ const AccountActions = {
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
rename: (username, newUsername) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
request
|
||||
.put('/rename')
|
||||
.send({ username, newUsername })
|
||||
.end((err, res) => {
|
||||
if (err) return reject(err);
|
||||
return resolve(res.body);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
createSession: (token) => {
|
||||
console.log('creating new session');
|
||||
const domain = window.domain === '.local.naturalcrit.com' ? 'localhost' : window.domain;
|
||||
document.cookie = `nc_session=${token}; max-age=${60 * 60 * 24 * 365}; path=/; samesite=lax; domain=${domain};`;
|
||||
},
|
||||
|
||||
removeSession: () => {
|
||||
console.log('removing session');
|
||||
const domain = window.domain === '.local.naturalcrit.com' ? 'localhost' : window.domain;
|
||||
document.cookie = `nc_session=, expires=Thu, 01 Jan 1970 00:00:01 GMT, samesite=lax, domain=${domain}`;
|
||||
},
|
||||
|
||||
@ -1,35 +1,74 @@
|
||||
import React from 'react';
|
||||
const React = require('react');
|
||||
const AccountActions = require('../account.actions.js');
|
||||
const NaturalCritIcon = require('naturalcrit/components/naturalcritLogo.jsx');
|
||||
const LoginForm = require('../loginPage/loginForm.jsx');
|
||||
|
||||
const AccountPage = (props) => {
|
||||
return (
|
||||
<div className="accountPage">
|
||||
<NaturalCritIcon />
|
||||
<div className="details">
|
||||
<h1>Account Page</h1>
|
||||
<br />
|
||||
<p>
|
||||
<b>Username:</b> {props.user.username}
|
||||
class AccountPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showLogin: false
|
||||
};
|
||||
this.toggleLogin = this.toggleLogin.bind(this);
|
||||
this.handleRenameSuccess = this.handleRenameSuccess.bind(this); // Bind method
|
||||
}
|
||||
|
||||
toggleLogin() {
|
||||
this.setState({ showLogin: !this.state.showLogin });
|
||||
}
|
||||
|
||||
handleRenameSuccess(newUsername, password) {
|
||||
console.log('handling rename, ', newUsername, password);
|
||||
AccountActions.removeSession();
|
||||
|
||||
AccountActions.login(newUsername, password).then(() => {
|
||||
this.setState({ showLogin: false });
|
||||
window.location.reload(); // Only reload after login is successful
|
||||
}).catch(error => {
|
||||
console.error('Login failed', error);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log(this.props.user);
|
||||
return (
|
||||
<div className="accountPage">
|
||||
<NaturalCritIcon />
|
||||
<div className="details">
|
||||
<h1>Account Page</h1>
|
||||
<br />
|
||||
</p>
|
||||
<br />
|
||||
<button
|
||||
className="logout"
|
||||
onClick={() => {
|
||||
if (confirm('Are you sure you want to log out?')) {
|
||||
AccountActions.removeSession();
|
||||
window.location = '/';
|
||||
}
|
||||
}}>
|
||||
Log Out
|
||||
</button>
|
||||
<br />
|
||||
<br />
|
||||
<small>Upcoming features will include account deletion and username changes.</small>
|
||||
<p>
|
||||
<b>Username:</b> {this.props.user.username}
|
||||
<br />
|
||||
</p>
|
||||
<br />
|
||||
<button
|
||||
className="logout"
|
||||
onClick={() => {
|
||||
if (confirm('Are you sure you want to log out?')) {
|
||||
AccountActions.removeSession();
|
||||
window.location = '/';
|
||||
}
|
||||
}}>
|
||||
Log Out
|
||||
</button>
|
||||
<button className="rename" onClick={this.toggleLogin}>
|
||||
Change my username
|
||||
</button>
|
||||
<br />
|
||||
<br />
|
||||
{this.state.showLogin && (
|
||||
<LoginForm
|
||||
user={this.props.user}
|
||||
renaming={true}
|
||||
onRenameSuccess={this.handleRenameSuccess} // Pass the function
|
||||
/>
|
||||
)}
|
||||
<small>Upcoming features will include account deletion and username changes.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AccountPage;
|
||||
|
||||
148
client/naturalcrit/loginPage/loginForm.jsx
Normal file
148
client/naturalcrit/loginPage/loginForm.jsx
Normal file
@ -0,0 +1,148 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const NaturalCritIcon = require('naturalcrit/components/naturalcritLogo.jsx');
|
||||
const AccountActions = require('../account.actions.js');
|
||||
|
||||
const RedirectLocation = 'NC-REDIRECT-URL';
|
||||
|
||||
const LoginForm = React.createClass({
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
user: null,
|
||||
renaming: null,
|
||||
onRenameSuccess: () => {},
|
||||
};
|
||||
},
|
||||
getInitialState: function () {
|
||||
return {
|
||||
visible: false,
|
||||
|
||||
username: '',
|
||||
newUsername: '',
|
||||
password: '',
|
||||
|
||||
errors: null,
|
||||
success: false,
|
||||
};
|
||||
},
|
||||
componentDidMount: function () {
|
||||
window.document.addEventListener('keydown', (e) => {
|
||||
if (e.code === 'Enter') this.login();
|
||||
});
|
||||
},
|
||||
|
||||
componentWillUnmount: function () {
|
||||
window.document.removeEventListener('keydown', this.handleKeyPress);
|
||||
},
|
||||
|
||||
handleUserChange: function (e) {
|
||||
this.setState({ username: e.target.value });
|
||||
},
|
||||
handleNewUserChange: function (e) {
|
||||
this.setState({ newUsername: e.target.value });
|
||||
},
|
||||
|
||||
handlePassChange: function (e) {
|
||||
this.setState({ password: e.target.value });
|
||||
},
|
||||
|
||||
login: function () {
|
||||
this.setState({
|
||||
processing: true,
|
||||
errors: null,
|
||||
});
|
||||
AccountActions.login(this.state.username, this.state.password)
|
||||
.then((token) => {
|
||||
this.setState({
|
||||
processing: false,
|
||||
errors: null,
|
||||
success: true,
|
||||
});
|
||||
if (this.props.renaming && this.state.newUsername) {
|
||||
AccountActions.rename(this.state.username, this.state.newUsername)
|
||||
.then(() => {
|
||||
console.log('renaming successfull, calling parent func');
|
||||
this.props.onRenameSuccess(this.state.newUsername, this.state.password); // Call the parent function
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
this.setState({
|
||||
processing: false,
|
||||
errors: err,
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
this.setState({
|
||||
processing: false,
|
||||
errors: err,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
renderErrors: function () {
|
||||
if (!this.state.errors) return;
|
||||
if (this.state.errors.msg) return <div className="errors">{this.state.errors.msg}</div>;
|
||||
return <div className="errors">Something went wrong</div>;
|
||||
},
|
||||
|
||||
renderButton: function () {
|
||||
return (
|
||||
<button
|
||||
className="action login"
|
||||
disabled={
|
||||
!this.state.username || !this.state.password || !(this.props.user && this.props.user.username)
|
||||
}
|
||||
onClick={this.login}>
|
||||
<i className="fa fa-sign-in" />
|
||||
login
|
||||
</button>
|
||||
);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
return (
|
||||
<div className="authForm">
|
||||
<label className="field user">
|
||||
{this.props.renaming && 'Old'} username
|
||||
<input type="text" onChange={this.handleUserChange} value={this.state.username} />
|
||||
</label>
|
||||
{this.props.renaming && (
|
||||
<label className="field user">
|
||||
new username
|
||||
<input type="text" onChange={this.handleNewUserChange} value={this.state.newUsername} />
|
||||
</label>
|
||||
)}
|
||||
|
||||
<label className="field password">
|
||||
password
|
||||
<input
|
||||
type={cx({ text: this.state.visible, password: !this.state.visible })}
|
||||
onChange={this.handlePassChange}
|
||||
value={this.state.password}
|
||||
/>
|
||||
<div
|
||||
className="control"
|
||||
onClick={() => {
|
||||
this.setState({ visible: !this.state.visible });
|
||||
}}>
|
||||
<i
|
||||
className={cx('fa', {
|
||||
'fa-eye': !this.state.visible,
|
||||
'fa-eye-slash': this.state.visible,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
{this.renderErrors()}
|
||||
{this.renderButton()}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = LoginForm;
|
||||
133
client/naturalcrit/loginPage/loginForm.less
Normal file
133
client/naturalcrit/loginPage/loginForm.less
Normal file
@ -0,0 +1,133 @@
|
||||
.authForm {
|
||||
width: 400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
padding-top: 50px;
|
||||
|
||||
.switchView {
|
||||
width: 100%;
|
||||
|
||||
&>div {
|
||||
.animate(background-color);
|
||||
display: inline-block;
|
||||
width: 50%;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
font-size: 0.8em;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
|
||||
i {
|
||||
vertical-align: middle;
|
||||
font-size: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.login:hover,
|
||||
.login.selected {
|
||||
background-color: fade(@blue, 20%);
|
||||
}
|
||||
|
||||
.signup:hover,
|
||||
.signup.selected {
|
||||
background-color: fade(@green, 20%);
|
||||
}
|
||||
}
|
||||
|
||||
.field {
|
||||
display:block;
|
||||
position: relative;
|
||||
height:fit-content;
|
||||
margin: 20px 0px;
|
||||
text-align: left;
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
line-height: 1.1em;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-size: 1.2em;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.control {
|
||||
position: absolute;
|
||||
display: block;
|
||||
right: -40px;
|
||||
bottom: 0px;
|
||||
height: 46px;
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
|
||||
i {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
&.password .control {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
button.action {
|
||||
.animate(opacity);
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
font-size: 0.8em;
|
||||
font-weight: 800;
|
||||
color: black;
|
||||
text-transform: uppercase;
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.3;
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
&.login {
|
||||
background-color: @blue;
|
||||
}
|
||||
|
||||
&.signup {
|
||||
background-color: @green;
|
||||
}
|
||||
|
||||
i {
|
||||
margin-right: 10px;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
}
|
||||
button.google {
|
||||
cursor: pointer;
|
||||
width: 191px;
|
||||
height: 46px;
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: unset;
|
||||
background-image: url('../assets/naturalcrit/styles/btn_google_signin_light_normal_web.png');
|
||||
background-size: contain;
|
||||
|
||||
&:hover {
|
||||
background-image: url('../assets/naturalcrit/styles/btn_google_signin_light_hover_web.png');
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-image: url('../assets/naturalcrit/styles/btn_google_signin_light_pressed_web.png');
|
||||
}
|
||||
}
|
||||
|
||||
.errors {
|
||||
margin-bottom: 20px;
|
||||
color: @red;
|
||||
}
|
||||
}
|
||||
@ -33,12 +33,15 @@ const LoginPage = React.createClass({
|
||||
};
|
||||
},
|
||||
componentDidMount: function () {
|
||||
window.document.onkeypress = (e) => {
|
||||
if (e.code == 'Enter') this.handleClick();
|
||||
};
|
||||
|
||||
window.document.addEventListener('keydown', (e) => {
|
||||
if (e.code === 'Enter') this.handleClick();
|
||||
});
|
||||
this.handleRedirectURL();
|
||||
},
|
||||
|
||||
componentWillUnmount: function () {
|
||||
window.document.removeEventListener('keydown', this.handleKeyPress);
|
||||
},
|
||||
|
||||
handleRedirectURL: function () {
|
||||
if (!this.props.redirect) {
|
||||
@ -313,7 +316,7 @@ const LoginPage = React.createClass({
|
||||
<div className="loginPage">
|
||||
<NaturalCritIcon />
|
||||
|
||||
<div className="content">
|
||||
<div className="authForm">
|
||||
<div className="switchView">
|
||||
<div
|
||||
className={cx('login', { 'selected': this.state.view === 'login' })}
|
||||
@ -328,8 +331,8 @@ const LoginPage = React.createClass({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="field user">
|
||||
<label>username</label>
|
||||
<label className="field user">
|
||||
username
|
||||
<input
|
||||
type="text"
|
||||
title={this.state.view === 'signup' ? 'Min 3 characters, and cannot contain ?!¿@ .' : ''}
|
||||
@ -340,16 +343,15 @@ const LoginPage = React.createClass({
|
||||
{this.state.usernameExists && !this.state.checkingUsername && this.state.view === 'signup' ? (
|
||||
<div className="userExists">User with that name already exists</div>
|
||||
) : null}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div className="field password">
|
||||
<label>password</label>
|
||||
<label className="field password">
|
||||
password
|
||||
<input
|
||||
type={cx({ text: this.state.visible, password: !this.state.visible })}
|
||||
onChange={this.handlePassChange}
|
||||
value={this.state.password}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="control"
|
||||
onClick={() => {
|
||||
@ -362,7 +364,7 @@ const LoginPage = React.createClass({
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
{this.renderErrors()}
|
||||
{this.renderButton()}
|
||||
<div className="divider">⎯⎯ OR ⎯⎯</div>
|
||||
|
||||
@ -2,152 +2,6 @@
|
||||
text-align: center;
|
||||
padding-top: 30px;
|
||||
|
||||
.content {
|
||||
|
||||
width: 400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
padding-top: 50px;
|
||||
|
||||
.switchView {
|
||||
width: 100%;
|
||||
|
||||
&>div {
|
||||
.animate(background-color);
|
||||
display: inline-block;
|
||||
width: 50%;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
font-size: 0.8em;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
|
||||
i {
|
||||
vertical-align: middle;
|
||||
font-size: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.login:hover,
|
||||
.login.selected {
|
||||
background-color: fade(@blue, 20%);
|
||||
}
|
||||
|
||||
.signup:hover,
|
||||
.signup.selected {
|
||||
background-color: fade(@green, 20%);
|
||||
}
|
||||
}
|
||||
|
||||
.field {
|
||||
position: relative;
|
||||
margin: 20px 0px;
|
||||
text-align: left;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-size: 0.6em;
|
||||
font-weight: 800;
|
||||
line-height: 1.1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-size: 1.2em;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.control {
|
||||
position: absolute;
|
||||
display: block;
|
||||
right: -40px;
|
||||
bottom: 0px;
|
||||
height: 46px;
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
|
||||
i {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
&.password .control {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.userExists {
|
||||
font-size: 0.6em;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
color: @red;
|
||||
}
|
||||
}
|
||||
|
||||
button.action {
|
||||
.animate(opacity);
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
font-size: 0.8em;
|
||||
font-weight: 800;
|
||||
color: black;
|
||||
text-transform: uppercase;
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.3;
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
&.login {
|
||||
background-color: @blue;
|
||||
}
|
||||
|
||||
&.signup {
|
||||
background-color: @green;
|
||||
}
|
||||
|
||||
i {
|
||||
margin-right: 10px;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
button.google {
|
||||
cursor: pointer;
|
||||
width: 191px;
|
||||
height: 46px;
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: unset;
|
||||
background-image: url('../assets/naturalcrit/styles/btn_google_signin_light_normal_web.png');
|
||||
background-size: contain;
|
||||
|
||||
&:hover {
|
||||
background-image: url('../assets/naturalcrit/styles/btn_google_signin_light_hover_web.png');
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-image: url('../assets/naturalcrit/styles/btn_google_signin_light_pressed_web.png');
|
||||
}
|
||||
}
|
||||
|
||||
.errors {
|
||||
margin-bottom: 20px;
|
||||
color: @red;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
font-size: 1em;
|
||||
font-weight: 800;
|
||||
|
||||
@ -20,7 +20,6 @@ router.post('/login', async (req, res) => {
|
||||
});
|
||||
|
||||
router.post('/signup', async (req, res) => {
|
||||
|
||||
try {
|
||||
const { user, pass } = req.body;
|
||||
const token = await AccountModel.signup(user, pass);
|
||||
@ -63,4 +62,22 @@ router.get('/user_exists/:username', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/rename', async (req, res) => {
|
||||
try {
|
||||
const { username, newUsername } = req.body;
|
||||
|
||||
const user = await AccountModel.getUser(username);
|
||||
if (!user) return res.status(404).json({ error: 'User not found' });
|
||||
|
||||
user.username = newUsername;
|
||||
await user.save();
|
||||
|
||||
res.json(true);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user