Passing variable from app.js to handlebars template - javascript

So I am creating a quote request contact form on a website and I need to make a confirmation or error message pop up under the form after it has been submitted. The issue I am faced with is how can I set a variable on the express side based on whether there was an error or not with the email sending and then use that variable within my handlebars template in order to display the proper message. I'm thinking I would use a helper to achieve this but I keep hitting a wall on attempting it. The logic should begin withing the transporter.sendMail as that is where the error will be determined. I put comments in to help identify.
Here is the backend of my contact form:
// ==== Contact Form ====
//Create Mailer options
const options = {
viewEngine: {
extname: '.hbs',
layoutsDir: __dirname + '/views/email/',
defaultLayout: 'template',
partialsDir: 'views/partials/'
},
viewPath: 'views/email/',
extName: '.hbs'
};
// Create Transporter
const transporter = nodemailer.createTransport({
host: 'smtp-mail.outlook.com',
port: 587,
auth: {
user: process.env.USER,
pass: process.env.PASS
}
});
// verify connection configuration
transporter.verify(function(error, success) {
if (error) {
console.log('Error with transporter verification:' + `\n${error}`);
}
});
//attach the plugin to the nodemailer transporter
transporter.use('compile', hbs(options));
app.post('/send', (req, res) => {
// Accepts the form data submitted and parse it
let form = new multiparty.Form();
let data = {};
form.parse(req, function(err, fields) {
Object.keys(fields).forEach(function(property) {
data[property] = fields[property].toString();
});
// Create Mail object with options
const mail = {
from: `"********" <${process.env.USER}>`,
to: '************', // receiver email,
subject: 'Quote Request',
template: 'email.body',
// Import variables into email for use with handlebars
context: {
name: data.name,
email: data.email,
number: data.number,
message: data.message
}
};
// Send email
transporter.sendMail(mail, (err, data) => {
if (err) {
console.log(err);
// if error return mailError = true;
}
else {
console.log('Email successfully sent to recipient!');
// if sent return mailSent = true;
}
});
});
});
Here is my script.js:
// Contact Form Client Functions
//get the form by its id
const form = document.getElementById('contact-form');
//add event listener (when clicking the submit button, do the following)
const formEvent = form.addEventListener('submit', (event) => {
// Prevent page from refreshing when submit button clicked
event.preventDefault();
//parse data to formData variable
let mail = new FormData(form);
//send mail
sendMail(mail);
// Determine if sendMail returned an error or not
console.log(typeof mailError);
// reset form feilds to empty
form.reset();
});
const sendMail = (mail) => {
console.log('step 1');
fetch('/send', {
method: 'post',
body: mail
}).then((response) => {
return response.json();
});
};
and here is the section within my template.hbs file that I need dynamically updated:
<div>
{{#if mailSent}}
<h4 style="color: lightgreen">Your message has been sent successfully!</h4>
{{else if mailError}}
<h4 style="color: red">ERROR: There was an issue sending your message, please
try again.</h4>
{{/if}}
</div>

I think you are mixing Server Side Rendering vs Client Side Rendering strategies (I suggest you to read this to understand the difference). Typically you'd want to use one or the other.
Server Side Rendering Approach: Here is a quick StackBlitz example I did based on your code using server side rendering that you can play with. The basic idea with this strategy is to let your express route render the response (using Handlebars):
app.post('/send-email', (req, res) => {
// proceed to send email
sendEmail((err, data) => {
// render view based on response:
res.render('form', {
sent: !err,
message: err?.message,
});
});
});
Notice how res.render is used in this case, we are not sending a JSON response but the direct view result instead, which would look something like this:
<form action="/send-email" method="POST">
<h1>Send Email</h1>
<p>Click send to get a random response!</p>
<input type="email" placeholder="Enter your email" value="test#mail.com" required />
<input type="submit" value="Send" />
</form>
<div class="msg">
{{#if sent}}
<h4 style="color: lightgreen">Your message has been sent successfully!</h4>
{{else if message}}
<h4 style="color: red">
ERROR: There was an issue sending your message, please try again.
<br />
Original server error: {{message}}
</h4>
{{/if}}
</div>
<script>
document.querySelector('form').addEventListener('submit', () => {
document.querySelector('.msg').style.display = 'none';
});
</script>
Notice also how we don't use Javascript here to send the request, just the default behavior of <form> to make the request. This will cause the page to reload.
Client Side Rendering Approach: Here is the same example slightly modified to use AJAX and fetch API.
Now our endpoint must return a JSON response that the client can use to react accordingly:
app.post('/send-email', (req, res) => {
sendEmail((err, data) => {
res.status(!err ? 200 : 500).json({
sent: !err,
message: err?.message,
});
});
});
Then we let the client side Javascript handle the request and subsequent update of the DOM:
<form>
<h1>Send Email</h1>
<p>Click send to get a random response!</p>
<input type="email" name="email" placeholder="Enter your email" value="test#mail.com" required />
<input type="submit" value="Send" />
</form>
<div class="msg">
<h4 class="feedback"></h4>
</div>
<script>
function sendMail(mail) {
return fetch('/send-email', {
method: 'post',
body: mail,
}).then(function (response) {
return response.json();
});
}
var msgContainer = document.querySelector('div.msg');
msgContainer.style.display = 'none';
document.querySelector('form').addEventListener('submit', function (e) {
e.preventDefault();
msgContainer.style.display = 'none';
var mail = new FormData(e.target);
sendMail(mail).then(function (res) {
var message = res.sent
? 'Your message has been sent successfully!'
: 'ERROR: There was an issue sending your message, please try again.';
var feedback = document.querySelector('h4.feedback');
feedback.textContent = message;
feedback.style.color = res.sent ? 'lightgreen' : 'red';
msgContainer.style.display = 'block';
});
});
</script>
This will NOT cause the page to reload.

Related

Undefined values in email response

I'm trying to use AJAX for my contact form which sends the form data to the server. Which should then email me users information through the input fields.
The issue I'm having is, the formData seems to doing as normal (appears in the network on the browser) But when my email comes through i'm getting undefined values?
const submit = document.querySelector('.contact-btn');
submit.addEventListener('click', send);
function send(event){
event.preventDefault();
const url = "https://us-central1-selexin-website.cloudfunctions.net/app/sendemail";
let form = document.querySelector('form');
let formData = new FormData(form);
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if(xhr.readyState === XMLHttpRequest.DONE){
console.log(formData)
// Make a pop up message in green below the textarea box to notify user email was sent.
}
}
xhr.open('POST', url, true);
xhr.send(formData);
};
Below is the field being emailed to me. As you can see, in the email body I've added "run test"as a string and that returns perfect in the email. Why is req.body giving me undefined values?
const transport = nodemailer.createTransport(sendgridTransport({
auth: {
api_key: apiKey
},
}));
app.use(express.urlencoded({extended: false}));
app.use(cors({ origin: true }));
app.post('/sendemail', (req, res) => {
const {name, email, number, message} = req.body;
return transport.sendMail({
to: 'email receiving',
from: 'from this email',
subject: 'New Contact Request',
html: `
<p>You have a new Contact Request</p>
<h3>Contact Details</h3>
<ul>
<li>Name: 'Run test'</li>
<li>Email: ${email}</li>
<li>Number: ${number}</li>
<li>Message: ${message}</li>
</ul>
`
}).then(() => {
if(res.sendStatus(200)){
console.log('it logs');
};
})
});
exports.app=functions.https.onRequest(app);
You're sending your request body as multipart/form-data, not application/x-www-form-urlencoded.
If you wanted to handle the former, you'd need something like the Multer middleware in your express app.
The quick and easy solution is to wrap your FormData in URLSearchParams
xhr.send(new URLSearchParams(formData))
This will post your data as application/x-www-form-urlencoded which is handled by the express.urlencoded() middleware you're already using.
I also highly recommend adding the event listener to your form's submit event instead of a button click. That way, you can catch things like "type Enter to submit"
document.querySelector("form").addEventListener("submit", e => {
e.preventDefault()
const formData = new FormData(e.target)
// ...and the rest of your "send" logic
})

How can I delay a POST request from a user form until AFTER a webhook POST from Stripe is received?

I want users to pay a fee before a POST request from a front end form is processed. I have a Stripe webhook that works fine on the backend, but I'm not sure how to delay the front end posting of the form until after the payment confirmation is received.
In the code below, right now, createTour and createTourPay run at the same time. I would like for createTourPay to execute first, and the createTour only triggers after Stripe posts to my application from the webhook. How can I achieve this?
Controller File (webhook):
exports.webhookCheckout = (req, res, next) => {
const signature = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
signature,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
return res.status(400).send(`Webhook error: ${err.message}`);
}
if (
event.type === 'checkout.session.completed' &&
event.line_items.name === 'New Job Purchase'
) {
res.status(200).json({ recieved: true });
// Somehow, I want this to trigger the execution of the POST request in my front end JS file.
} else {
if (event.type === 'checkout.session.completed')
createBookingCheckout(event.data.object);
res.status(200).json({ recieved: true });
}
};
Front end JS file:
export const createTourPay = async myForm => {
try {
// 1) Get the checkout session from API response
const session = await axios(`/api/v1/tours/tour-pay`);
const complete = 1;
// console.log(session);
// 2) Create checkout form + charge the credit card
await stripe.redirectToCheckout({
sessionId: session.data.session.id
});
} catch (err) {
// console.log(err);
showAlert('error', err);
}
};
export const createTour = async myForm => {
try {
const startLocation = {
type: 'Point',
coordinates: [-10.185942, 95.774772],
address: '123 Main Street',
description: 'Candy Land'
};
const res = await axios({
method: 'POST',
headers: {
'Content-Type': `multipart/form-data; boundary=${myForm._boundary}`
},
url: '/api/v1/tours',
data: myForm
});
if (res.data.status === 'success') {
showAlert('success', 'NEW TOUR CREATED!');
window.setTimeout(() => {
location.assign('/');
}, 1500);
}
} catch (err) {
showAlert('error', err.response.data.message);
}
};
Broadly: don't do this. Instead, you in fact should create some pending/unpaid version of the "tour" (or any other product/service) in your system, then attach the unique id (eg: tour_123) to the Checkout session when you create it, either using the client_reference_id (doc) or metadata (doc):
const session = await stripe.checkout.sessions.create({
// ... other params
client_reference_id: 'tour_123',
metadata: { tour_id: 'tour_123' },
});
Then you'd use the webhook to inspect those values, and update your own database to indicate the payment has been made and that you can fulfill the order to the customer (ship product, send codes, allow access to service etc).
If you really want to proceed with a more synchronous flow, you can use separate auth and capture to sequence your customer experience and capture the funds later after authorizing and creating your tour entity.
Edit: a note about security
You should never trust client-side logic for restricted operations like creating a "paid" tour. A motivated user could, for example, simply call your /api/v1/tours create endpoint without ever going through your payment flow. Unless you validate a payment and track that state on your server you won't be able to know which of these had actually paid you.

Authentication, Compare data input from client side with mySql in server side using express JS

I need help comparing data and return true or false from the cilent side to server side to check an email is valid or not. In the client side, the client will enter an email and click a button to verify, then server will check the database if the email exist or not. If the email exists the the user is valid, and if the email doesn't exist then the client is not valid and cannot proceed to the next page. I'm not really familiar with express and some mysql query. I tested my code in postman application and it returns valid everytime. Here is some sample emails from mySql.
I'm using app.post command in my javascript express code, but looks like i'm doing it wrong and i wrote the if statement incorrectly. In postman application when i check it, it always returns valid and in client side i cannot authenticate with any email. I'm not sure what condition should i put because i'm not really familiar with express.
app.post('/verifyEmail', (req, res) => {
var email = req.body.email;
let sql = 'SELECT * FROM email WHERE email = ?'; //incorrect condition checking
let query = db.query(sql, (err, result) => {
if (email == null || !email) { //incorrect condition checking
throw err;
res.send('invalid');
}
console.log('valid');
res.send('valid');
})
})
In the client side, i'm using Angular with typescript.
//service.ts
email: string[];
getEmailAPI(): Observable < any > {
return this.http.get("http://localhost:8000/verifyEmail")
.map((res: Response) => res.json())
.catch((error: any) => Observable.throw(error.json().error || 'Server error'))
}
//component.ts
Email = [];
isVerified: boolean;
getEmailAPI() {
this.QuestionService.getEmailAPI().subscribe(
data => console.log('All email', this.Email = data),
error => console.log('server returns error')
);
}
verifyEmail(formValue) {
this.AppService.getEmailAPI().subscribe(
data => {
if (data) {
// do whatever needed is with returned data
this.isVerified = true;
} else {
this.isVerified = false;
}
},
error => console.log('server returns error')
);
}
<!--component.html-->
<form #emailVerification='ngForm' ngNativeValidate>
<label>Please Enter Your Email Below</label>
<input name="email" type="text" required/>
<button type="submit" (click)="verifyEmail(emailVerification.value)">VERIFY</button>
</form>
Can anyone help me, please? Please let me know if more snippets are needed.
you will always get valid since you are checking whether the email variable is null and your end result should return the json.Since in your client you're going to get the json. Change the code to following
app.post('/verifyEmail', (req, res) => {
var email = req.body.email;
let sql = 'SELECT count(*) as count FROM email WHERE email = ?';
let query = db.query(sql, [email],(err, result) => {
if (result[0].count == 1) {
res.json({
"data": "valid"
})
} else {
res.json({
"data": "invalid"
})
}
});
});

Axios POST to server returns empty string

I have a ReactJS form, in which you can enter a new username, which is sent to the server via axios POST and finally saved to a database.
When I click "add" (and thus submit the form), however, it does not save the username I typed into the form input, but returns an empty string.
This is the code for the form including the submit-function:
addUser (e) {
var data = {
entry: this.state.currentname
};
axios.post('users/newuser',
data, {
headers: {"Content-Type": "application/x-www-form-urlencoded"}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
render() {
return (
<div className="App">
<form onSubmit={this.addUser} encType="multipart/form-data">
<label>New username:</label>
<input name="username" value={this.state.currentname} onChange={this.handleChange}></input>
<button type="submit" >Add</button>
</form>
<h1>Current Users: </h1>
<ul>
{this.state.users.map((name, n) =>
<li key={"user_"+n}>{name.username}</li>
)}
</ul>
</div>
);
}
users is the file which contains my curd functions. Here is how I'm currently adding data to the database:
router.route('/newuser')
.post(function (req,res) {
var newUser = new Todo();
newUser.username = req.body.entry;
newUser.save(function (err) {
if(err)
res.send(err);
res.send('User added successfully!');
});
})
Unfortunately, I'm not exactly sure what is going wrong, but I assume that I'm not requesting the username correctly in users.js, since it does insert new data into my database, without a username though.
This is my folder structure (don't know if it's relevant):
-client
public
-index.html
src
-App.js(contains the form)
-server.js
-routes
-users.js
What am I doing wrong exactly and how can I fix that?
So I'm not sure if this is the best way to solve it or if there are better ways (I won't mark my own answer as correct answer since I don't know how correct it is).
What I did was the following:
I added a query parameter to the string in my axios.post request and called for that parameter value in node.js in the function where I insert new data.
These are the parts, which I changed:
App.js:
axios.post('users/newuser?username='+data.entry,
data, {
headers: {"Content-Type": "application/x-www-form-urlencoded"}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
user.js:
router.route('/newuser')
.post(function (req,res) {
var newUser = new Todo();
newUser.username = req.body.username;
newUser.save(function (err) {
if(err)
res.send(err);
res.send('User added successfully!');
});
})

How to send form input from client to server for external API call in Angular/Express?

I am using Angular to get form input and then the client will make a request to the server which then makes a call to the external API. How can I get the input on the form to be concatenated to the server's API call?
// app.js
$scope.searchTag = function () {
var tag = $scope.tag.replace(/\s+/, '');
var url = 'http://api.com' + tag;
$http.get('/api')
.then(function (response) {
console.log(response.data);
$scope.tag = '';
$scope.recipes = response.data;
});
};
// server.js
app.get('/api', function (req, res) {
request('http://api.com' + ***tag***, function (error, response,) {
res.json;
});
});
// index.html
<div class="form-group">
<input type="text" ng-model="tag" class="form-control" placeholder="Let's find some recipes for you" style="width: 240px">
</div>

Categories