How can I send data from a form to a webhook (zappier)? - webhooks

I made a custom theme in WP, and in one page I have this form:
<form id="contactform">
<input id="contactName" name="name" type="text" placeholder="Your Name" required>
<input id="contactEmail" name="address" type="email" placeholder="Your E-mail" required>
<textarea id="contactMessage" name="message" placeholder="Your Message" required></textarea>
<button id="contactSubmitButton" type="submit">SUBMIT</button>
</form>
At the moment I send the form data by email to a specific address and redirect to a thank-you page. I would like to send it to a zap.
The code I use for the current functionality is the following:
<script>
(function($){
$('#contactform').submit( function(event){
event.preventDefault();
var endpoint = '<?php echo admin_url('admin-ajax.php');?>';
var form = $('#contactform').serialize();
var formdata = new FormData;
formdata.append('action','contactus');
formdata.append('nonce', '<?php echo wp_create_nonce('uglyrg');?>');
formdata.append('contactus', form);
$.ajax(endpoint, {
type:'POST',
data:formdata,
processData: false,
contentType: false,
success: function(res){
document.location.href = "../thank-you-for-contacting-us/";
},
error: function(err){
alert(err.responseJSON.data);
}
})
})
})(jQuery)
</script>
and the code in the functions.php file:
add_action('wp_ajax_contactus','contactus_form');
add_action('wp_ajax_nopriv_contactus','contactus_form');
function contactus_form()
{
if( !wp_verify_nonce( $_POST['nonce'],'ascoul'))
{
wp_send_json_error('Nonce is incorrect', 401);
die();
}
$formdata = [];
wp_parse_str($_POST['contactus'], $formdata);
// admin email
$admin_email = get_option('admin_email');
// Email headers
$headers[] = 'Content-Type: text/html; charset=UTF-8';
$headers[] = 'From: Website <' . $admin_email . '>';
$headers[] = 'Reply-to:' . $formdata['address'];
// Who are we sending the email to?
$send_to = $admin_email;
// Subject
$subject = "Enquiry from " . $formdata['name'];
// Message
$message = '';
foreach($formdata as $index => $field)
{
$message .= '<strong>' . $index . '</strong>: ' . $field . '<br />';
}
try {
if(wp_mail($send_to, $subject, $message, $headers))
{
wp_send_json_success('Form submitted!');
}
else {
wp_send_json_error('An error occured!');
}
} catch (Exception $e)
{
wp_send_json_error($e->getMessage());
};
}
I have put so much effort, in creating a custom theme, and I would like to avoid switching to a WP theme that supports Elementor and other plugins that support webhooks without any coding.
I would appreciate your help.
Thank you

I did find a solution, finally:
//send to zappier
$url="hook"
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $formdata
));
curl_exec($ch);
curl_close($ch);

Related

How to upload files from a backend (Heroku) to frontend in (Netlify) hosted on github

I've developed an app that's uploaded to Github and I'm using Heroku to host the (Backend folder) from Github using (automatic deployment) and also using Netlify to host the (Frontend folder) and it's working great in my local computer, but when I try to upload files from my form in frontend it sends a request to the backend and the backend it self saves the file to /uploads folder that's located in frontend directory.
My file structure is like this:
[Server]
- controllers
- - food.js
[Client]
- public
-- uploads
- src
-- pages
--- dashboard
---- food
----- AddFood.js
it's working great on localhost, and this is my code:
(client)
AddFood.js:
import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import Axios from 'axios'
import useDocumentTitle from '../../../hooks/useDocumentTitle'
import Modal from '../../../components/Modal/Modal'
import { Success, Error, Loading } from '../../../components/Icons/Status'
import { createSlug } from '../../../functions/slug'
import goTo from '../../../functions/goTo'
const AddFood = () => {
useDocumentTitle('Add Food')
//Form States
const [foodName, setFoodName] = useState('')
const [foodPrice, setFoodPrice] = useState('')
const [foodDesc, setFoodDesc] = useState('')
const [foodFile, setFoodFile] = useState('')
const [preview, setPreview] = useState()
const [addFoodStatus, setAddFoodStatus] = useState()
const [addFoodMessage, setAddFoodMessage] = useState()
//Form errors messages
const ImgErr = document.querySelector('[data-form-img-msg]')
const foodNameErr = document.querySelector('[data-form-name-msg]')
const priceErr = document.querySelector('[data-form-price-msg]')
const descErr = document.querySelector('[data-form-desc-msg]')
const formMsg = document.querySelector('[data-form-msg]')
const modalLoading = document.querySelector('#modal')
const BASE_URL =
process.env.NODE_ENV === 'development'
? process.env.REACT_APP_API_LOCAL_URL
: process.env.REACT_APP_API_URL
const updateFoodImg = e => {
const file = e.target.files[0]
if (file) {
const fileType = file.type.split('/')[0]
if (fileType === 'image') setFoodFile(file)
const fileSizeToMB = file.size / 1000000
const MAX_FILE_SIZE = 1 //mb
if (fileSizeToMB > MAX_FILE_SIZE) {
if (ImgErr)
ImgErr.textContent = `file size can't be more than ${MAX_FILE_SIZE} MB`
} else {
ImgErr.textContent = ''
}
}
}
useEffect(() => {
// if there's an image
if (foodFile) {
const reader = new FileReader()
reader.onloadend = () => setPreview(reader.result)
reader.readAsDataURL(foodFile)
} else {
setPreview(null)
}
}, [foodFile])
const handleAddFood = async e => {
e.preventDefault()
//using FormData to send constructed data
const formData = new FormData()
formData.append('foodName', foodName)
formData.append('foodPrice', foodPrice)
formData.append('foodDesc', foodDesc)
formData.append('foodImg', foodFile)
if (
ImgErr.textContent === '' &&
foodNameErr.textContent === '' &&
priceErr.textContent === '' &&
descErr.textContent === ''
) {
//show waiting modal
modalLoading.classList.remove('hidden')
try {
const response = await Axios.post(`${BASE_URL}/foods`, formData)
const { foodAdded, message } = response.data
setAddFoodStatus(foodAdded)
setAddFoodMessage(message)
//Remove waiting modal
setTimeout(() => {
modalLoading.classList.add('hidden')
}, 300)
} catch (err) {
formMsg.textContent = `Sorry something went wrong ${err}`
}
} else {
formMsg.textContent = 'please add all details'
}
}
return (
<>
{addFoodStatus === 1 ? (
<Modal
status={Success}
msg='Added food'
redirectLink='menu'
redirectTime='3000'
/>
) : addFoodStatus === 0 ? (
<Modal
status={Error}
msg={addFoodMessage}
msg=''
/>
) : null}
<section className='py-12 my-8 dashboard'>
<div className='container mx-auto'>
<h3 className='mx-0 mt-4 mb-12 text-2xl text-center'>Add food</h3>
<div>
<div className='food'>
{/* Show Modal Loading when submitting form */}
<Modal
status={Loading}
modalHidden='hidden'
classes='text-blue-500 text-center'
msg='Please wait'
/>
<form
method='POST'
className='form'
encType='multipart/form-data'
onSubmit={handleAddFood}
>
<label className='flex flex-wrap items-center justify-center gap-4 mb-8 sm:justify-between'>
<img
src={
preview === null
? 'https://source.unsplash.com/random?food'
: preview
}
alt='food' //change with food image name
className='object-cover p-1 border border-gray-400 w-28 h-28 dark:border-gray-300 rounded-xl'
/>
<input
type='file'
name='foodImg'
id='foodImg'
accept='image/*'
onChange={updateFoodImg}
className='grow-[.7] cursor-pointer text-lg text-white p-3 rounded-xl bg-orange-800 hover:bg-orange-700 transition-colors'
required
/>
<span
className='inline-block my-2 text-red-400 font-[600]'
data-form-img-msg
></span>
</label>
<label htmlFor='foodName' className='form-group'>
<input
type='text'
id='foodName'
className='form-input'
autoFocus
required
onChange={e => setFoodName(createSlug(e.target.value.trim()))}
/>
<span className='form-label'>Food Name</span>
<span
className='inline-block my-2 text-red-400 font-[600]'
data-form-name-msg
></span>
</label>
<label htmlFor='foodPrice' className='form-group'>
<input
type='number'
id='foodPrice'
className='form-input'
min='5'
max='500'
required
onChange={e => setFoodPrice(e.target.value.trim())}
/>
<span className='form-label'>Price</span>
<span
className='inline-block my-2 text-red-400 font-[600]'
data-form-price-msg
></span>
</label>
<label htmlFor='foodDescription' className='form-group'>
<textarea
name='foodDescription'
id='foodDescription'
minLength='10'
maxLength='300'
className='form-input'
required
onChange={e => setFoodDesc(e.target.value.trim())}
></textarea>
<span className='form-label'>Description</span>
<span
className='inline-block my-2 text-red-400 font-[600]'
data-form-desc-msg
></span>
</label>
<div
className='my-14 text-red-400 font-[600] text-center text-xl'
data-form-msg
></div>
<div className='flex items-center justify-evenly'>
<button
type='submit'
className='min-w-[7rem] bg-green-600 hover:bg-green-700 text-white py-1.5 px-6 rounded-md'
>
Add
</button>
<Link
to={goTo('menu')}
className='text-gray-800 underline-hover text-bold dark:text-white'
>
Food Menu
</Link>
</div>
</form>
</div>
</div>
</div>
</section>
</>
)
}
export default AddFood
(server)
foods.js:
const FoodsModel = require(`${__dirname}/../models/food-model.js`)
const { v4: uuidv4 } = require('uuid')
const sharp = require('sharp')
const deleteFile = require('../functions/deleteFile')
const addFood = async (req, res) => {
const { foodName, foodPrice, foodDesc } = req.body
const { foodImg } = req.files
const foodImgName = uuidv4() + foodImg.name
const foodImgMovePath = `${__dirname}/../../client/public/uploads/${foodImgName}.webp`
const foodImgDisplayPath = `/uploads/${foodImgName}`
const foods = new FoodsModel({
foodImgDisplayPath,
foodName,
foodPrice,
foodDesc
})
sharp(foodImg.data)
.rotate()
.resize(200)
.jpeg({ mozjpeg: true, quality: 50 })
.toBuffer()
.then(newBuffer => {
//changing the old jpg image buffer to new webp buffer
foodImg.data = newBuffer
foodImg.mv(foodImgMovePath, err => {
if (err) {
res.json({
message: `Sorry something wrong with server! 😥: ${err}`
})
return
}
foods.save()
res.json({
message: 'Food Added Successfully',
foodAdded: 1
})
})
})
.catch(err => {
res.json({
//https://mhmdhidr-restaurant.netlify.app/uploads/20cc09a0-1811-48b0-bffa-49e7a1981537chicken-legs.webp
message: `Sorry! Something went wrong, check the error => 😥: \n ${err}`,
foodAdded: 0
})
})
}
const getFood = async (req, res) => {
res.json(res.paginatedResults)
}
const deleteFood = async (req, res) => {
const { prevFoodImg } = req.body
const { foodId } = req.params
deleteFile(prevFoodImg)
try {
await FoodsModel.findByIdAndRemove(foodId)
res.json({
message: 'Food Deleted Successfully',
foodDeleted: 1
})
} catch (error) {
res.json({
message: `Sorry! Something went wrong, check the error => 😥: \n ${error}`,
foodDeleted: 0
})
}
}
const updateFood = async (req, res) => {
const { foodName, foodPrice, foodDesc, prevFoodImg } = req.body
const { foodId } = req.params
const { foodImg } = req.files || ''
const foodImgName = uuidv4() + foodImg?.name || ''
const foodImgMovePath = `${__dirname}/../../client/public/uploads/${foodImgName || ''}`
const foodImgDisplayPath =
foodImg !== '' && foodImg !== undefined ? `/uploads/${foodImgName}` : prevFoodImg
try {
await FoodsModel.findByIdAndUpdate(foodId, {
foodImgDisplayPath,
foodName,
foodPrice,
foodDesc
})
if (foodImg !== '' && foodImg !== undefined) {
deleteFile(prevFoodImg)
foodImg.mv(foodImgMovePath, err => {
if (err) {
res.json({ message: `Sorry something wrong with server! 😥: ${err}` })
}
})
}
res.json({
message: 'Food Updated Successfully',
foodUpdated: 1
})
} catch (error) {
res.json({
message: `Sorry! Something went wrong, check the error => 😥: \n ${error}`,
foodUpdated: 0
})
}
}
module.exports = { addFood, getFood, deleteFood, updateFood }
But when I try to upload a file in the Netlify app this error shows up:
Error: ENOENT: no such file or directory, open '/app/controllers/../../client/public/uploads/9631bb96-e41d-4c9a-aa35-d22b551ab662MASHAWI-IN-DUBAI.jpeg.webp'
I've tried to Google it a lot but unfortunately didn't find a solution.
Thanks for your help.
A two part answer:
Your back-end has no business putting files into your front-end's directory structure.
A better choice might be to use an uploads/ folder in the back-end project, exposing those over HTTPS, and linking to them from your front-end.
But that won't work on Heroku due to its ephemeral filesystem.
An even better choice would be to save them to a cloud-based object store like Amazon S3 or Azure Blob Storage, or a more specialized service like Cloudinary if they're images. Heroku tends to recommend S3.
Your back-end now just needs to store the URL to each file and provide that link to your front-end upon request.
Even on other hosts that allow you to save files into your back-end's filesystem, using a third-party service has many benefits. You can trivially scale horizontally (adding new nodes), your application becomes less stateful, etc.
User uploads never belong in your code repository, no matter how and where you choose to host them. They are content, not code, and should not be tracked and versioned alongside your code.

How i can update the image url if user select new file other wise image url not update in nodejs

I'm creating a book project where i'm saving the books images into the cloudinary and there url's saving into the mongodb database which are working well.But i'm facing issue during the updation of a book when i update my book then the url of book is not updated and console giving me error Cannot read properties of undefined (reading 'map') where i want to update the url with new one url of image but its not working Please any one can solve this
this is my update.js code
module.exports.updateBook = async (req, res) => {
try {
const { id } = req.params;
const book = req.body;
const singleBook = await Book.findById(id);
// Delete Prvious Url From the Cloudinary and Reset It to the new ..
cloudinary.v2.uploader.destroy(singleBook.image[0].filename);
book.image = req.files.map((f) => ({
url: f.path,
filename: f.filename,
}));
console.log("Single Book ===", singleBook);
const updateBook = await Book.findByIdAndUpdate(
id,
{ $set: book },
{ new: true }
);
if (updateBook) {
res
.status(200)
.json({ success: true, message: "Book Updated Successfully!" });
} else {
res.status(400).json({
success: false,
message: "Book Not Updated There Is an error!",
});
}
} catch (err) {
console.log("** Error In Update Book **", err.message);
}
};
this is my route handler
const express = require("express");
const router = express.Router();
const book = require("../controller/book");
const authenticated = require("../middleware/verifyToken");
const multer = require("multer");
const { storage } = require("../cloudinary");
const upload = multer({ storage });
// Update Book By ID
router.route("/:id").put(authenticated, upload.array("image"), book.updateBook);
module.exports = router;
this is my reactjs update method
const formik = useFormik({
initialValues: {
title: book?.title,
author: book?.author,
price: book?.price,
description: book?.description,
image: book?.image[0].url,
},
validationSchema: validationSchema,
enableReinitialize: true,
onSubmit: (values) => {
const formData = new FormData();
formData.append("title", values.title);
formData.append("price", values.price);
formData.append("description", values.description);
formData.append("author", values.author);
formData.append("image", values.image);
Axios.put(`${Base_URL}/book/${id}`, values, {
headers: {
Authorization: authHeader(),
},
})
.then((res) => {
if (res.data.success) {
message = res.data.message;
setAlertContentupdate(message);
setAlertupdate(true);
setTimeout(() => {
handleClose();
navigate(`/book/${id}`);
getBook();
console.log("Response == ", res.data.message);
}, 3000);
}
})
.catch((err) => {
console.log("Error ====", err.message);
});
},
this is my jsx code for updating book
<form onSubmit={formik.handleSubmit}>
<TextField
name="title"
autoFocus
margin="dense"
label="Book Title"
type="text"
fullWidth
variant="standard"
value={formik.values.title}
onChange={formik.handleChange}
error={formik.touched.title && Boolean(formik.errors.title)}
helperText={formik.touched.title && formik.errors.title}
/>
<TextField
name="author"
margin="dense"
label="Book Author"
type="text"
fullWidth
variant="standard"
value={formik.values.author}
onChange={formik.handleChange}
error={formik.touched.author && Boolean(formik.errors.title)}
helperText={formik.touched.author && formik.errors.author}
/>
{/* File Input Field */}
{/* Picture Input */}
<input
type="file"
name="image"
accept=".png, .jpeg, .jpg"
onChange={(e) => {
formik.setFieldValue("image", e.target.files[0]);
}}
/>
{formik.touched.image && formik.errors.image ? (
<div style={{ color: "#e53935", fontSize: "12px" }}>
{formik.errors.image}
</div>
) : null}
{/* Price Input Field */}
<TextField
name="price"
margin="dense"
label="Book Price"
type="text"
fullWidth
variant="standard"
value={formik.values.price}
onChange={formik.handleChange}
error={formik.touched.price && Boolean(formik.errors.price)}
helperText={formik.touched.price && formik.errors.price}
/>
<TextField
name="description"
margin="dense"
label="Book Description"
type="text"
fullWidth
variant="standard"
value={formik.values.description}
onChange={formik.handleChange}
error={
formik.touched.description &&
Boolean(formik.errors.description)
}
helperText={
formik.touched.description && formik.errors.description
}
/>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button type="submit">Update</Button>
</DialogActions>
</form>
In formik i'm getting the book data from back end api's and putting into the formik initial values But Problem is that when i clicked on the update button then the backend compiler giving me this error Cannot read properties of undefined (reading 'map') Please any one can solve this thanks in advance
So this line looks like the issue for me:
cloudinary.v2.uploader.destroy(singleBook.image[0].filename);
Using this is actually deleting your asset so you are probably want to just update it using the explicit API. See https://cloudinary.com/documentation/image_upload_api_reference#explicit
So maybe something like:
cloudinary.v2.uploader.explicit(singleBook.image[0].filename);
Let me know if this helps?

AWS Simple Email Service + Nodemailer = no errors in dev and in prod -- instant delivery in dev -- yet nothing is delivered in prod?

I have been trouble shooting what the disconnect might be between production and development for sending emails using SES + Nodemailer in Nextjs. However, I can't seem to determine what is happening as there are no bounces in my SES console and no errors being logged to the dev console or real-time serverless function logs in production. The repo can be found here
Any insight would be greatly appreciated.
client-side form, #components/Portals/nodemail.tsx
import { Input, Button, Textarea, Logo, ModalBackdrop } from '#components/UI';
import { useState, SyntheticEvent, FC, useEffect, useCallback } from 'react';
import { useUI } from '#components/context';
import css from './contact-us.module.css';
import cn from 'classnames';
import { validEmail } from '#lib/validate-email';
const SendEmail: FC = () => {
const { setModalView } = useUI();
const [inputE1, setInputE1] = useState('');
const [inputE2, setInputE2] = useState('');
const [inputE3, setInputE3] = useState('');
const [inputE4, setInputE4] = useState('');
// const inputText = useRef<HTMLTextAreaElement>(null);
const [dirty, setDirty] = useState(false);
const [message, setMessage] = useState('');
const [disabled, setDisabled] = useState(false);
const [loading, setLoading] = useState(false);
const userSend = async (e: SyntheticEvent<EventTarget>) => {
e.preventDefault();
// const prod = `drisdell.org/api/nodemailer`;
// const dev = '/api/nodemailer';
if (!dirty && !disabled) {
setDirty(true);
handleValidation();
}
setLoading(true);
setMessage('');
let res = await fetch('/api/nodemailer', {
body: JSON.stringify({
text: inputE3,
subject: inputE4,
name: inputE2,
email: inputE1
}),
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
method: 'POST'
});
const { error, data } = await res.json();
if (error) {
setMessage(error);
return;
}
setLoading(false);
setInputE1('');
setInputE2('');
setInputE3('');
setInputE4('');
setMessage(
'Success 🎉 email sent! We will get back to you within several business days' +
`${data}`
);
await setModalView('SUCCESS_VIEW');
};
const handleValidation = useCallback(() => {
if (dirty) {
setDisabled(!validEmail(inputE1) || !inputE2 || !inputE3 || !inputE4);
}
}, [inputE1, inputE2, inputE3, inputE4, dirty]);
useEffect(() => {
handleValidation();
}, [handleValidation]);
return (
<form
onSubmit={userSend}
className={cn('w-100 flex flex-col justify-between')}
>
<div className='flex justify-center pb-4 '>
<Logo className='h-20 w-20 md:h-40 md:w-40 rounded-full' />
</div>
<div className='relative max-w-xl mx-auto'>
<ModalBackdrop />
{message && (
<div className='text-white border border-white p-2 mb-2 rounded-2xl'>
{message}
</div>
)}
<label htmlFor='email'>{'Email Address'}</label>
<Input
id='from-input'
name='from'
placeholder='user#example.net'
onChange={setInputE1}
required={true}
type='email'
className='mb-2 bg-primary-9 text-primary-0 font-medium focus:outline-none rounded-md'
/>
<label htmlFor='name'>{'Full Name'}</label>
<Input
id='name-input'
name='name'
placeholder='first & last names'
onChange={setInputE2}
required={true}
type='text'
className='mb-2 bg-primary-9 text-primary-0 font-medium focus:outline-none rounded-md'
/>
<label htmlFor='subject'>{'Subject'}</label>
<Input
id='subject-input'
name='subject'
placeholder='Email subject...'
onChange={setInputE4}
required={true}
type='text'
className='mb-2 bg-primary-9 text-primary-0 font-medium focus:outline-none rounded-md'
/>
<label htmlFor='text'>{'Body'}</label>
<Textarea
id='text-textarea'
name='text'
placeholder='Email body...'
onChange={setInputE3}
required={true}
minLength={5}
cols={1}
className='mb-2 bg-primary-9 text-primary-0 font-medium focus:outline-none rounded-md'
/>
<div className='w-auto px-8 flex flex-col'>
<Button
type='submit'
variant='slim'
loading={loading}
disabled={disabled}
className={cn(
css.root,
'my-4 w-auto max-w-sm bg-primary-7 text-primary-0 hover:bg-primary-9 rounded-md duration-150 transition-colors'
)}
>
{'SUBMIT EMAIL'}
</Button>
</div>
<span className='pt-1 text-center text-sm'>
<span className='text-primary-9'>Interested in a Career?</span>
<a
className='text-primary-9 font-bold hover:underline cursor-pointer'
onClick={() => setModalView('SUBMIT_RESUME_VIEW')}
>
Submit a Resume
</a>
</span>
</div>
</form>
);
};
export default SendEmail;
pages/api/nodemailer.ts
import nodemailer, { SentMessageInfo } from 'nodemailer';
import { NextApiRequest, NextApiResponse } from 'next';
import secrets from 'aws';
import Mail from 'nodemailer/lib/mailer';
const {
SMTP_SENDER_ADDRESS,
SMTP_RECIPIENT_ADDRESS,
SMTP_PASSWORD,
SMTP_USERNAME,
SMTP_BCC_ADDRESS
} = secrets;
const senderAddress = SMTP_SENDER_ADDRESS;
const toAddress = SMTP_RECIPIENT_ADDRESS;
const ccAddress = SMTP_SENDER_ADDRESS;
const bccAddress = SMTP_BCC_ADDRESS;
const smtpUsername = SMTP_USERNAME;
const smtpPassword = SMTP_PASSWORD;
export default async (req: NextApiRequest, res: NextApiResponse) => {
const { text, subject, name, email } = req.body;
try {
const body_subject = `Contact Us Submission Event - ${subject}`;
const body_text = `Contact Us Form Submission via AWS SES & Nodemailer
---------------------------------------------------------
${text}
`;
const body_html = `<html>
<head></head>
<body>
<h1>${subject}</h1>
\n
<h2>Name: ${name}</p>
\n
<h2>email: ${email}</h2>
\n
<p>${text}</p>
</body>
</html>`;
let transporter = nodemailer.createTransport({
host: 'email-smtp.us-east-2.amazonaws.com',
port: 465,
secure: true,
auth: {
user: smtpUsername,
pass: smtpPassword
}
});
let mailOptions: Mail.Options = {
sender: senderAddress,
from: `${senderAddress}`,
to: toAddress,
cc: ccAddress,
bcc: bccAddress,
subject: body_subject,
text: body_text,
html: body_html
};
let response: SentMessageInfo = transporter.sendMail(
mailOptions,
(info, err) => {
if (!err)
console.log(
'\n info.message: ',
info?.message,
'\n info.stack: ',
info?.stack,
'\n info.name: ',
info?.name
);
console.log(err);
return info;
}
);
if (response === typeof Error) {
return res.status(400).json({
error:
'There was an internal error âš™... \n Shoot me an email at [Mary.Drisdell#drisdellconsulting.com]'
});
}
return res.status(200).json({ error: '', data: response ?? '' });
} catch (error) {
return res.status(500).json({ error: error.message || error.toString() });
}
};
Vercel Dev CLI
While troubleshooting I came across the vercel dev command that can be used locally to test api routes in a vercel environment with the next framework. That said, I have been utilizing this new dev script and it results in a near-instant delivery to targeted addresses with no errors all the same...
Vercel real-time serverless function log for /api/nodemailer route after sending an email that was never delivered but has also never bounced 🤔
Any thoughts? I have tried just about everything including quadruple checking my environmental secrets in production. The DNS records tied to the primary domain for SES match up, as does the unlimited AWS SES Access Programmatic IAM policy I've configured. Any help would be tremendously appreciated as I have been troubleshooting this daily since last weekend.
The live production site where you can send an email is here.

Form data in self.body is null. I am using Preact and Total js. value="#{M.email}" is not working. It populates the form with this value

submitHandler is the function that I call onSubmit and sending data using XMLHttpRequest. I am sending data using xhr.Send() but in the controller, in self.body I am getting null values.
class Form extends Component {
render(props, state) {
<div>
<div class="field">
<label class="label">PHONE NUMBER</label>
<div class="control">
<input
class="input"
type="tel"
placeholder="+91 "
name="phone"
value="#{M.phone}"
onInput={linkstate(this, "phone")}
/>
</div>
</div>
</div>;
}
}
export default Form;
submitHandler = () => {
let formData = new FormData(document.getElementById("signup"));
let xhr = new XMLHttpRequest();
xhr.open("POST", "/xhr", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
console.log("Request finished");
}
};
xhr.onload = () => {
alert(xhr.responseText);
};
xhr.send(formData);
};
Are you sure that formData contains some data? Check the request in web developer tools and try to catch data.
Self.body from my understanding requires a JSON object. I converted the formData into a JSON object and it works.
xhr.send(JSON.stringify(Object.fromEntries(formData)));

How to add action buttons in a popup and navigate to a website in chrome extension?

I am in need to a show 2 pop ups on 2 different situation.Presently I am checking a server file and storing its credentials in a localStorage.Each time when the user clicks on the extension,it should check if the localStorage is empty or not.If it is empty,then a pop up should be seen and asks for his username.this is stored in localstorage.Next time when the user clicks on the icon,the localstorage is not empty,so it should show another pop up showind a field for username with 2 buttons namely 'change settings' and 'go to website'.When the user clicks on change settings,again the popup shuuld appear asking user name.If he clicks go to website,it should navigate to a website.How can this be done?please help me.I have tried button the button is not working.And also the 2nd pop up is always shown only on reloading the extension.Please help me.
Here is my background.js
here is my updated popup.js
window.addEventListener('DOMContentLoaded', function() {
var divLoading = document.querySelector('div#loadingContainer');
var divSettings = document.querySelector('div#settingsContainer');
var divLoggedIn = document.querySelector('div#loggedInContainer');
var divChange = document.querySelector('div#settingsChange');
var user1 = divSettings.querySelector('input#user1');
var form = divSettings.querySelector('form#userinfo');
var user2 = divLoggedIn.querySelector('span#user2');
var change = divLoggedIn.querySelector('input#change');
var calpine = divLoggedIn.querySelector('input#calpine');
var user3 = divChange.querySelector('input#user3');
var form3 = divChange.querySelector('input#changeset');
var cancel = divChange.querySelector('input#emailcancel');
var user = localStorage.username;
if (user) {
// user1.value = user2.value = user;
user1.value = user2.textContent = user;
user3.value = user;
divLoggedIn.style.display = 'block';
divSettings.style.display = 'none';
divChange.style.display = 'none';
} else {
divSettings.style.display = 'block';
divChange.style.display = 'none';
divLoggedIn.style.display = 'none';
user1.focus();
user1.select();
}
divLoading.style.display = 'none';
form.addEventListener('submit', function(evt) {
evt.preventDefault();
var userStr = user1.value;
chrome.runtime.getBackgroundPage(function(bgPage) {
bgPage.login(userStr);
});
window.close();
});
form3.addEventListener('click', function() {
var userStr = user3.value;
chrome.runtime.getBackgroundPage(function(bgPage) {
bgPage.login(userStr);
});
window.close();
});
change.addEventListener('click', function(evt) {
divLoggedIn.style.display = 'none';
divSettings.style.display = 'none';
divChange.style.display = 'block';
user3.focus();
user3.select();
});
cancel.addEventListener('click', function() {
divLoggedIn.style.display = 'block';
divSettings.style.display = 'none';
divChange.style.display = 'none';
user3.focus();
user3.select();
});
calpine.addEventListener('click', function() {
chrome.tabs.create({ url: 'http://www.calpinemate.com/' });
});
});
I have made some changed as i was asked to do so.I have added a new div named divchange.
here is my updated userinfo.html
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="popbak.js"></script>
</head>
<body>
<div id="loadingContainer"></div>
<div id="settingsContainer">
<b>Please Enter your Email ID/Employee Code</b>
<br />
<br />
<form id="userinfo">
<table>
<tr><td> <label for="user">Email/Employee Code:</label></td>
<td> <input type="text" id="user1" required /></td></tr>
<tr><td> <input type="submit" id="login" value="Log In" /></td></tr>
</table>
</form>
</div>
<div id="settingsChange">
<b>Please Enter your Email ID/Employee Code</b>
<br />
<br />
<table>
<tr><td><label for="user">Email/Employee Code:</label></td>
<td><input type="text" id="user3" required /></td></tr>
<tr><td><input type="button" id="emailchange" value="Change" /></td>
<td><input type="button" id="emailcancel" value="Cancel" /></td> </tr>
</table>
</div>
<div id="loggedInContainer">
<table>
<tr><td> <label for="user">Email/Employee Code:</label></td>
<!-- <input type="text" id="user2" readonly /> -->
<td><span id="user2"></span></td> </tr>
<br />
<tr><td><input type="button" id="calpine" value="Go to Calpinemate"/></td>
<td><input type="button" id="change" value="Change Settings"/></td></tr>
</table>
</div>
</body>
</html>
here is my bgpage.login()
function login(useremail){
if(!useremail)
{
alert('Please enter your Email/Employee code'); //this is not working
return;
}
var urlPrefix = 'http://www.calpinemate.com/employees/attendanceStatus/';
var urlSuffix = '/3';
var req1 = new XMLHttpRequest();
req1.addEventListener("readystatechange", function() {
if (req1.readyState == 4) {
if (req1.status == 200) {
var item=req1.responseText;
if(item==1){
localStorage.username=useremail;
updateIcon();
}
else
{
alert('Please enter a valid Email/employee code');
updateIcon();
}
}
else {
alert("ERROR: status code " + req1.status);
}
}
});
var url = urlPrefix + encodeURIComponent(useremail) + urlSuffix;
req1.open("GET", url);
req1.send(null);
}
Here is my background.js
var myNotificationID = null;
var oldChromeVersion = !chrome.runtime;
setInterval(function() {
updateIcon();
}, 1000);
function getGmailUrl() {
return "http://calpinemate.com/";
}
function isGmailUrl(url) {
return url.indexOf(getGmailUrl()) == 0;
}
function onInit() {
updateIcon();
if (!oldChromeVersion) {
chrome.alarms.create('watchdog',{periodInMinutes:5,delayInMinutes: 0});
}
}
function onAlarm(alarm) {
if (alarm && alarm.name == 'watchdog') {
onWatchdog();
}
else {
updateIcon();
}
function onWatchdog() {
chrome.alarms.get('refresh', function(alarm) {
if (alarm) {
console.log('Refresh alarm exists. Yay.');
}
else {
updateIcon();
}
});
}
if (oldChromeVersion) {
updateIcon();
onInit();
}
else {
chrome.runtime.onInstalled.addListener(onInit);
chrome.alarms.onAlarm.addListener(onAlarm);
}
function updateIcon(){
if(localStorage.username){
var urlPrefix = 'http://www.calpinemate.com/employees/attendanceStatus/';
var urlSuffix = '/2';
var req = new XMLHttpRequest();
req.addEventListener("readystatechange", function() {
if (req.readyState == 4) {
if (req.status == 200) {
var item=req.responseText;
if(item==1){
chrome.browserAction.setIcon({path:"calpine_logged_in.png"});
chrome.browserAction.setBadgeBackgroundColor({color:[190, 190, 190, 230]});
chrome.browserAction.setBadgeText({text:""});
chrome.notifications.clear('id1', function(){});
}
else{
chrome.browserAction.setIcon({path:"calpine_not_logged_in.png"});
chrome.browserAction.setBadgeBackgroundColor({color:[190, 190, 190, 230]});
chrome.browserAction.setBadgeText({text:""});
chrome.notifications.create(
'id1',{
type: 'basic',
iconUrl: '/calpine_not_logged_in.png',
title: 'Warning : Attendance',
message: 'Please mark your Attendance !',
buttons: [{ title: 'Mark',
iconUrl: '/tick.jpg'
},{ title: 'Ignore',
iconUrl: '/cross.jpg'}],
priority: 0},
function(id) { myNotificationID = id;}
);
}
}
else {
alert("ERROR: status code " + req.status);
}
}
});
var url = urlPrefix + encodeURIComponent(localStorage.username) + urlSuffix;
req.open("GET", url);
req.send(null);
}
}
onInit();
First of all, the localStorage of the background-page and that of the popup are not the same objects. Besides, each time the popup is shown, it is loaded anew, thus the localStorage is empty.
UPDATE: Thx to rsanchez's comment, I correct my mistake: the popup shares the localStorage object of the extension (which is the same as the one of the background-page).
You should use the localStorage of the background-page. (Keep in mind this works only because you have a persistent background-page !)
The simplest (and most reliable) way is to have a single popup with two different divs (one for entering credentials and one for logging in) and display only one at a time.
E.g.:
1) Remove any chrome.browserAction.onClicked... listener from the background-page (it won't hurt leaving it there as it will never be triggered, but it will confuse you in the future).
2) Declare a popup in your manifest (if you don't have already done do):
<pre>
...
"browser_action": {
...
"default_popup": "popup.html"
},
...
</pre>
3). Create a file named popup.html with the following code:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="popup.js"></script>
</head>
<body>
<div id="loadingContainer"><h3>Loading...</h3></div>
<div id="settingsContainer" style="display:none;">
<b>Enter your Email ID/Employee Code</b>
<br />
<br />
<form id="userinfo">
<label for="user">Email/Employee Code:</label>
<input type="text" id="user1" required />
<input type="submit" id="login" value="Log In" />
</form>
</div>
<div id="loggedInContainer" style="display:none;">
<label for="user">Email/Employee Code:</label>
<!--<input type="text" id="user2" readonly />-->
<span id="user2"></span>
<br />
<input type="button" id="calpine" value="Go to Calpinemate"/>
<input type="button" id="change" value="Change Settings"/>
</div>
</body>
</html>
</pre>
4) Create a file named popup.js with the following code:
window.addEventListener('DOMContentLoaded', function() {
/* Container divs */
var divLoading = document.querySelector('div#loadingContainer');
var divSettings = document.querySelector('div#settingsContainer');
var divLoggedIn = document.querySelector('div#loggedInContainer');
/* Settings-container fields */
var user1 = divSettings.querySelector('input#user1');
var form = divSettings.querySelector('form#userinfo');
/* LoggedIn-container fields */
//var user2 = divLoggedIn.querySelector('input#user2');
var user2 = divLoggedIn.querySelector('span#user2');
var change = divLoggedIn.querySelector('input#change');
var calpine = divLoggedIn.querySelector('input#calpine');
/* Query the extension's localStorage
* in order to decide which DIV to show */
var user = localStorage.username;
if (user) {
/* 'Username' is set: Show the LoggedIn-container
* (after updating the value of the (readonly) '#user' field) */
//user1.value = user2.value = user;
user1.value = user2.textContent = user;
divLoggedIn.style.display = 'block';
} else {
/* 'Username' is not set: Show the Settings-container */
divSettings.style.display = 'block';
user1.focus();
user1.select();
}
divLoading.style.display = 'none';
/* Listener for '#userinfo' form */
form.addEventListener('submit', function(evt) {
evt.preventDefault();
var userStr = user1.value;
chrome.runtime.getBackgroundPage(function(bgPage) {
bgPage.login(userStr);
});
window.close();
});
/* Listener for '#change' button */
change.addEventListener('click', function(evt) {
divLoggedIn.style.display = 'none';
divSettings.style.display = 'block';
user1.focus();
user1.select();
});
/* Listener for '#calpine' button */
calpine.addEventListener('click', function() {
chrome.tabs.create({ url: 'https://your.domain.goes/here' });
});
});

Resources