basic renaming

This commit is contained in:
Víctor Losada Hernández 2024-12-08 00:26:43 +01:00
parent 837125195d
commit 81aa84e22d
7 changed files with 397 additions and 187 deletions

View File

@ -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}`;
},

View File

@ -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;

View 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;

View 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;
}
}

View File

@ -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>

View File

@ -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;

View File

@ -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;