Reactjs and Stripe: show success and error messages on submit - javascript

I am working on a payment system with Stripe on Reactjs.
I want to be able to display Success or Error messages but I am still new to React and I'm not sure where the code should be placed.
Success message: for when the payment is successful.
Error message: if there was some problem with the payment.
I also want to show the activation code they receive as a response, once the token is created. Like this:
{this.state.code && Your activation code (code)is: {this.state.code} and it is valid for {this.state.subscription_days} days.
But it does not work.
class CheckoutForm extends Component {
constructor(props) {
super(props);
this.state = {
complete: false,
referrer: '',
email: '',
amount: '',
};
this.submit = this.submit.bind(this);
}
componentDidMount() {
console.log(this.props);
let referrer = this.props.match.params.referrer; // url - added route to index.js
if (referrer) {
console.log(referrer);
this.setState({ referrer, });
}
// let amount = this.state.amount;
// document.getElementById('get3months').addEventListener('click', amount._handleAmount);
}
// user clicked submit
submit(ev) {
ev.preventDefault(); // prevent default submission and refreshing the page
this.props.stripe.createToken() // which elements to tokenize
.then(response => {
console.log('Received Stripe token:', response.token);
axios.post('http://10.250.57.37:8000/subscriptions/codes/pay/',
{
token: response.token,
amount: this.state.amount,
email: this.state.email,
referrer: this.state.referrer, // rn name or empty string, filip
},
{
'Content-Type': 'application/json', // header
}
)
.then(response => {
console.log(response);
});
})
.catch(error => {
console.log(error);
});
}
render() {
if (this.state.complete) return <p>Purchase Complete!</p>;
return (
<div className="checkout-form">
<PrimeHeading
heading={this.props.heading}
subheading={this.props.subheading}
/>
<p>Would you like to complete the purchase?</p>
<form onSubmit={this.submit} style={{ minHeight: 300, }}>
<label>
Email
<input
id="email"
name="email"
type="email"
placeholder="Enter your email"
required
onChange={this._handleEmailChange.bind(this)}
value={this.state.email}
/>
</label>
{/* <label>
Referral
<input
id="referrer"
name="referrer"
type="text"
placeholder="Enter your friends' usernames"
required
/>
</label> */}
<CheckoutCardSection />
<Button
// label="Pay" {this.state.amount} "DKK"
onClick={this.submit}
type="button"
size="medium"
backgroundColor="#43ddb1"
color="white"
noShadow
/>
</form>
{this.state.code && <div>Your activation code is: {this.state.code} and it is valid for {this.state.subscription_days} days.</div>}
</div>
);
}
_handleEmailChange(event) {
let email = event.target.value;
this.setState({ email, });
}
}
Let me know if you need more explanation. Help is MUCH appreciated!

In the code that you are showing, you should set a new state in either the then or catch callbacks. You can have some extra properties in your component's state to achieve this.
...
this.state = {
complete: false,
referrer: '',
email: '',
amount: '',
code: null,
subscription_days: null,
error: null,
};
...
And then, you would set it like this:
...
.then(response => {
console.log('Received Stripe token:', response.token);
axios.post('http://10.250.57.37:8000/subscriptions/codes/pay/',
{
token: response.token,
amount: this.state.amount,
email: this.state.email,
referrer: this.state.referrer, // rn name or empty string, filip
},
{
'Content-Type': 'application/json', // header
}
)
// Use the appropiate property in your response to set the values.
// Note that I'm using destructuring assignment
.then(({ code, subscription_days })=> {
this.setState({
code,
subscription_days
});
});
})
.catch(error => {
this.setState({
error: `Your error message.`//Maybe error.message?
});
});
...
Finally, I'd recommend to pull out your network call code from the component to a separate module and just return the response. It'd make your component code more readable.

Related

Vue JS - Checkbox validation error on submit

In my registration form I have checkbox that confirms whether the user accepted the terms and conditions. The checkbox should validate once I hit the submit button, however since the checkbox is initially unselected, the validation error shows up straight away. Eventually, the error disappears reactively once I tick the checkbox, but for this particular scenario I would like to have the validation error show up only after I hit submit (if I did not check it). I'm not getting any particular console errors, but I'm simply getting stuck on the execution. Would anyone be able to show me how I can achieve this? I'd appreciate any help!
Checkbox.vue - this is the component representing the checkbox.
<template>
<div class="check-wrapper">
<label :for="id" class="check-label">
<input v-model="checkboxValue"
:id="id"
:checked="isCheckboxChecked"
:oninput="checkCheckbox()"
type="checkbox"
name="newsletter"/>
<span v-if="labelText && !isLabelHtmlText">{{ labelText }}</span>
<span v-if="labelText && isLabelHtmlText" class="label-html" v-html="labelText"></span>
<span :class="{'check-mark-error': checkboxError}" class="check-mark"></span>
</label>
<p v-if="checkboxError" class="checkbox-error text-error">{{checkboxError}}</p>
</div>
</template>
<script>
data: () => ({
checkboxValue: false
}),
methods: {
updateValue: function () {
if (this.$props.callback) {
this.$props.callback(this.$props.id, this.$props.checkboxData, this.checkboxValue);
}
},
checkCheckbox: function () {
this.updateValue();
}
}
</script>
Register.vue - this is the parent component where the registration takes place
<template>
<BasicCheckbox :id="'terms-privacy'"
:callback="onTermsClick"
:label-text="'terms and conditions'"
:is-label-html-text="true"
:checkbox-error="termsPrivacyError"
class="terms-privacy"/>
</template>
<script>
methods: {
validateData: function (data) {
if (!this.termsPrivacyError) {
this.sendRegistration(data).then(response => {
if (response) {
console.log('Registration successful');
this.loginUser({email: data.email, password: data.password}).then(response => {
if (response) {
console.log('User logged in!');
this.$router.push({name: ROUTE_NAMES_HOME.HOME});
}
})
}
});
}
},
// Terms and Privacy Checkbox
onTermsClick: function (checkboxId, checkboxData, data) {
this.termsPrivacyError = !data ? termsPrivacyErrorText : '';
},
}
</script>
First of all, this is not an elegant solution but it works, we use a computed value to control if the error should be displayed. We update it in submit method, and cancel it when we click it checkbox for demonstration purpose.
new Vue({
el: "#app",
data: {
termsState: false,
validated: false
},
computed: {
termsError() {
return this.validated && !this.termsState
}
},
methods: {
handleTermsState() {
this.validated = false
},
handleSubmit() {
this.validated = true
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id='app'>
<label for="terms">
Terms and Privacy Policy
<input type="checkbox" id="terms" name="terms" v-model="termsState" #change="handleTermsState">
{{ termsState }}
</label>
<p style="color: red" class="for-error terms-error" v-if="termsError">You have to agree the terms and privacy condition.</p>
<div><button type="submit" #click="handleSubmit">Submit</button></div>
</div>
From your scenario, what I understood, the validation is not happening when the user didn't check privacy and policy. If the user ticks and unticks the checkbox, the validation is working.
If that's the case, what you can do is check the child component "Checkbox.vue" data property "checkboxValue" value, as the default value is already false, and it will be true if the user did the action and tick the checkbox. Just before submitting the form, add the checkboxValue condition check.
Here is the updated Register.vue component file:
<template>
<BasicCheckbox
:id="'terms-privacy'"
:callback="onTermsClick"
:label-text="'terms and conditions'"
:is-label-html-text="true"
ref="BasicCheckbox"
:checkbox-error="termsPrivacyError"
class="terms-privacy"
/>
</template>
<script>
methods: {
validateData: function (data) {
if (!this.termsPrivacyError && this.$refs.BasicCheckbox.checkboxValue) {
this.sendRegistration(data).then(response => {
if (response) {
console.log('Registration successful');
this.loginUser({email: data.email, password: data.password}).then(response => {
if (response) {
console.log('User logged in!');
this.$router.push({name: ROUTE_NAMES_HOME.HOME});
}
})
}
});
}
},
// Terms and Privacy Checkbox
onTermsClick: function (checkboxId, checkboxData, data) {
this.termsPrivacyError = !data ? termsPrivacyErrorText : '';
},
}
</script>
What I modified only:
I added the attribute of ref for the component stage `BasicCheckbox':
ref="BasicCheckbox"
And for the validation, I just only added condition whether the ref component 'BasicCheckbox' has value `true':
if (!this.termsPrivacyError && this.$refs.BasicCheckbox.checkboxValue)

Vue form validation

I have some problems with mine Vue app.
I'm trying to validate login form that is connected with my Laravel App.
This is how template looks
<template>
<div>
<div class="main" v-if="canLogin">
<img class="logo" src="../assets/logo.png">
<form id="app"
#submit="checkForm"
method="post">
<p v-if="validation.length">
<b>Please correct the following error(s):</b>
<ul>
<li v-for="validation in validation">{{ error }}</li>
</ul>
</p>
<input class="form-input" type="email" v-model="form.email" id="email" align="center" placeholder="eMail" required>
<input class="form-input" type="password" v-model="form.password" id="password" align="center" placeholder="Password" required>
<button #click.prevent="login" class="submit">Sign In</button>
</form>
</div>
<div class="main" v-if="!canLogin">
<span> YOU ARE BLOCKED FOR 15 MINUTES</span>
</div>
</div>
</template>
As you see I want to foreach errors, but it's always giving error that
'validation' is defined but never used
And this is how mine script looks.
<script>
import User from "../apis/User";
export default {
data() {
return {
form: {
email: "",
password: ""
},
validation: [],
errors: '',
message: '',
canLogin: true,
};
},
mounted() {
User.canLogin().then(response => {
this.canLogin = response.data.canLogin;
});
},
methods: {
checkForm: function (e) {
this.errors = [];
if (!this.form.password) {
this.errors.push("Name required.");
}
if (!this.form.email) {
this.errors.push('Email required.');
} else if (!this.validEmail(this.email)) {
this.errors.push('Valid email required.');
}
if (!this.errors.length) {
return true;
}
e.preventDefault();
},
validEmail: function (email) {
var re = /^(([^<>()[\]\\.,;:\s#"]+(\.[^<>()[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
},
login() {
User.login(this.form)
.then(response => {
this.$store.commit("LOGIN", true);
localStorage.setItem("token", response.data.token);
this.$router.push({ name: "Dashboard" });
this.$snotify.success(response.data.message, {
timeout: 2000,
showProgressBar: true,
closeOnClick: true,
pauseOnHover: true
});
})
.catch(error => {
if (error.response.status === 400) {
this.errors = error.response.data.message;
this.$snotify.error(error.response.data.message, {
timeout: 2000,
showProgressBar: true,
closeOnClick: true,
pauseOnHover: true
});
}
if(error.response.status === 429){
this.canLogin = false;
}
});
}
}
};
</script>
I'm catching few thhings, like, canLogin, this is checking if IP is not blocked.
There is one more error like:
Elements in iteration expect to have 'v-bind:key' directives
I'm just a started with vue so don't judge me if it's simple fix.
BTW: without validation works fine, I believe it's not only problem with those errors and probbly I'm not catching some things as needed.
What am I doing wrong here?
Change
<ul>
<li v-for="validation in validation">{{ error }}</li>
</ul>
To:
<ul>
<li v-for="(error,index) in errors" v-bind:key="index">{{ error }}</li>
</ul>
In vue, you must provide a key for every v-for looping.
And change your data to:
data() {
return {
form: {
email: "",
password: ""
},
validation: [],
errors: [],
message: '',
canLogin: true,
};
},
I made your errors variable an arrayList.

Sending photo with vuejs

I am using ElementUi uploader and i need to send my file with the rest of my form data, but it doesn't seem to send right details of photo to back-end:
Screenshots
Console log when i select an image
Data that sent to back-end
Code
photo input
<el-upload
action="#"
:limit="1"
:multiple="false"
:on-change="photoChanged"
:on-exceed="handleExceed"
list-type="picture-card"
:on-remove="handleRemove"
:on-preview="handlePictureCardPreview"
:before-remove="beforeRemove"
:auto-upload="false">
<i slot="default" class="el-icon-plus"></i>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>
Script
export default {
data() {
return {
dialogImageUrl: '',
dialogVisible: false,
form: {
name: '',
slug: '',
price: '',
new_price: '',
sku: '',
qty: 1,
active: '',
photo: '',
shortDesc: '',
longDesc: '',
region: '',
date1: '',
date2: '',
type: [],
tags: [],
brand_id: '',
categories: [],
resource: '',
user_id: ''
}
}
},
methods: {
onSubmit(e) { //send data to back-end
e.preventDefault();
axios.post('/api/admin/products/store', this.form)
.then(res => {
console.log(res);
})
.catch(error => {
console.log(error);
})
},
handleRemove(file) {
this.form.photo = ''; // remove photo from from when it's removed
},
photoChanged(file, fileList){
this.form.photo = file.raw; // add photo to form when it's selected
console.log('file', file) // screenshot 1
console.log('raw', file.raw) //screenshot 2
},
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url;
this.dialogVisible = true;
},
handleExceed(files, fileList) {
this.$message.warning(`The limit is 1, you selected ${files.length} files this time, add up to ${files.length + fileList.length} totally, remove old image and try again.`);
},
beforeRemove(file) {
return this.$confirm(`Cancel the transfert of ${ file.name } ?`);
}
},
}
</script>
Any idea?
I have used FormData to send the photo or document to the server.
JavaScript FormData
<form id="myForm" name="myForm">
<div>
<label for="username">Enter name:</label>
<input type="text" id="username" name="username" v-model="imageData.name">
</div>
<div>
<label for="useracc">Enter account number:</label>
<input type="text" id="useracc" name="useracc" v-model="imageData.account">
</div>
<label for="userfile">Upload file:</label>
<input type="file" id="userfile" name="userfile">
</div>
<input type="submit" value="Submit!">
</form>
export default {
data() {
return {
imageData: {}
}
},
methods: {
uploadImageToServer() {
// 1.Save the form Data and return the new id to save the image
axios.post('/api/admin/products/store', this.imageData)
.then(res => {
if(res.id) {
//2. Save the image to id
let formData = new FormData();
let file = document.getElementById('userfile');
formData.append('file', file)
axios.post('/api/admin/products/image/{id}', formData)
.then(res => {
console.log(res)
})
}
})
.catch(err => {
console.log(err)
})
}
}
}
Here,
Both form data & file data maynot be send in single requst.
1. Saving the form data and return the id.
2. Saving the image data to the id.
Replace the html with 'element-ui' syntax. Ensure that your rest api receives the form data as the input.
convert your file to base64
when you select an image, use code below
onImageChange() {
let file = this.form.photo
if (file == '')
return;
this.createImage(file);
}
createImage(file) {
let reader = new FileReader();
let el = this
reader.onload = (e) => {
el.form.photo = e.target.files[0];
};
reader.readAsDataURL(file);
},
attach onImageChange function in your input file
Solved
Well I have decided to give up on sending image with rest of data to backend and upload image first with action="#" in my input and in return i get file name in my form and just send the file name with rest of form instead of sending image file.
<el-upload
action="/api/upload"
:on-success="handleAvatarSuccess"
.....>
methods: {
handleAvatarSuccess(res, file) {
this.form.photo = res.data;
},
}
So it sends my file to back-end as soon as it's selected and set the name of stored file in my form.photo and that name will be send with rest of my form inputs.
Hope it could be useful to others as well.

Now to pass form values from React to Node server

I have an EmailList component in react with a form that calls EmailServer.js 'NodeMailer' on the server. I am able to send emails with test subject and message defined in the MailServer.js file statically. How can I pass the subject and message as state to be used in NodeMailer.
EmailList Component
var EmailList = React.createClass({
getInitialState() {
return {
subject: '',
message: ''
}
},
subjectChange(e) {
this.setState({subject: e.target.value})
},
messageChange(e) {
this.setState({message: e.target.value})
},
render() {
return (
<div>
<div>
<Breadcrumb title={this.props.route.title + ' - ' + this.props.location.state.title} />
</div>
<div className='ui-outer'>
<h2 className='text-center'>Email List</h2>
<form action='../EmailServer.js'>
<div className="form-group">
<label htmlFor="subject">Subject</label>
<input type="text" name="subject" className="form-control" onChange={this.subjectChange} value={this.state.subject} />
</div>
<div className="form-group">
<label htmlFor="message">Message</label>
<textarea rows="8" name="message" className="form-control" onChange={this.messageChange} value={this.state.message} />
</div>
<button type="submit" className="btn btn-success">Submit</button>
</form>
</div>
</div>
)
}
})
EmailServer.js
var nodemailer = require('nodemailer');
// Create reusable transporter object using the default SMTP transport
var transporter = nodemailer.createTransport('smtps://user%40gmail.com:pass#smtp.gmail.com');
// Setup e-mail data with unicode symbols
var mailOptions = {
from: 'em#il.com',
to: 'test#email.com',
subject: 'Hello', // <----Pass Subject State HERE
text: 'Hello World', // <-----Pass Message state HERE
html: '<b>Hello World</b>' // <-----Pass Message State HERE
};
// Send email with defined transport object
transporter.sendMail(mailOptions, function(error, info) {
if (error) {
return console.log(error);
}
console.log('Message Sent: ' + info.response);
});
If i got your issue correct, there might be a number of issues here.
You might want to design your code on server side to handle email sending with nodejs express server requested from client.
app.get('/sendemail', function(req, res) {here you go and read request posted data/json})
Where app is nodejs express server instance.
Let react handler to submit a request with one of async http clients (like axios or jquery).
sendEmail: function(e){
e.preventDefault();
$.post(
"/sendemail",{
email: this.state.email,
password: this.state.password,
first_name: this.state.first_name,
last_name: this.state.last_name
}, function(result){
//track result here
});
}
Set form to be submitted with react function:
<form onSubmit={this.sendEmail} >...</form>

React not finding events

I am implementing a very simple React login page. I have started with the following component, Account.
var Account = React.createClass({
getInitialState: function() {
return {
showSignUp: false,
showLogin: true
}
},
update: function(data) {
this.setState(data);
},
render: function() {
if(this.state.showSignUp) {
return <SignUp/>
}
else {
return <Login update={this.update}/>
}
}
});
As expected, the Login component is displayed and renders the following:
return (
<div>
<p><input type="text" placeholder={Language.languagePack.account.username} onChange={this.usernameChange}/></p>
<p><input type="password" placeholder={Language.languagePack.account.password} onChange={this.passwordChange}/></p>
<p><a onClick={this.performLogin}>{Language.languagePack.account.login}</a></p>
<p><a onClick={this.handleSignUp}>{Language.languagePack.account.signUp}</a></p>
<p>{failedMessage}</p>
</div>
)
This all works fine. The application is picking up on the changes via the onChange hook. If the user clicks "Sign Up" though, then the following code is called:
handleSignUp: function() {
this.props.update({showSignUp: true, showLogin: false})
},
Which calls the update method in the Account class, which updates the state and causes a re-render. This is what causes it to switch to the SignUp component.
return (
<div id="signUp">
<p><input type="text" placeholder={Language.languagePack.account.username} onChange={this.usernameChange} /></p>
<p><input type="password" placeholder={Language.languagePack.account.password} onChange={this.passwordChange} /></p>
<p><input type="email" placeholder={Language.languagePack.account.email} onChange={this.emailChange} /></p>
<p><a onClick={this.handleSignUp}>{Language.languagePack.account.signUp}</a></p>
</div>
)
And for some reason, none of the events are firing on this. onChange or onClick doesn't seem to be registered. I think this is related to my implementation of switching components based on a state change that renders different components. My question is, why is this happening and what part of React have I misunderstood to make this happen?
Full Classes
Login Component
var Login = React.createClass({
getInitialState: function() {
return {
username: '',
password: '',
failed: false
}
},
usernameChange: function(event) {
this.setState({
username: event.target.value,
failed: false
});
},
passwordChange: function(event) {
this.setState({
password: event.target.value,
failed: false
});
},
performLogin: function() {
var username = this.state.username;
var password = this.state.password;
console.log("Attempting login with username " + username + " and password " + password);
var _this = this;
Api.login(username, password, function(response) {
_this.props.update({user: response, loggedIn: true});
},
function(response) {
_this.setState({failed: true});
})
},
handleSignUp: function() {
this.props.update({showSignUp: true, showLogin: false})
},
render: function() {
var failedMessage = null;
if(this.state.failed) {
failedMessage = <div className="failed-auth">{Language.languagePack.account.invalidCredentials}</div>;
}
return (
<div>
<p><input type="text" placeholder={Language.languagePack.account.username} onChange={this.usernameChange}/></p>
<p><input type="password" placeholder={Language.languagePack.account.password} onChange={this.passwordChange}/></p>
<p><a onClick={this.performLogin}>{Language.languagePack.account.login}</a></p>
<p><a onClick={this.handleSignUp}>{Language.languagePack.account.signUp}</a></p>
<p>{failedMessage}</p>
</div>
)
}
});
Signup Component
var SignUp = React.createClass({
getInitialState : function() {
return {
username: '',
password: '',
email: ''
}
},
usernameChange: function(event) {
this.setState({
username: event.target.value
});
},
passwordChange: function(event) {
this.setState({
password: event.target.value
});
},
emailChange: function(event) {
this.setState({
email: event.target.value
});
},
handleSignUp : function() {
var username = this.state.username;
var password = this.state.password;
var email = this.state.email;
console.log("Signing up with username=" + username + " and password=" + password + "and email=" + email);
},
handleLogin : function() {
console.log("Fired!");
},
render: function () {
return (
<div id="signUp">
<p><input type="text" placeholder={Language.languagePack.account.username} onChange={this.usernameChange} /></p>
<p><input type="password" placeholder={Language.languagePack.account.password} onChange={this.passwordChange} /></p>
<p><input type="email" placeholder={Language.languagePack.account.email} onChange={this.emailChange} /></p>
<p><a onClick={this.handleSignUp}>{Language.languagePack.account.signUp}</a></p>
</div>
)
}
});
Your code does work; However, I did remove references to language.LanguagePack, since that's not defined in the code you provided. If you have a javascript error, it will prevent code from running.
https://jsfiddle.net/tqz3skcr/2/
var SignUp = React.createClass({
getInitialState : function() {
return {
username: '',
password: '',
email: ''
}
},
usernameChange: function(event) {
console.log('username Changed');
this.setState({
username: event.target.value
});
},
passwordChange: function(event) {
console.log('password Changed');
this.setState({
password: event.target.value
});
},
emailChange: function(event) {
console.log('email changed');
this.setState({
email: event.target.value
});
},
handleSignUp : function() {
var username = this.state.username;
var password = this.state.password;
var email = this.state.email;
console.log("Signing up with username=" + username + " and password=" + password + "and email=" + email);
},
handleLogin : function() {
console.log("Fired!");
},
render: function () {
return (
<div id="signUp">
<p><input type="text" onChange={this.usernameChange} /></p>
<p><input type="password" onChange={this.passwordChange} /></p>
<p><input type="email" onChange={this.emailChange} /></p>
<p><a onClick={this.handleSignUp}></a></p>
</div>
)
}
});
ReactDOM.render(
<SignUp />,
document.getElementById('container')
);
I don't see anything obvious but you could try this pattern to show/hide the components. Toggle showing and hiding components in ReactJs.
First of all, make your life easier and don't use indicators like:
{
showSignUp: true,
showLogin: false
}
something like this would be much simpler and would produce less errors:
{
formToShow: "signUpForm" // or "loginForm"
}
I would say, if you start coding in this way the issue will resolve by "clean code magic" ))

Categories