This post is not a question actually.
More like help to the world for people who are struggling with Nuxt.js and SendGrid.
I've been using stackoverflow for such a long time so maybe now it's my turn to start helping others.
I've been working on Nuxt.js WebApp development for the past 8 weeks.
Nuxt.js is a massive learning curve and challenge for me but I really love working with this technology.
I spent the last 2 days developing sending the form with the use of SendGrid. There's not too much help online and I've been struggling a lot but I made it!
So maybe some people will find my post useful.
Here's my form:
<form
v-show="!isSubmitted"
class="contact-us__form"
#submit.prevent="validate">
<b-form-group :class="{'form-group--error': $v.name.$error}">
<b-form-input
id="name"
v-model.trim="$v.name.$model"
type="text"
placeholder="Full Name">
></b-form-input>
<div class="error" v-if="!$v.name.required">Field is required</div>
<div class="error" v-if="!$v.name.minLength">Name must have at least {{$v.name.$params.minLength.min}} letters.</div>
</b-form-group>
<b-form-group :class="{'form-group--error': $v.phone.$error}">
<b-form-input
id="phone"
v-model.trim="$v.phone.$model"
type="number"
placeholder="Phone Number">
></b-form-input>
<div class="error" v-if="!$v.phone.required">Field is required</div>
</b-form-group>
<b-form-group :class="{'form-group--error': $v.email.$error}">
<b-form-input
id="email"
v-model.trim="$v.email.$model"
type="email"
placeholder="Email Address">
></b-form-input>
<div class="error" v-if="!$v.email.required">Field is required</div>
</b-form-group>
<div class="d-flex align-items-end">
<b-form-group :class="{'form-group--error': $v.message.$error}">
<b-form-textarea
id="message"
v-model.trim="$v.message.$model"
type="text"
placeholder="Message"
></b-form-textarea>
<div class="error" v-if="!$v.message.required">Field is required</div>
<div class="error" v-if="!$v.message.minLength">Name must have at least {{$v.message.$params.minLength.min}} characters.</div>
</b-form-group>
<b-form-group>
<b-button
type="submit"
variant="secondary"
v-html="'S'"
:disabled="submitting" />
</b-form-group>
</div>
</form>
script:
export default {
mixins: [validationMixin],
components: {
subscribeBox
},
data() {
return {
map: bgMap,
name: "",
phone: "",
email: "",
message: "",
submitting: false,
isSubmitted: false,
error: false,
}
},
validations: {
name: {
required,
minLength: minLength(4)
},
phone: {
required,
},
email: {
required,
email
},
message: {
required,
minLength: minLength(5)
}
},
methods: {
validate() {
if (this.$v.$invalid || this.$v.$error|| this.submitting) {
this.$v.$touch();
return
}
this.onSsubmit();
},
async onSsubmit() {
this.submitting = true;
this.error = false;
try {
await this.$axios.$post('/api/v1/send-email', {
name: this.name,
phone: this.phone,
email: this.email,
message: this.message,
});
this.submitting = false
this.isSubmitted = true
await new Promise(resolve => setTimeout(resolve, 2500))
} catch(e) {
this.submitting = false
this.error = true
console.error(e)
}
},
},
}
nuxt.config.js
serverMiddleware: ['~/api/v1/send-email.js'],
api/v1/send-email.js (all the API Keys are placed in .env file)
const express = require("express");
const bodyParser = require('body-parser')
const sgMail = require('#sendgrid/mail');
const app = express();
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
app.use(bodyParser.json())
app.post("/", (req, res) => {
let msg = {
to: req.body.email, // Change to your recipient
from: '', // Change to your verified sender
subject: 'Message from ' + req.body.name,
text: 'telephone ' + req.body.phone + ', ' + 'message ' + req.body.message,
}
sgMail
.send(msg)
.then(() => {
return res.status(200).json({ 'message': 'Email sent!' })
})
.catch((error) => {
return res.status(400).json({ 'error': 'Opsss... Something went wrong ' + error })
})
});
module.exports = {
path: "/api/v1/send-email",
handler: app
};
This app is still not finished but the code is working 100%!
I'm new to Nuxt.js so some bits may not look awesome but I'm also happy and open to feedback and suggestions.
Thank you for reading my post and good luck with your project! :)
Related
There is a problem that is wasting too much time. I installed the Nuxt js recaptcha module. but the information given in the documentation is insufficient. I haven't used recaptcha before. How exactly should I use it.
<template>
<div class="mx-auto mt-5" style="width: 500px; max-width:90%">
<div class="mx-auto mt-5" style="width: 230px;">
<img
src="#/assets/media/images/site/logo.png"
style="width: 110px"
/>.com'a Üye Olun
</div>
<div class="bg-white p-4 mt-2" style="border-radius:20px">
<b-form #submit.prevent="onSubmit" #reset="onReset" v-if="show">
<b-form-group id="input-group-2" label-for="input-2">
<b-form-input
id="input-2"
class="form-control form-control-lg"
v-model="form.userFullName"
placeholder="İsim soyisim"
required
></b-form-input>
</b-form-group>
<b-form-group id="input-group-2" label-for="input-2">
<b-form-input
id="input-5"
class="form-control form-control-lg"
v-model="form.userName"
placeholder="Kullanıcı adı"
required
></b-form-input>
</b-form-group>
<b-form-row>
<b-col>
<b-form-input
id="input-1"
v-model="form.userEmail"
type="email"
class="form-control form-control-lg"
placeholder="E-mail adresiniz"
required
></b-form-input>
</b-col>
<b-col>
<b-form-input
id="input-3"
v-model="form.userPassword"
class="form-control form-control-lg"
placeholder="Şifreniz"
required
></b-form-input>
</b-col>
</b-form-row>
<b-form-group
id="input-group-4"
class="mt-3"
v-slot="{ ariaDescribedby }"
>
<b-form-checkbox-group
v-model="form.checked"
id="checkboxes-4"
:aria-describedby="ariaDescribedby"
>
<b-form-checkbox class="text-dark" value="1"
>Beni Hatırla</b-form-checkbox
>
</b-form-checkbox-group>
</b-form-group>
<b-button
:disabled="isClickSubmit"
type="submit"
class="btn btn-dark btn-lg btn-block"
variant="primary"
>
<b-spinner v-if="isClickSubmit" small style="margin-bottom:3px" type="grow"></b-spinner>
Kaydol</b-button
>
</b-form>
</div>
</div>
</template>
import axios from "axios";
export default {
layout: "default",
data() {
return {
isClickSubmit: false,
form: {
userEmail: "",
userFullName: "",
userName: "",
userPassword: null
},
show: true
};
},
methods: {
async mounted() {
try {
const bune = await this.$recaptcha.init();
console.log(bune);
} catch (e) {
console.log(e);
}
},
async onSubmit(event) {
this.isClickSubmit = true;
this.onReset();
try {
console.log(this.$recaptcha);
const token = await this.$recaptcha.execute("login");
console.log("ReCaptcha token:", token);
// await this.$recaptcha.reset()
const form = this.form;
const sonuc = await axios.post("http://localhost:3000/api/users", {
form
});
this.isClickSubmit = false
} catch (error) {
console.log("Login error:", error);
}
// console.log(JSON.stringify(this.form));
},
onReset() {
this.form.userEmail = "";
this.form.userFullName = "";
this.form.userName = "";
this.form.userPassword = null
}
}
};
nuxt.config.js:
env: {
GOOGLE_SECRET: '...' },
privateRuntimeConfig: {
secretKey: process.env.GOOGLE_SECRET },
modules: [
[
"#nuxtjs/recaptcha",
{
siteKey:process.env.GOOGLE_SECRET ,
version: 3,
} ]
],
You don't seem to have the recaptcha element in your template.
<!-- Add this where you want the captcha, regardless of version -->
<recaptcha #error="onError" #success="onSuccess" #expired="onExpired" />
<script>
export default {
data() {
return {
isClickSubmit: false,
form: {
userEmail: "",
userFullName: "",
userName: "",
userPassword: null,
token: null
},
show: true
};
},
methods: {
onSuccess(token) {
this.form.token = token;
},
onExpired() {
this.$recaptcha.reset();
},
onError(error) {
console.error(error);
}
}
}
Before you make your request, you'll need to send some things to Google. You'll make this call before serving any requests. This function is from a project of mine.
// Backend code
function Recaptcha(token, ip, callback) {
axios.post(`https://www.google.com/recaptcha/api/siteverify?secret=${SECRET_KEY}&response=${token}`,
{
remoteip: ip,
},
{
headers: {
'Content-Type':
'application/x-www-form-urlencoded; charset=utf-8',
},
},
)
.then(callback);
}
Example usage of Recaptcha function:
Hopefully this helps you understand it a bit better.
I want to submit a form data to Quip's Create Document API using vue.js and axios. This is what I've tried so far:
Form:
<form #submit="saveToQuip" method="POST" action="./post-order.html">
<div class="row">
<div class="col-lg-6 col-md-6">
<div class="row">
<div class="col-lg-12">
<div class="checkout__input">
<label>Receiver's Name<span>*</span></label>
<input type="text" name="First Name" v-model="fullname" required>
</div>
</div>
</div>
.....
</form>
JS:
new Vue({
el: '#submit-order',
data () {
return {
cartItems: [],
fullname: '',
facebookname: '',
email: 'NONE',
address: '',
phone_number: '',
payment_method: '',
delivery_dtime: '',
ordernotes: 'NONE'
}
},
methods:{
saveToQuip(submitEvent) {
......
axios.post('where-to-send-form-data', {
headers : {
Authorization: 'Bearer ' + personal_token,
'Content-Type': content_type
//Access-Control-Allow-Origin : *
},
params: {
title: this.fullname,
type: 'document',
member_ids: folder_id,
content: content
}
})
.then((response) => {
console.log(response)
})
.catch(function (error) {
console.log(error);
})
.then(function () {
}); ;
}
}
})
When I try to submit my form, it does not run the axios post command, and there is no error in the console. It just redirects the page to the next page. How do I achieve this?
Remove both method and action from your form and trigger the action from a button
<form #submit="saveToQuip" method="POST" action="./post-order.html">
to
<form>
...
<button #click="saveToQuip()">SAVE</button>
I'm studying reactivity of vuex using nuxt and module mode of store. The problem is, that despite all data in store is changed by actions => mutations successfully, they do not appear on the page, and shows only empty new element of store array. here are my files:
store>contacts>index.js:
let initialData = [
{
id: 1,
name: 'Michael',
email: 'michael.s#mail.com',
message: 'message from Michael'
},
{
id: 2,
name: 'Mark',
email: 'mark.sh#email.com',
message: 'message from Mark'
},
{
id: 3,
name: 'Valery',
email: 'valery.sh#mail.com',
message: 'message from Valery'
}
]
const state = () =>{
return {
contacts: []
}
}
const getters = {
allContacts (state) {
return state.contacts
}
}
const actions = {
async initializeData({ commit }) {
commit('setData', initialData)
},
addNewContact({ commit, state }, newContact) {
commit('addContact', newContact)
}
}
const mutations = {
setData: (state, contacts) => (state.contacts = contacts),
addContact: (state, newContact) => state.contacts.push(newContact)
}
export default { state, getters, mutations, actions}
component itself:
<template>
<div class="contact-form">
<div class="links">
<nuxt-link to="/">home</nuxt-link>
<nuxt-link to="/contact-form">contact form</nuxt-link>
</div>
<h1>leave your contacts and message here:</h1>
<div class="input-wrapper">
<form class="feedback-form" action="">
<div class="name">
<label for="recipient-name" class="col-form-label">Ваше имя:</label>
<input type="text" id="recipient-name" v-model="obj.userName" name="name" class="form-control" placeholder="Представьтесь, пожалуйста">
</div>
<div class="form-group">
<label for="recipient-mail" class="col-form-label">Ваш email:</label>
<input type="email" v-model="obj.userEmail" name="email" id="recipient-mail" class="form-control" placeholder="example#mail.ru">
</div>
<div class="form-group">
<label for="message-text" class="col-form-label">Сообщение:</label>
<textarea name="message" v-model="obj.userMessage" id="message-text" class="form-control"></textarea>
</div>
<button #click.prevent="addToStore()" type="submit">submit</button>
</form>
</div>
<h3>list of contacts</h3>
<div class="contacts-list">
<div class="list-element" v-for="contact in allContacts" :key="contact.id">
id: {{contact.id}} <br> name: {{contact.name}}<br/> email: {{contact.email}}<br/> message: {{contact.message}}
</div>
</div>
</div>
</template>
<script>
import { mapMutations, mapGetters, mapActions } from 'vuex'
export default {
data() {
return {
obj: {
userName: '',
userEmail: '',
userMessage: ''
}
}
},
mounted() {
console.log(this.showGetters)
},
created() {
this.initializeData()
},
methods: {
...mapActions({
initializeData: 'contacts/initializeData',
addNewContact: 'contacts/addNewContact'
}),
addToStore() {
this.addNewContact(this.obj)
},
},
computed: {
...mapGetters({
allContacts: 'contacts/allContacts',
}),
showGetters () {
return this.allContacts
}
},
}
</script>
so, could anybody help to understand, what is wrong?
You've got mismatched field names.
Inside obj you've called them userName, userEmail and userMessage. For all the other contacts you've called them name, email and message.
You can use different names if you want but somewhere you're going to have to map one onto the other so that they're all the same within the array.
You should be able to confirm this via the Vue Devtools. The first 3 contacts will have different fields from the newly added contact.
Hello I use the library view-qrcode-reader for QR Code scan. I try to add a BEEP scan but I do not feel that the library does this kind of thing. So I did the thing manually but it does not work either.
<template>
<div class="container" id="app">
<router-link class="waves-effect waves-light btn" to="/livreur"><i class="material-icons">arrow_back</i>
</router-link>
<div class="row center">
<span v-if="errors" class="card-panel white-text red darken-1"><strong>{{ errors }}</strong></span>
</div>
<div class="row infos">
<span v-if="infos" class="card-panel white-text green green-1"><strong>Email & SMS envoyé à {{ infos.firstname }} {{ infos.lastname }}</strong></span>
</div>
<br/>
<div class="row">
<qrcode-stream #decode="onDecode"></qrcode-stream>
</div>
</div>
</template>
<script>
var attributes = {
setting: {
soundSrc: '/sounds/beep.mp3'
}
}
export default {
name: 'prepa',
data () {
return {
infos: '',
errors: '',
success: ''
}
},
methods: {
onDecode (decodedString) {
var audio = new Audio(attributes.setting.soundSrc)
audio.play()
this.$http.get('/orders/delivery/prepa/' + decodedString, {headers: {Authorization: `Bearer ${localStorage.token}`}})
.then(request => this.trackingInfos(request))
.catch((request) => this.trackingFail(request))
},
trackingInfos (req) {
if (req.data.error) {
return this.trackingFail(req)
}
this.errors = ''
this.infos = req.data.datas
this.save()
},
trackingFail (req) {
if (req.data.error) {
this.errors = req.data.error
}
},
save () {
this.$http.post('/order_histories', {
location: this.infos.city,
status: '/api/order_statuses/' + this.$preparation,
User: '/api/users/' + localStorage.userId,
orderId: '/api/orders/' + this.infos.orderId,
headers: {Authorization: `Bearer ${localStorage.token}`}
})
.then(request => this.successPrepa(request))
.catch(() => this.failPrepa())
},
successPrepa (req) {
if (req.data.error) {
return this.failPrepa(req)
}
this.errors = ''
this.success = req.status
},
failPrepa (req) {
if (req.data.error) {
this.errors = req.data.error
}
}
}
}
</script>
No beep and an error that appears in the console.
:8080/sounds/beep.mp3:1 GET http://127.0.0.1:8080/sounds/beep.mp3 net::ERR_ABORTED 404 (Not Found)
:8080/#/prepa:1 Uncaught (in promise) DOMException
Thanks for your help.
So I have an issue with async validator on my email/user form element. Every time a letter is typed in, it checks for validation. If the email is 30chars, then that is over 30 calls! Anyone know the best way to debounce a vuelidate custom validator? When I try to use debounce, i get all sorts of errors from vuelidate since it's expecting a response.
<div class="form-group">
<label for="emailAddress">Email address</label>
<input type="email" class="form-control col-md-6 col-sm-12" v-bind:class="{ 'is-invalid': $v.user.email.$error }" id="emailAddress" v-model.trim="$v.user.email.$model" #change="delayTouch($v.user.email)" aria-describedby="emailHelp" placeholder="email#example.com">
<small v-if="!$v.user.email.$error" id="emailHelp" class="form-text text-muted">We'll never share your email with anyone.</small>
<div class="error invalid-feedback" v-if="!$v.user.email.required">Email address is required.</div>
<div class="error invalid-feedback" v-if="!$v.user.email.email">This is not a valid email address.</div>
<div class="error invalid-feedback" v-if="!$v.user.email.uniqueEmail">This email already has an account.</div>
</div>
<script>
import { required, sameAs, minLength, maxLength, email } from 'vuelidate/lib/validators'
import webapi from '../services/WebApiService'
import api from '../services/ApiService'
const touchMap = new WeakMap();
const uniqueEmail = async (value) => {
console.log(value);
if (value === '') return true;
return await api.get('/user/checkEmail/'+value)
.then((response) => {
console.log(response.data);
if (response.data.success !== undefined) {
console.log("Email already has an account");
return false;
}
return true;
});
}
export default {
name: "Register",
data() {
return {
errorMsg: '',
showError: false,
user: {
firstName: '',
lastName: '',
email: '',
password: '',
password2: ''
}
}
},
validations: {
user: {
firstName: {
required,
maxLength: maxLength(64)
},
lastName: {
required,
maxLength: maxLength(64)
},
email: {
required,
email,
uniqueEmail //Custom async validator
},
password: {
required,
minLength: minLength(6)
},
password2: {
sameAsPassword: sameAs('password')
}
}
},
methods: {
onSubmit(user) {
console.log(user);
/*deleted*/
},
delayTouch($v) {
console.log($v);
$v.$reset()
if (touchMap.has($v)) {
clearTimeout(touchMap.get($v))
}
touchMap.set($v, setTimeout($v.$touch, 1250))
}
}
}
</script>
When I try to wrap my async function with the debounce, vuelidate does not like it, so I removed it. Not sure how to limit the custom "uniqueEmail" validator.
as 'Teddy Markov' said, you could call $v.yourValidation.$touch() on your input blur event. I think it's more efficient way to use Vuelidate.
<input type="email" class="form-control col-md-6 col-sm-12"
:class="{ 'is-invalid': !$v.user.email.$anyError }"
id="emailAddress" v-model.trim="$v.user.email.$model"
#change="delayTouch($v.user.email)"
#blur="$v.user.email.$touch()"
aria-describedby="emailHelp"
placeholder="email#example.com"
>
I found the best way was to combine with regex for emails
if regex test passes then proceed to check if unique - else skip the check completely.