I am trying to setup a Sendgrid Newletter signup, using the following code, stored in pages/api
import axios from "axios";
export default async function handler(req, res) {
if (req.method === "PUT") {
axios
.put(
"https://api.sendgrid.com/v3/marketing/contacts",
{
contacts: [{ email: `${req.body.mail}` }],
list_ids: [process.env.SENDGRID_MAILING_ID],
},
{
headers: {
"content-type": "application/json",
Authorization: `Bearer ${process.env.SENDGRID_SECRET}`,
},
}
)
.then((result) => {
// return
res.status(200).send({
message:
"Your email has been succesfully added to the mailing list. Welcome 👋",
});
})
.catch((err) => {
// return
res.status(500).send({
message:
"Oups, there was a problem with your subscription, please try again or contact us",
});
});
}
}
The front end component looks similar to
import axios from "axios";
import { toast } from "react-toastify";
import { useState } from "react";
const MailingList = () => {
const [mail, setMail] = useState(null);
const [loading, setLoading] = useState(false);
const subscribe = () => {
setLoading(true);
axios
.put("api/mailingList", {
mail,
})
.then((result) => {
if (result.status === 200) {
toast.success(result.data.message);
setLoading(false);
}
})
.catch((err) => {
console.log(err);
setLoading(false);
});
};
return (
<div className='my-10'>
<hr className='my-5' />
<h2 className='text-3xl md:text-3xl font-semibold font-title'>
Stay Tuned!
</h2>
<label className='label'>
<p className='text-lg max-w-xl text-center m-auto leading-9'>
Want to be the first to know when SupaNexTail launches and get an
exclusive discount? Sign up for the newsletter!
</p>
</label>
<div className='mt-5'>
<input
onChange={(e) => {
setMail(e.target.value);
}}
type='email'
placeholder='Your email'
className='input input-primary input-bordered'></input>
<button
onClick={subscribe}
className={`btn ml-3 ${
loading ? "btn-disabled loading" : "btn-primary"
}`}>
I'm in!
</button>
</div>
<hr className='my-5' />
</div>
);
};
export default MailingList;
The emails are actually being added to the Sendgrid mailing list, but no response error is being displayed, email field is not cleared. And this is displayed in the console:
API resolved without sending a response for /api/MailingList, this may result in stalled requests.
The same console warning is displayed when return res.status(..
Need some advice on how to solve this!
Related
so I just got into React and I am still trying to figure out how to do some simple stuff, but I get an error that says : 'Cannot GET /'. I am trying to do back-end also in react so I have an index.js with the following code:
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const app = express();
const port = 3001;
app.use(bodyParser.json());
app.use(cors());
app.post('/', (req, res) => {
res.json({
message: "Hello World!"
});
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});
As per my App.js goes like this:
import React, { useState } from 'react';
import './App.css';
function App() {
const [message, setMessage] = useState('');
const [response, setResponse] = useState('');
const handleSubmit = (event) => {
event.preventDefault();
fetch('http://localhost:3001', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ message })
})
.then(res => res.json())
.then(data => setResponse(data.message))
.catch(err => console.log(err));
}
return (
<div className="App">
<form onSubmit={handleSubmit}>
<label>
Message:
<textarea value={message} onChange={e => setMessage(e.target.value)} />
</label>
<input type="submit" value="Submit" />
</form>
<div>
{response}
</div>
</div>
);
}
export default App;
Maybe there is something wrong in my html tags? I missed a pretty obv thing? I have no clue at this point that's why I am asking for everybody's help :)
expecting a textarea in localhost:3001 and a sumbit value
export const LoginForm = () => {
const setIsLogin = useSetRecoilState(isLoginAtom);
const [isSubmitting, setIsSubmitting] = useState(false);
const router = useRouter();
const onValid = ({ email, password }: LoginProps) => {
axios
.post(`/api/auth/token`, {
email,
password,
})
.then((res) => {
setIsSubmitting(true);
setIsLogin(true);
toast.success('Success Login!');
router.push('/');
})
.catch((err) => {
console.error(err);
toast.error('Login Error');
});
};
const {
register,
handleSubmit,
formState: { errors },
} = useForm<LoginProps>();
return (
<>
<form
className="flex flex-col mx-auto justify-center items-start mt-10 "
onSubmit={handleSubmit(onValid)}
>
//....
</form>
</>
);
};
// _app.tsx
<div>
<Component {...pageProps} />
<ToastContainer
theme="colored"
autoClose={3000}
position="top-center"
/>
</div>
I'm using nextjs 12 version.
The code above is part of the code for login. When I log in, I get the following error:
After logging out and logging back in, the error does not appear.
I read the related issue https://github.com/fkhadra/react-toastify/issues/858
That doesn't seem to help much.
Is the part declared in _app.tsx the problem?
I'm using next-auth 4.18.4 and can't figure out how to set up the Credential provider properly. At this point, when the user logs in, the status remains unauthenticated, and only updates to authenticated when I refresh the page. This is what I have in api/auth/[...nextauth].ts:
import NextAuth from 'next-auth'
import CredentialsProvider from 'next-auth/providers/credentials'
import { verifyPassword } from '../../../lib/auth'
import conn from '../../../lib/db'
export default NextAuth({
providers: [
CredentialsProvider({
async authorize(credentials) {
const query = `SELECT * FROM users WHERE username = $1`
const values = [ credentials.username ]
let user
try {
const result = await conn.query(query, values)
if (result.rows.length > 0) user = result.rows[0]
} catch (err) {
console.log(`Error fetching user from DB: ${err.stack}`)
throw new Error(err)
}
if (!user) throw new Error('No user found!')
const isValid = await verifyPassword(
credentials?.password,
user.password
)
if (!isValid) throw new Error('Could not log you in!')
return {
uid: user.id,
username: user.username,
profilePic: user.profile_pic
}
},
}),
],
})
This is my login page:
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import { zodResolver } from '#hookform/resolvers/zod'
import { signIn } from 'next-auth/react'
import { useRouter } from 'next/router'
import Input from "../components/input"
const validationSchema = z
.object({
userName: z
.string()
.min(1, { message: 'Username is required' })
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d]{5,10}$/, {
message: '5-10 upper and lowercase letters, and digits',
}),
password: z
.string()
.min(5, { message: 'Between 5-10 characters' })
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d]{5,10}$/, {
message: 'Upper and lowercase letters, and digits',
})
})
export default function Auth() {
const {
register,
handleSubmit,
watch,
formState: { errors, isValid },
setValue
} = useForm({
mode: 'all',
resolver: zodResolver(validationSchema),
})
const router = useRouter()
async function submitHandler(data: any) {
const result = await signIn('credentials', {
redirect: false, // don't redirect if user enters wrong credentials
username: data.userName,
password: data.password
})
// console.log(result) // testing
// If the 'error' property is null (meaning log in was successful)
if (!result?.error) {
// Let's clear the input fields
setValue('userName', '')
setValue('password', '')
// And redirect the user to the main page
router.replace('/')
} else {
// If the 'error' property was false, let's print the login error
console.log(`Error: ${JSON.stringify(result.error)}`)
}
}
return (
<div className='text-white max-w-4xl mx-auto pt-10 pb-20 px-2'>
<h1 className='text-2xl text-center pb-8'>Log in</h1>
<form
onSubmit={handleSubmit(submitHandler)}
className='space-y-4 flex flex-col items-center '
>
<Input
id='userName'
type='text'
label='Username'
register={register}
registerOptions={{ required: true }}
errors={errors}
isRequired={true}
/>
<Input
id='password'
type='password'
label='Password'
register={register}
registerOptions={{ required: true }}
errors={errors}
isRequired={true}
/>
<button
type='submit'
disabled={!isValid}
className={`p-3 border-[1px] border-slate-500 rounded-md hover:enabled:bg-white hover:enabled:bg-opacity-20 disabled:cursor-not-allowed w-[90%]`}
>
{isValid ? 'Log In' : 'Please, fill the form'}
</button>
</form>
</div>
)
}
And my index page:
import { useSession } from 'next-auth/react'
export default function Home() {
const { data: session, status } = useSession()
console.log(status);
return (
<div>
<h1 className='text-2xl text-white text-center p-4'>Home Page</h1>
{status === 'authenticated' ?
(<h2 className='text-2xl text-white p-14'>Logged in!</h2>)
:
(<h2 className='text-2xl text-white p-14'>Not logged in!</h2>)
}
</div>
)
}
And the session provider in _app.ts:
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import Layout from '../components/layout'
import { SessionProvider } from 'next-auth/react'
export default function App({
Component,
pageProps: { session, ...pageProps },
}) {
return (
<SessionProvider session={session}>
<Layout>
<Component {...pageProps} />
</Layout>
</SessionProvider>
)
}
By the way, I forgot to add that after logging in, I can see the next-auth.session cookie being created in the browser, but still, status doesn't change until I reload.
How can I add an image from NextJS to Strapi Media library? I Try to upload the image from the NextJS frontend, the image will be uploaded to my Strapi Media library and my Cloudinary account but the image will not be associated/linked to that particular post
Here is my code
path: components/ImageUpload.js
import { useState } from "react";
import { API_URL } from "../config/index";
import styles from "#/styles/FormImage.module.css";
export default function ImageUpload({ sportNewsId, imageUploaded }) {
const [image, setImage] = useState(null);
const handleFilechange = (e) => {
console.log(e.target.files);
setImage(e.target.files[0]);
};
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("files", image);
formData.append("ref", "sports");
formData.append("refid", sportNewsId);
formData.append("field", "image");
const res = await fetch(`${API_URL}/upload`, {
method: "POST",
body: formData,
});
if (res.ok) {
imageUploaded();
}
};
return (
<div className={styles.form}>
<h4>Upload Sport News Image</h4>
<form onSubmit={handleSubmit}>
<div className={styles.file}>
<input type="file" onChange={handleFilechange} />
<input type="submit" value="Upload" className="btn" />
</div>
</form>
</div>
);
}
path:pages/news/edit/[id].js
import Link from "next/link";
import { useState } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
import moment from "moment";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import Layout from "#/components/Layout";
import { API_URL } from "#/config/index";
import styles from "#/styles/FormEdit.module.css";
import Modal from "#/components/Modal";
import ImageUpload from "#/components/ImageUpload";
export default function EditNews({ sportNews }) {
const [values, setValues] = useState({
name: sportNews.name,
detail: sportNews.detail,
date: sportNews.date,
time: sportNews.time,
});
const [previewImage, setPreviewImage] = useState(
sportNews.image ? sportNews.image.formats.thumbnail.url : null
);
const [showModal, setShowModal] = useState(false);
const router = useRouter();
const { name, detail, date, time } = values;
const handleSubmit = async (e) => {
e.preventDefault();
const emptyFieldCheck = Object.values(values).some(
(element) => element === ""
);
if (emptyFieldCheck) {
toast.error("Please fill all input field");
}
const response = await fetch(`${API_URL}/sports/${sportNews.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(values),
});
if (!response.ok) {
toast.error("something went wrong!!!");
} else {
const sport = await response.json();
router.push(`/news/${sport.slug}`);
}
};
const imageUploaded = async (e) => {
const res = await fetch(`${API_URL}/sports/${sportNews.id}`);
const data = await res.json();
console.log("showing =>", data);
console.log(setPreviewImage);
setPreviewImage(data.image[0].formats.thumbnail.url);
setShowModal(false);
};
const handleInputchange = (e) => {
const { name, value } = e.target;
setValues({ ...values, [name]: value });
};
return (
<Layout title="Add New Sport News">
<Link href="/news">Go Back</Link>
<h2>Add Sport News</h2>
<ToastContainer />
<form onSubmit={handleSubmit} className={styles.form}>
<div className={styles.grid}>
<div>
<label htmlFor="name">Name</label>
<input
name="name"
id="name"
type="text"
value={name}
onChange={handleInputchange}
/>
</div>
<div>
<label htmlFor="date">Date</label>
<input
name="date"
id="date"
type="date"
value={moment(date).format("yyyy-MM-DD")}
onChange={handleInputchange}
/>
</div>
<div>
<label htmlFor="time">Time</label>
<input
name="time"
id="time"
type="text"
value={time}
onChange={handleInputchange}
/>
</div>
</div>
<div>
<label htmlFor="detail">Detail</label>
<textarea
name="detail"
id="detail"
type="text"
value={detail}
onChange={handleInputchange}
/>
</div>
<input className="btn" type="submit" value="Add News" />
</form>
{/* {console.log(previewImage)} */}
{previewImage ? (
<Image src={previewImage} height={100} width={180} />
) : (
<div>
<p>No Image Available</p>
</div>
)}
<div>
<button onClick={() => setShowModal(true)} className="btn-edit">
Update Image
</button>
</div>
<Modal show={showModal} onClose={() => setShowModal(false)}>
<ImageUpload sportNewsId={sportNews.id} imageUploaded={imageUploaded} />
</Modal>
</Layout>
);
}
export async function getServerSideProps({ params: { id } }) {
const res = await fetch(`${API_URL}/sports/${id}`);
const sportNews = await res.json();
return {
props: { sportNews },
};
}
this is the error message it is showing.
how do I resolve this error, any assistance will be appreciated
Thanks a lot
For a formData you have to add a header :
'Content-Type': 'multipart/form-data'
I have been struggling during hours to find this. I am uploading a file directly from an entry and not with the /upload route but it might work the same way. Using axios for the post method here is an example :
const form = new FormData();
const postData = {
name: 'test2',
};
form.append('files.image', file);
form.append('data', JSON.stringify(postData));
await axios
.post(getStrapiURL('/ingredients'), form, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
.then((response) => {
// Handle success.
console.log('Well done!');
console.log('Data: ', response.data);
})
.catch((error) => {
// Handle error.
console.log('An error occurred:', error.response);
});
From my observation, the problem is on the setPreviewImage line remove the [0] array brackets from the image in order to access the Cloudinary thumbnail Url you will get from the Strapi API after each image upload.
The function below should make it work
const imageUploaded = async (e) => {
const res = await fetch(`${API_URL}/sports/${sportNews.id}`);
const data = await res.json();
console.log("showing =>", data);
console.log(setPreviewImage);
setPreviewImage(data.image.formats.thumbnail.url);
setShowModal(false);
};
I'm trying to do a credentials auth with next-auth. I have to use a custom sign-in page and I absolutely can't make it work for approximately one entire week.
I have :
// [...nextauth.js]
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
import axios from '#api/axios';
const options = {
providers: [
Providers.Credentials({
async authorize(credentials) {
const { data: user, status } = await axios.post('/users/authentication', credentials);
if (user && status === 200) {
return user;
} else {
throw new Error('error message');
}
}
})
],
pages: {
signIn: '/profil/authentication/login',
error: '/profil/authentication/login'
},
session: {
jwt: true,
maxAge: 30 * 24 * 60 * 60 // 30 days
},
debug: true
};
export default (req, res) => NextAuth(req, res, options);
and :
// profil/authentication/login
import { signOut, useSession } from 'next-auth/client';
import AuthenticationForm from '#components/auth/authenticationForm';
import Layout from '#components/layout';
const Login = () => {
const [session] = useSession();
const intl = useIntl();
return (
<Layout>
{!session && <AuthenticationForm />}
{session && (
<>
Signed in as {session.user.email}
<br />
<button onClick={signOut}>Sign out</button>
</>
)}
</Layout>
);
};
export default Login;
// authenticationForm.js
import { signIn, csrfToken } from 'next-auth/client';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import PasswordInput from '#components/auth/passwordInput';
import Button from '#components/form/button';
import TextInput from '#components/form/textInput';
const AuthenticationForm = ({ csrf }) => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const router = useRouter();
const handleChangeUsername = ({ target: { value } }) => setUsername(value);
const handleChangePassword = ({ target: { value } }) => setPassword(value);
const handleLogin = () => {
signIn('credentials', {
username,
password,
callbackUrl: `${window.location.origin}/profil`
})
.then((res) => {
console.log('form::res -> ', res);
router.back();
})
.catch((e) => {
console.log('form::e -> ', e);
setError('login error');
});
};
useEffect(() => {
if (router.query.error) {
setError(router.query.error);
setUsername(router.query.username);
}
}, [router]);
return (
<form onSubmit={handleLogin}>
<TextInput
name="username"
value={username}
onChange={handleChangeUsername}
/>
<PasswordInput handleChange={handleChangePassword} />
{error && <div>{error}</div>}
<Button type="submit">
connexion
</Button>
<input name="csrfToken" type="hidden" defaultValue={csrf} />
</form>
);
};
AuthenticationForm.getInitialProps = async (context) => {
return {
csrf: await csrfToken(context)
};
};
export default AuthenticationForm;
And for sure a NEXTAUTH_URL=http://localhost:3000 in .env.local.
If I go on /profil/authentication/login, I see my form and when I click connect, I always some errors like : "Failed to fetch", nothing more, or :
[next-auth][error][client_fetch_error] (2)Â ["/api/auth/csrf", TypeError: Failed to fetch]
https://next-auth.js.org/errors#client_fetch_error
Even if I try to delete all the csrf handling in my form and let sign-in "do it alone yea".
I'm really stuck with this lib and I most likely will change for another one but I would like to know what am I doing wrong? Is there a FULL example with custom sign-in page and errors handled on the same sign-in page. This is so basic that I can't understand why I don't find one easily.
#Tralgar
I think that problem is related to CSRF policy on your backend, if you are on localhost then localhost:3000 and localhost:2000 is like two different domains. Just make sure if you have your frontend domain in your backend cors policy (if on localhost it must be with a port)
I was able to fix the error by deleting the .next build folder and creating a new build by running npm run build