I am building an amazon clone, i am at the step to add stripe for payment, my build has worked as expected until now this is for a final please assist - next.js

my console is giving me the non specific error "Uncaught (in promise) AxiosError"
this is my checkout-session, in the terminal it seems to have a problem with the .map in the stripe session , ive verified the object format and made sure it was compliant with stripe standards, i am at a loss and hoping this may be a simple fix
import { useSession, useEffect } from 'next-auth/react'
import React from 'react'
import Header from '../components/Header'
import Image from 'next/image'
import { useSelector } from 'react-redux'
import { selectItems, selectTotal } from '../slices/basketSlice'
import CheckoutProduct from '../components/CheckoutProduct'
import Currency from 'react-currency-formatter'
import { link, loadStripe } from '#stripe/stripe-js'
import axios from 'axios'
const stripePromise = loadStripe(`${process.env.stripe_public_key}`);
function checkout() {
const { data: session } = useSession();
const total = useSelector(selectTotal);
const items = useSelector(selectItems);
const createCheckoutSession = async () => {
const stripe = await stripePromise
const checkoutSession = await axios.post('/api/create-checkout-session', {
items: items,
email: session.user.email
})
const result = await stripe.redirectToCheckout({
sessionId: checkoutSession.data.id
})
if (result.error) alert(result.error.message);
};
return (
<div className='bg-gray-100'>
<Header />
<main className='lg:flex max-w-screen-2xl mx-auto'>
{/* left */}
<div className='flex-grow m-5 shadow-sm'>
<Image
src='https://links.papareact.com/ikj'
width={1020}
height={250}
objectfit='contain'
/>
<div className='flex flex-col p-5 space-y-10 bg-white'>
<h1 className='text-3xl border-b pb-4'>{items.length === 0 ? 'Your Amazon Cart is Empty' : 'Shopping Cart'}</h1>
{items.map((item, i) => (
<CheckoutProduct
key={i}
id={item.id}
title={item.title}
rating={item.rating}
price={item.price}
description={item.description}
category={item.category}
image={item.image}
hasPrime={item.hasPrime}
/>
))}
</div>
</div>
{/* right */}
<div className='flex flex-col bg-white p-10 shadow-md'>
{items.length > 0 && (
<>
<h2 className='whitespace-nowrap'>Subtotal({items.length} items):
<span className='font-bold'>
<Currency quantity={total} currency="USD"/>
</span>
</h2>
<button
onClick={createCheckoutSession}
role={link}
disabled={!session}
className={`button mt-2 ${
!session && 'from-gray-300 to-gray-500 border-gray-200 text-gray-300 cursor-not-allowed'}`}
>
{!session ? 'Sign in to checkout' : 'Proceed to checkout'}
</button>
</>
)}
</div>
</main>
</div>
)
}
export default checkout
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
export default async(req, res) => {
const { items, email } = req.body;
const transformedItems = item.map((item) => ({
quantity: 1,
price_data: {
currency: 'usd',
unit_amount: item.price * 100,
product_data: {
name: item.title,
description: item.description,
images: [item.image]
},
},
}));
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
shipping_options: [`shr_1M2oWjIlRietTXvq5h1c8DDQ`],
shipping_address_collection: {
allowed_countries: ['USA']
},
items: transformedItems,
mode: 'payment',
success_url: `${process.env.HOST}/success`,
cancel_url: `${process.env.HOST}/checkout`,
metadata: {
email,
images: JSON.stringify(items.map((item) => item.image))
}
});
res.status(200).json({ id: session.id })
}

It looks like your request to Stripe is not adhering to the expected structure, and I suspect looking at the logs in your Stripe dashboard will show a list of failed requests with detailed error messages on why they failed.
items is not a parameter when creating Checkout Sessions, line_items is what you're looking for.
shipping_address_collection.allowed_countries is expecting a two-letter country code, so you want to use US instead of USA.

Related

Make react swiper pagination as a separate component and plug it in swiper

I am working on a project with a setup combination of react, tailwind and react icons. In my project multiple sliders are required (some has navigation, some has pagination etc…)
So, I created a slider factory component Carousel and controlling variation through props
// Carousel/index.jsx
import { Children, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { HiOutlineArrowLeft, HiOutlineArrowRight } from "react-icons/hi";
import { Swiper, SwiperSlide } from "swiper/react";
import { Autoplay, Keyboard, Mousewheel, Navigation, Pagination } from "swiper";
import "swiper/css";
import "swiper/css/pagination";
import "swiper/css/navigation";
import NextSlideBtn from "./NextSlideBtn";
import PrevSlideBtn from "./PrevSlideBtn";
const Carousel = ({
children,
loop,
pagination,
navigation,
autoplay,
breakpoints,
}) => {
const childrenArray = Children.toArray(children);
const [swiperRef, setSwiperRef] = useState();
return (
<div className="group relative">
<Swiper
className="rounded overflow-hidden"
onSwiper={setSwiperRef}
modules={[
Autoplay,
Keyboard,
Mousewheel,
Pagination,
Navigation,
]}
mousewheel
keyboard
grabCursor
loop={loop || false}
autoplay={
autoplay && {
delay: 5000,
disableOnInteraction: true,
}
}
spaceBetween={breakpoints && 20}
breakpoints={
breakpoints && {
450: {
slidesPerView: 2,
},
768: {
slidesPerView: 3,
},
1536: {
slidesPerView: 4,
},
}
}
pagination={
pagination && {
clickable: true,
}
}
>
{childrenArray.map((item) => (
<SwiperSlide key={uuidv4()}>{item}</SwiperSlide>
))}
</Swiper>
{navigation && (
<div className="hidden group-hover:block">
<PrevSlideBtn swiperRef={swiperRef}>
<HiOutlineArrowLeft className="text-violet-600" />
</PrevSlideBtn>
<NextSlideBtn swiperRef={swiperRef}>
<HiOutlineArrowRight className="text-violet-600" />
</NextSlideBtn>
</div>
)}
</div>
);
};
export default Carousel;
// Carousel/NextSlideBtn.jsx
const NextSlideBtn = ({ children, swiperRef }) => {
const handleNextSlide = () => swiperRef.slideNext();
return (
<button
onClick={handleNextSlide}
className="absolute z-50 top-1/2 -translate-y-1/2 -right-2 translate-x-2 bg-white shadow-md rounded-full p-3"
>
{children}
</button>
);
};
export default NextSlideBtn;
// Carousel/PrevSlideBtn.jsx
const PrevSlideBtn = ({ children, swiperRef }) => {
const handlePrevSlide = () => swiperRef.slidePrev();
return (
<button
onClick={handlePrevSlide}
className="absolute z-50 top-1/2 -translate-y-1/2 -left-2 -translate-x-2 bg-white shadow-md rounded-full p-3"
>
{children}
</button>
);
};
export default PrevSlideBtn;
One of the requirement was to create custom navigation button, which I managed to accomplish as you can see in the code above, but for more visibility I am marking the flow in short
// Carousel/index.jsx
const [swiperRef, setSwiperRef] = useState();
<div>
<Swiper
onSwiper={setSwiperRef}
...
>
...
</Swiper>
<PrevSlideBtn swiperRef={swiperRef} />
<NextSlideBtn swiperRef={swiperRef} />
</div>
// Carousel/NextSlideBtn.jsx
const NextSlideBtn = ({ children, swiperRef }) => {
const handleNextSlide = () => swiperRef.slideNext();
return (
<button
onClick={handleNextSlide}
...
>
{children}
</button>
);
};
export default NextSlideBtn;
// Carousel/PrevSlideBtn.jsx
const PrevSlideBtn = ({ children, swiperRef }) => {
const handlePrevSlide = () => swiperRef.slidePrev();
return (
<button
onClick={handlePrevSlide}
...
>
{children}
</button>
);
};
export default PrevSlideBtn;
Couple of github issues, stackoverflow questions and swiper docs helped me out to accomplish this, and it was pretty straight forward to understand.
Now my question is how can I customize swiper react pagination (like radio buttons which some websites use, or any other styles…) using tailwind and I want to make it as a separate component like I did for navigation buttons.
I’ve tried many solutions eg:
const paginationBullets = {
type: "custom",
clickable: true,
renderCustom: (_, current, total) => {
return <MyCustomSwiperPaginationComponent current={current} total={total} />;
},
};
But nothing seems to work as expected.
Please help me out.
Component separation is my goal.

react toastify cannot reading content

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?

Nextjs SendGrid Mailing Warning: List API resolved

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!

i have an issue with rendering content from graphCMS, the categories in particular

learning how to build a blog with nextjs and graphCMS, been able to get some content using gql queries but for no understandable reason, i can't seem to render the categories.name content onto the site and I am not getting an error. i would like to know what the problem is as I have encountered this prior to this moment with the sanity.io platform for an e-commerce project.
import React, { useState, useEffect } from 'react'
import Link from 'next/link'
import { getCategories } from '../services'
const Categories = () => {
const [categories, setCategories] = useState([]);
useEffect(() => {
getCategories()
.then((newCategories) => setCategories(newCategories))
}, []);
return (
<div className='bg-white shadow-lg rounded-lg p-8 mb-12'>
<h3 className='text-xl mb-8 font-semibold border-b pb-4'>
Categories
</h3>
{categories.map((category) => {
<Link key={category.slug} href={`/category/${category.slug}`}>
<span className='cursor-pointer block pb-3 mb-3'>
{category.name}
</span>
</Link>
})}
</div>
below is the query that I used to set up the process for rendering the content I need to display on the site
export const getCategories = async () => {
const query = gql`
query GetCategories {
categories {
name
slug
}
}
`
const result = await request(graphqlAPI, query);
return result.categories;
}
Assuming you are getting data and the state is correct, you're missing return in your map
{categories.map((category) => {
// notice this return keyword!!
return <Link key={category.slug} href={`/category/${category.slug}`}>
<span className='cursor-pointer block pb-3 mb-3'>
{category.name}
</span>
</Link>
})}
figured it out, had to change {} to () after the .map element and =>

Trying to create a dynamic link with API and React router, console is showing [object%20Object].json 404?

I'm currently working on a site that pulls Reddit data from different subreddits to show recipes, you can toggle between 3 different ones. That part works fine! I'm able to pull the list of posts from each subreddit, but now I'm trying to make it so when you click a button, it routes you to post details where it'll show the post and comments. I have to do another API call to get that information.
Somewhere along the way, it's getting messed up and it's showing "xhr.js:210 GET https://www.reddit.com/r/recipes/comments/[object%20Object].json 404:" It's a dynamic route, so there are two different parameters I'm trying to use. I did try and console log the parameter for ID(the one showing up as [object object] and it shows up fine by itself and is not an object from what I can tell.
Please see some of the code below where I think things could be going wrong. I'm guessing it's the API call because when I console log it, it only shows the subreddit as an arg but not sure..
redditAPI.js:
import axios from 'axios';
export default axios.create({
baseURL:"https://www.reddit.com/r/"
})
store.js:
import {configureStore} from '#reduxjs/toolkit';
import postReducer from './posts/postSlice';
export const store = configureStore({
reducer: {
posts: postReducer
}
});
postSlice.js:
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import redditApi from '../../common/api/redditApi';
import { redditDetails } from '../../common/api/redditApi';
export const fetchAsyncPosts = createAsyncThunk('posts/fetchAsyncPosts', async (subreddit) => {
const response = await redditApi.get(subreddit)
return response.data.data.children;
});
export const fetchAsyncPostsDetail = createAsyncThunk('posts/fetchAsyncPostsDetail', async (sub, postID) => {
const response = await redditApi.get(`${sub}/comments/${postID}.json`)
return response.data.data.children;
});
const initialState = {
posts: [],
selectedSubreddit: 'recipes.json',
selectedPost: []
}
const postSlice = createSlice({
name: "posts",
initialState,
reducers: {
addPosts: (state, { payload }) => {
state.posts = payload;
},
addSelectedPost: (state, {payload}) => {
state.selectedPost = payload;
},
setSelectedSubreddit(state, action) {
state.selectedSubreddit = action.payload;
}
},
extraReducers: {
[fetchAsyncPosts.pending] : () => {
console.log("Pending");
},
[fetchAsyncPosts.fulfilled] : (state, {payload}) => {
console.log("Fulfilled");
return {...state, posts: payload};
},
[fetchAsyncPosts.rejected]: () => {
console.log("Rejected");
},
[fetchAsyncPostsDetail.fulfilled] : (state, {payload}) => {
console.log("Fulfilled");
return {...state, selectedPost: payload};
},
},
});
export const {addPosts, setSelectedSubreddit} = postSlice.actions;
export const selectSelectedSubreddit = (state) => state.selectedSubreddit;
export const getAllPosts = (state) => state.posts.posts;
export const getSelectedPost = (state) => state.posts.selectedPost;
export default postSlice.reducer;
App.js
import "./App.scss";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import PageNotFound from "./components/PageNotFound/PageNotFound";
import Header from "./components/Header/Header";
import Home from "./components/Home/Home";
import PostDetails from "./components/PostDetails/PostDetails";
import Footer from "./components/Footer/Footer";
import 'bootstrap/dist/css/bootstrap.min.css';
function App() {
return (
<div className="App">
<Router>
<Header/>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/:sub/comments/:postID" element={<PostDetails />} />
<Route path="*" element={ <PageNotFound />} />
</Routes>
</Router>
</div>
);
}
export default App;
Home.js:
import React, {useEffect} from 'react'
import { useDispatch, useSelector } from 'react-redux';
import PostListing from '../PostListing/PostListing'
import { fetchAsyncPosts } from '../../features-redux/posts/postSlice';
function Home() {
const dispatch = useDispatch();
const subreddit = useSelector((state) => state.posts.selectedSubreddit);
useEffect(() => {
dispatch(fetchAsyncPosts(subreddit));
}, [dispatch, subreddit]);
return (
<div>
<div className="jumbotron jumbotron-fluid">
<div className="container text-center">
<h1 className="display-4">Welcome to Tasteful Reddit</h1>
<p className="lead">Toggle between subreddits above to view their recipes.</p>
</div>
</div>
<PostListing />
</div>
)
}
export default Home
postListing.js:
import React from 'react'
import { useSelector } from 'react-redux'
import { getAllPosts } from '../../features-redux/posts/postSlice'
import PostCard from '../PostCard/PostCard';
function PostListing() {
const posts = useSelector(getAllPosts);
const rendering = () => posts.map((post, key) => {
return <PostCard key={key} data={post.data} />;
});
console.log(posts);
return (
<div className="post-wrapper">
<div className="post-container">
{rendering()}
</div>
</div>
)
}
export default PostListing
postCard.js:
import React from 'react'
import parse from 'html-react-parser';
import {Link} from 'react-router-dom';
function PostCard(props) {
const {data} = props;
/* Function to change escaped HTML to string */
const htmlDecode = (input) => {
var doc = new DOMParser().parseFromString(input, "text/html");
return doc.documentElement.textContent;
}
/* Decode reddit JSON's youtube embed HTML */
const youtubeHtmlString = htmlDecode(data.media_embed.content);
/* This function runs through the reddit data to make sure that there is a
an image for the post. If so, shows image
and if its a reddit hosted video or youtube video it will render the video.
Gallery-style posts & all else shows empty div*/
const mediaRender = () => {
if (data.thumbnail !== 'self' && data.thumbnail !== 'default' && data.is_self !== true && data.is_gallery !== true && data.domain !== 'youtu.be' && data.domain !== 'v.redd.it') {
return <img src = {data.url} alt={data.title} className="card-img-top"/>;
} if ( data.is_video == true) {
return (
<div>
<video controls preload = "none">
<source src={data.media.reddit_video.fallback_url} type="video/mp4"/>
Your browser does not support the video tag.
</video>
</div>
)
} if (data.domain == 'youtu.be') {
return (
<div className="Container">
{parse(youtubeHtmlString)}
</div>
)
} else {
return <div></div>
}
}
/* If only text & no photos, render text info*/
const renderSelf = () => {
if(data.is_self == true) {
return (<p>{data.selftext}</p>)
} else {
return <p></p>
}
}
return (
<div className="card mb-3 mx-auto text-center" style={{width: "70%"}}>
<div className="row g-0">
<div className="col-md-5">
{mediaRender()}
</div>
<div className="col-md-7">
<div className="card-body">
<h5 className="card-title">{parse(data.title)}</h5>
<div className="card-text">{renderSelf()}</div>
<div className="card-text"><small className="text-muted">By {data.author}</small></div>
<Link to={`/${data.subreddit}/comments/${data.id}`}>
<button className="btn btn-primary">Go to post</button>
</Link>
</div>
</div>
</div>
</div>
)
}
export default PostCard
postDetails.js:
import React, { useEffect } from 'react'
import { useParams } from 'react-router-dom';
import {useDispatch, useSelector} from 'react-redux';
import { fetchAsyncPostsDetail, getSelectedPost } from '../../features-redux/posts/postSlice';
function PostDetails() {
let {sub, postID} = useParams();
const dispatch = useDispatch();
const data = useSelector(getSelectedPost);
console.log(postID)
useEffect(() => {
dispatch(fetchAsyncPostsDetail(sub, postID));
console.log(dispatch(fetchAsyncPostsDetail(sub, postID)))
}, [dispatch, postID, sub]);
console.log(data);
return (
<div>
PostDetails
</div>
)
}
export default PostDetails
Any help would be appreciated, because I'm lost!
Thanks!
I figured it out today, for those interested!
I finally got it! I figured I was doing something wrong with react router, and that's why I couldn't get it to work but I didn't want to stop using react router. I tried many different ways to fix it, including putting both arguments in an object, I tried changing it by passing what I needed with a pathname AND a state, but the state didn't work right. think that I was passing the state in an incorrect way somehow. I think I wrote it like state{ permalink: data.permalink} or something. I also tried changing it so it only took one parameter, but for some reason the react router Link only wanted to use the post IDs and would go to my page not found if I tried to use permalink. After researching other ways to pass data using React Router, I landed on using From to grab the data from the previous page the user was on and used useLocation to grab the state.
Here is a snippet of what I changed in Link for in PostCard.js. The data was passed in props from postListing.js, just for reference:
return (
<div className="card mb-3 mx-auto text-center" style={{width: "70%"}}>
<div className="row g-0">
<div className="col-md-5">
{mediaRender()}
</div>
<div className="col-md-7">
<div className="card-body">
<h5 className="card-title">{parse(data.title)}</h5>
<div className="card-text">{renderSelf()}</div>
<div className="card-text"><small className="text-muted">By {data.author}</small></div>
<Link to={`/post/${data.id}`}
state={{ from: data}}>
<button className="btn btn-primary">Go to post</button>
</Link>
</div>
</div>
</div>
</div>
)
}
I also stopped using the postId to try and make the API call, and instead only used it to make the dynamic link to the component. After that, I used From's state to grab the permalink(from previous reddit post data) to make the API call to get the comment information.
Here is a snippet of PostDetails.js where I made the changes:
function PostDetails() {
const location = useLocation();
const {from} = location.state;
const comments = useSelector(getSelectedPost);
const dispatch = useDispatch();
useEffect( () => {
dispatch(getPostComments(from.permalink));
}, [dispatch]);
I also found out I was trying to access the data incorrectly in my API call, as the data was structured differently than I originally thought.
Here is the changed thunk in PostSlice.js:
export const getPostComments = createAsyncThunk('posts/getPostComments', async (permalink) => {
const response = await fetch(`http://www.reddit.com${permalink}.json`);
const json = await response.json();
return json[1].data.children.map((comment) => comment.data)
});
There were multiple things wrong with my react app, but hopefully this helps someone else! :)
The second argument to createAsyncThunk is the payloadCreator.
It's called with two arguments, arg which is a single value, and the thunkAPI object.
// First, create the thunk
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
}
)
// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))
You are passing the thunkAPI argument as the postID value in the GET request.
export const fetchAsyncPostsDetail = createAsyncThunk(
'posts/fetchAsyncPostsDetail',
async (sub, postID) => {
const response = await redditApi.get(`${sub}/comments/${postID}.json`)
return response.data.data.children;
}
);
If you need to pass multiple values to your thunk then pack them into a single object.
export const fetchAsyncPostsDetail = createAsyncThunk(
'posts/fetchAsyncPostsDetail',
async ({ sub, postID }, thunkAPI) => {
const response = await redditApi.get(`${sub}/comments/${postID}.json`)
return response.data.data.children;
}
);
...
useEffect(() => {
dispatch(fetchAsyncPostsDetail({ sub, postID }));
}, [dispatch, postID, sub]);

Resources