I'm not entirely sure how to toggle the popover based on a boolean, in my case: if there are search results. I thought I could just use the open attribute. Any help is appreciated!
To sum up this component:
Type in a search term in the input
An API call is being made and results are returned from te API
The PopoverPanel should then be opened
<template>
<form :action="action" method="GET" class="flex-1">
<Popover :open="searchResults.length > 0" class="relative w-full">
<PopoverOverlay
:class="`bg-black ${open ? 'fixed inset-0 opacity-10' : 'opacity-0'}`"
#click="() => emptySearchResults()"
/>
<div class="relative">
<label for="search" class="sr-only">Search...</label>
<input
id="search"
v-model="searchTerm"
type="search"
placeholder="Search..."
class="h-10 w-full px-4 py-2 text-sm rounded-lg placeholder-gray-400 focus:outline-none"
>
</div>
<PopoverPanel class="absolute top-full right-0 w-full mt-3 py-2 px-2 bg-gray-700 rounded-lg shadow-lg">
<a
v-for="result in searchResults"
:key="result.id"
:href="result.url"
class="flex items-center font-semibold p-2 text-sm text-white leading-none rounded-lg hover:bg-gray-600"
>
<span class="mr-3 py-1 px-2 text-xs text-gray-900 leading-none bg-gray-300 rounded-full">
{{ result.module }}
</span>
{{ result.title }}
</a>
</PopoverPanel>
</Popover>
</form>
</template>
<script setup>
import { ref, watch } from 'vue';
import { Popover, PopoverPanel } from '#headlessui/vue';
import debounce from '#/Helpers/debounce';
import xhr from '#/Helpers/xhr';
const searchTerm = ref('');
const searchResults = ref([]);
const getSearchResults = debounce(async (value) => {
if (value === '') return;
const { data } = await xhr.post('search', { q: value });
const { results } = await data;
searchResults.value = results;
}, 250);
const emptySearchResults = () => {
searchTerm.value = '';
searchResults.value = [];
};
watch(searchTerm, (value) => getSearchResults(value));
</script>
Related
I'm currently trying to pass the getUser function from a file called Login.vue which gets the login details to an Authenticated.vue page to display the user and their email. How can I go about doing this. I've tried following the official vue documents but can't get it to work. Not sure whats going wrong.
Login.Vue
<template>
<form #submit.prevent="submitLogin">
<div>
<!-- Email -->
<div>
<label for="email" class="block font-medium text-sm text-gray-700">
Email
</label>
<input v-model="loginForm.email" id="email" type="email" class="block mt-1 w-full rounded-md shadow-sm border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" required autofocus autocomplete="username">
<!-- Validation Errors -->
<div class="text-red-600 mt-1">
<div v-for="message in validationErrors?.email">
{{ message }}
</div>
</div>
</div>
<!-- Password -->
<div class="mt-4">
<label for="password" class="block font-medium text-sm text-gray-700">
Password
</label>
<input v-model="loginForm.password" id="password" type="password" class="block mt-1 w-full rounded-md shadow-sm border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" required autocomplete="current-password">
<!-- Validation Errors -->
<div class="text-red-600 mt-1">
<div v-for="message in validationErrors?.password">
{{ message }}
</div>
</div>
</div>
<!-- Remember me -->
<div class="block mt-4">
<label class="flex items-center">
<input type="checkbox" name="remember" v-model="loginForm.remember" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" />
<span class="ml-2 text-sm text-gray-600">Remember me</span>
</label>
</div>
<!-- Buttons -->
<div class="flex items-center justify-end mt-4">
<button class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:shadow-outline-gray transition ease-in-out duration-150 ml-4" :class="{ 'opacity-25': processing }" :disabled="processing">
Log in
</button>
</div>
</div>
</form>
</template>
<script>
import { useRouter} from 'vue-router';
import { reactive } from '#vue/runtime-core'
export default {
data() {
return {
categories: {},
router: useRouter(),
loginForm: reactive({
email: '',
password: '',
remember: false
}),
user: reactive({
name: '',
email: '',
}),
processing: false,
validationErrors: {},
}
},
mounted() {
},
methods: {
submitLogin() {
if (this.processing) return
this.processing = true;
axios.post('/login', this.loginForm)
.then(response => this.loginUser(response))
.catch(error => {
if (error.response?.data) {
this.validationErrors = error.response.data.errors
}
})
.finally(this.processing = false)
},
loginUser(response){
this.user.name = response.data.name
this.user.email = response.data.email
localStorage.setItem('loggedIn', JSON.stringify(true))
this.router.push({name: 'posts.index'})
},
getUser(){
axios.get('/api/user')
.then(response => {
loginUser(response)
})
this.user.name = response.data.name
this.user.email = response.data.email
console.log(this.user.name);
console.log(this.user.email);
},
},
}
</script>
Authenticated.vue
<template>
<div class="min-h-screen bg-gray-100">
<nav class="bg-white border-b border-gray-100">
<!-- Primary Navigation Menu -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<!-- Logo -->
<div class="shrink-0 flex items-center">
<a href="/">
<svg viewBox="0 0 316 316" xmlns="http://www.w3.org/2000/svg" class="block h-10 w-auto fill-current text-gray-600">
<path d="M305.8 81.125C305.77 80.995 305.69 80.885 305.65 80.755C305.56 80.525 305.49 80.285 305.37 80.075C305.29 79.935 305.17 79.815 305.07 79.685C304.94 79.515 304.83 79.325 304.68 79.175C304.55 79.045 304.39 78.955 304.25 78.845C304.09 78.715 303.95 78.575 303.77 78.475L251.32 48.275C249.97 47.495 248.31 47.495 246.96 48.275L194.51 78.475C194.33 78.575 194.19 78.725 194.03 78.845C193.89 78.955 193.73 79.045 193.6 79.175C193.45 79.325 193.34 79.515 193.21 79.685C193.11 79.815 192.99 79.935 192.91 80.075C192.79 80.285 192.71 80.525 192.63 80.755C192.58 80.875 192.51 80.995 192.48 81.125C192.38 81.495 192.33 81.875 192.33 82.265V139.625L148.62 164.795V52.575C148.62 52.185 148.57 51.805 148.47 51.435C148.44 51.305 148.36 51.195 148.32 51.065C148.23 50.835 148.16 50.595 148.04 50.385C147.96 50.245 147.84 50.125 147.74 49.995C147.61 49.825 147.5 49.635 147.35 49.485C147.22 49.355 147.06 49.265 146.92 49.155C146.76 49.025 146.62 48.885 146.44 48.785L93.99 18.585C92.64 17.805 90.98 17.805 89.63 18.585L37.18 48.785C37 48.885 36.86 49.035 36.7 49.155C36.56 49.265 36.4 49.355 36.27 49.485C36.12 49.635 36.01 49.825 35.88 49.995C35.78 50.125 35.66 50.245 35.58 50.385C35.46 50.595 35.38 50.835 35.3 51.065C35.25 51.185 35.18 51.305 35.15 51.435C35.05 51.805 35 52.185 35 52.575V232.235C35 233.795 35.84 235.245 37.19 236.025L142.1 296.425C142.33 296.555 142.58 296.635 142.82 296.725C142.93 296.765 143.04 296.835 143.16 296.865C143.53 296.965 143.9 297.015 144.28 297.015C144.66 297.015 145.03 296.965 145.4 296.865C145.5 296.835 145.59 296.775 145.69 296.745C145.95 296.655 146.21 296.565 146.45 296.435L251.36 236.035C252.72 235.255 253.55 233.815 253.55 232.245V174.885L303.81 145.945C305.17 145.165 306 143.725 306 142.155V82.265C305.95 81.875 305.89 81.495 305.8 81.125ZM144.2 227.205L100.57 202.515L146.39 176.135L196.66 147.195L240.33 172.335L208.29 190.625L144.2 227.205ZM244.75 114.995V164.795L226.39 154.225L201.03 139.625V89.825L219.39 100.395L244.75 114.995ZM249.12 57.105L292.81 82.265L249.12 107.425L205.43 82.265L249.12 57.105ZM114.49 184.425L96.13 194.995V85.305L121.49 70.705L139.85 60.135V169.815L114.49 184.425ZM91.76 27.425L135.45 52.585L91.76 77.745L48.07 52.585L91.76 27.425ZM43.67 60.135L62.03 70.705L87.39 85.305V202.545V202.555V202.565C87.39 202.735 87.44 202.895 87.46 203.055C87.49 203.265 87.49 203.485 87.55 203.695V203.705C87.6 203.875 87.69 204.035 87.76 204.195C87.84 204.375 87.89 204.575 87.99 204.745C87.99 204.745 87.99 204.755 88 204.755C88.09 204.905 88.22 205.035 88.33 205.175C88.45 205.335 88.55 205.495 88.69 205.635L88.7 205.645C88.82 205.765 88.98 205.855 89.12 205.965C89.28 206.085 89.42 206.225 89.59 206.325C89.6 206.325 89.6 206.325 89.61 206.335C89.62 206.335 89.62 206.345 89.63 206.345L139.87 234.775V285.065L43.67 229.705V60.135ZM244.75 229.705L148.58 285.075V234.775L219.8 194.115L244.75 179.875V229.705ZM297.2 139.625L253.49 164.795V114.995L278.85 100.395L297.21 89.825V139.625H297.2Z"/>
</svg>
</a>
</div>
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<router-link :to="{name:'posts.index'}" active-class="border-b-2 border-indigo-400" class="inline-flex items-center px-1 pt-1 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out">
Posts
</router-link>
<router-link :to="{name:'posts.create'}" active-class="border-b-2 border-indigo-400" class="inline-flex items-center px-1 pt-1 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out">
Create Post
</router-link>
</div>
</div>
<div class="flex items-center">
<div>
<div>Hi, {{user.name}}</div>
<div class="text-sm text-gray-500"> {{user.email}} </div>
</div>
</div>
<button #click="logout" type="button" class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:shadow-outline-gray transition ease-in-out duration-150 ml-4" :class="{ 'opacity-25': processing }" :disabled="processing">
Log out
</button>
</div>
</div>
</nav>
<!-- Page Heading -->
<header class="bg-white shadow">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{currentPageTitle}}
</h2>
</div>
</header>
<!-- Page Content -->
<main>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<router-view></router-view>
</div>
</div>
</div>
</div>
</main>
</div>
</template>
<script>
import { reactive } from '#vue/runtime-core'
export default {
props: {
getUser: {type: Function}
},
computed: {
currentPageTitle(){
return this.$route.meta.title;
}
},
mounted() {
this.getUser()
},
methods: {
}
}
</script>
App.js
require('./bootstrap');
import { createApp, onMounted } from 'vue'
//import App from './layouts/App'
import LaravelVuePagination from 'laravel-vue-pagination';
import VueSweetalert2 from 'vue-sweetalert2';
import 'sweetalert2/dist/sweetalert2.min.css';
import router from './routes/index'
const app = createApp({
})
app.use(router)
app.use(VueSweetalert2)
app.component('Pagination', LaravelVuePagination)
app.mount('#app')
you should vuex for this perpose.
getUser(){
axios.get('/api/user')
.then(response => {
this.$store.commit('storeUser',response)
this.router.push({name: 'posts.index'})
})
},
in store/index.js (create folder of store in root )
import { createApp } from 'vue'
import { createStore } from 'vuex'
// Create a new store instance.
const store = createStore({
state () {
return {
userData:{}
}
},
getters: {
userData(state){
return state.userData
}
},
mutations: {
storeUser (state,payload) {
state.userData= payload
}
}
})
export default store
in main.js
import store from 'store/index.js'
const app = createApp({ /* your root component */ })
// Install the store instance as a plugin
app.use(store)
in Authenticated.vue (or whereevet you want to use any value from store)
computed:{
userData(){
return this.$store.getters.userData // here you can get your
response object from api
which you call during login
}
}
Intro
Basically, I am building an e-commerce website. But I got stuck in this problem while saving the order details after I click payment button (although I had not used any payment gateway).
Problem
After selecting the items in cart , I fill the details in Checkout page and go to pay option to confirm order. In this checkout page I call addorder api which first do some checks(like whether item got out of stock or not) on the product in cart and then create new order.
After creating a new order in order DB (MOngo DB I am using), I goto order page like this with my orderId
res.redirect('/order/' + orderToAdd.orderId, 200);
After clicking the pay button shown in the image above, it redirects to http://localhost:3000/order/814407650978 giving 404 (Not Found).
Thus when I fetch the order details from the MyOrder.js using context.query.orderId, from getServerSideProps() , I am getting null.
export async function getServerSideProps(context) {
if (!mongoose.connections[0].readyState) {
await mongoose.connect(process.env.MONGO_URI);
}
let order = await Order.findById({orderId:context.query.orderId})
console.log("got = ", order);
return {
props: { order: JSON.parse(JSON.stringify(order)) }, // will be passed to the page component as props
};
}
You can see full code below
Code
This is a complete order DB entry, where I am only sending the products object to MyOrder.js
complete order = {
email: 'mohit6#test.com',
orderId: '869972292107',
paymentInfo: 'No payment info',
products: {
'wear-the-chess-king-M-Red': {
qty: 2,
price: 599,
name: 'King Chess Stylish Men’s t-Shirt',
size: 'M',
variant: 'Red'
}
},
address: 'A-22\nVinayak campus',
subtotal: 1198,
status: 'Paid',
_id: new ObjectId("626bd1bd01597b226fc76531"),
createdAt: 2022-04-29T11:53:33.046Z,
updatedAt: 2022-04-29T11:53:33.046Z,
__v: 0
}
Checkout.js
import React, { useState } from "react";
import { AiFillPlusCircle, AiFillMinusCircle } from "react-icons/ai";
import { MdOutlinePayment } from "react-icons/md";
import { useRouter } from "next/router";
import "react-toastify/dist/ReactToastify.css";
import { toast, ToastContainer } from "react-toastify";
// Added payment AiOutlineGateway, but due to no merchant key, it is creating invalid checksum => hence push to different branch in both local & remote
const Checkout = ({ cart, clearCart, subtotal, addToCart, removeFromCart }) => {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");
const [address, setAddress] = useState("");
const [pincode, setPincode] = useState("");
const [city, setCity] = useState("");
const [statemap, setStatemap] = useState("");
const [disable, setDisable] = useState(true);
const router = useRouter();
const handleChange = async (e) => {
if (e.target.name === "name") {
setName(e.target.value);
} else if (e.target.name === "email") {
setEmail(e.target.value);
} else if (e.target.name === "phone") {
setPhone(e.target.value);
} else if (e.target.name === "address") {
setAddress(e.target.value);
} else if (e.target.name === "pincode") {
setPincode(e.target.value);
if (e.target.value.length == 6) {
let pins = await fetch(`${process.env.NEXT_PUBLIC_HOST}/api/pincode`);
let pinjson = await pins.json();
//console.log(pinjson)
if (Object.keys(pinjson).includes(e.target.value)) {
setCity(pinjson[e.target.value][0]);
setStatemap(pinjson[e.target.value][1]);
} else {
setCity("");
setStatemap("");
}
} else {
setCity("");
setStatemap("");
}
}
if (name && email && phone && address && pincode) setDisable(false);
else setDisable(true);
};
const initiateOrder = async () => {
let oid = Math.floor(Math.random() * Date.now());
const data = {
cart: cart,
subtotal: subtotal,
oid: oid,
email: email,
name: name,
address: address,
pincode: pincode,
phone: phone,
};
//console.log(JSON.stringify(data));
let res = await fetch(`${process.env.NEXT_PUBLIC_HOST}/api/addorder`, {
method: "POST", // or 'PUT'
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
let response = await res.json();
//console.log("response from order- ", response);
if (response.success === true) {
localStorage.setItem("token", response.authToken);
toast.success("Order Added Successfully!", {
position: "bottom-center",
autoClose: 2000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
setTimeout(() => {
router.push("/order");
}, 2500);
} else {
if(response.error === "err1"){
toast.error("Total price of your cart have changed accidently", {
position: "bottom-center",
autoClose: 2000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
}
else if(response.error === "err2"){
toast.error("Some items in your cart went out of stock !", {
position: "bottom-center",
autoClose: 2000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
}
else if(response.error === "err5"){
toast.error("Prices of some of the items in your cart have changed !", {
position: "bottom-center",
autoClose: 2000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
}
else {
toast.error("Error in Adding Order !", {
position: "bottom-center",
autoClose: 2000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
}
}
};
return (
<>
<div className="container px-2 sm:m-auto">
<ToastContainer
position="bottom-center"
autoClose={2000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
/>
<h1 className="text-xl md:text-3xl text-center my-8 font-semibold">
Checkout
</h1>
{/* part 1 */}
<h2 className="text-xl my-4 font-semibold">1. Delivery Details</h2>
<div className="mx-auto flex">
<div className="px-2 w-1/2">
<div className="mb-4">
<label htmlFor="name" className="leading-7 text-sm text-gray-600">
Name
</label>
<input
type="name"
id="name"
name="name"
value={name}
onChange={handleChange}
className="w-full bg-white rounded border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
/>
</div>
</div>
<div className="px-2 w-1/2">
<div className=" mb-4">
<label
htmlFor="email"
className="leading-7 text-sm text-gray-600"
>
Email
</label>
<input
type="email"
id="email"
name="email"
value={email}
onChange={handleChange}
className="w-full bg-white rounded border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
/>
</div>
</div>
</div>
<div className="px-2 w-full">
<div className=" mb-4">
<label
htmlFor="address"
className="leading-7 text-sm text-gray-600"
>
Address
</label>
<textarea
id="address"
name="address"
value={address}
onChange={handleChange}
className="w-full bg-white rounded border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
/>
</div>
</div>
<div className="mx-auto flex">
<div className="px-2 w-1/2">
<div className="mb-4">
<label
htmlFor="phone"
className="leading-7 text-sm text-gray-600"
>
Phone
</label>
<input
type="text"
id="phone"
name="phone"
value={phone}
onChange={handleChange}
className="w-full bg-white rounded border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
/>
</div>
</div>
{/* city */}
<div className="px-2 w-1/2">
<div className=" mb-4">
<label
htmlFor="pincode"
className="leading-7 text-sm text-gray-600"
>
Pincode
</label>
<input
type="text"
id="pincode"
name="pincode"
value={pincode}
onChange={handleChange}
className="w-full bg-white rounded border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
/>
</div>
</div>
</div>
<div className="mx-auto flex">
<div className="px-2 w-1/2">
<div className="mb-4">
<label
htmlFor="state"
className="leading-7 text-sm text-gray-600"
>
State
</label>
<input
type="text"
id="state"
name="state"
value={statemap}
className="w-full bg-white rounded border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
onChange={handleChange}
/>
</div>
</div>
<div className="px-2 w-1/2">
<div className=" mb-4">
<label htmlFor="city" className="leading-7 text-sm text-gray-600">
City
</label>
<input
type="text"
id="city"
name="city"
value={city}
className="w-full bg-white rounded border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
onChange={handleChange}
/>
</div>
</div>
</div>
{/* part 2 */}
<h2 className="text-xl my-4 font-semibold">2. Review Cart Items</h2>
<div className="z-10 sideCart p-6 mx-2 my-4 bg-blue-100 border-[1px] border-blue-800 rounded">
<ol className="list-decimal font-semibold">
{Object.keys(cart).length === 0 && (
<div className="mt-5 text-center text-xl font-extralight">
Your Cart is Empty :(
</div>
)}
{
//k is cart {k: {name, price,qty,size,variant} }
Object.keys(cart).map((k) => {
return (
<li key={k}>
<div className="flex item my-5 items-center justify-between">
<div className=" font-semibold">
{cart[k].name} [{cart[k].variant} - {cart[k].size}]
</div>
<div className="w-1/3 font-semibold flex items-center justify-center">
<AiFillPlusCircle
onClick={() =>
addToCart(
k,
1,
cart[k].price,
cart[k].name,
cart[k].size,
cart[k].variant
)
}
className="text-blue-700 text-xl cursor-pointer"
/>
<span className="mx-2 text-sm">{cart[k].qty}</span>
<AiFillMinusCircle
onClick={() =>
removeFromCart(
k,
1,
cart[k].price,
cart[k].name,
cart[k].size,
cart[k].variant
)
}
className="text-blue-700 text-xl cursor-pointer"
/>
</div>
</div>
</li>
);
})
}
</ol>
<span className="subtotal text-xl font-extrabold">
Subtotal : ₹ {subtotal} /-
</span>
</div>
<button
disabled={disable}
onClick={initiateOrder}
className="disabled:bg-blue-300 flex text-white bg-blue-500 border-0 py-2 px-3 focus:outline-none hover:bg-blue-600 rounded text-base mx-2 my-4"
>
<MdOutlinePayment className="m-1" />
Pay ₹ {subtotal}
</button>
</div>
</>
);
};
export default Checkout;
/api/addorder.js
import connectDB from "../../middleware/mongoose";
import Order from "../../models/Order";
import Product from "../../models/Product";
const handler = async (req, res) => {
if (req.method === "POST") {
let product,
sumtotal = 0;
for (let item in req.body.cart) {
//got key(slug) as item
console.log(item);
sumtotal += req.body.cart[item].qty * req.body.cart[item].price;
product = await Product.findOne({ slug: item });
//TODO1: check if cart is tampered or not
if (product.price !== req.body.cart[item].price) {
res.status(500).json({ success: false, error: "err1" });
//console.log("ResT - ",res.json)
return;
}
//TODO2: check if cart items are out of stock
if (product.availableQty < req.body.cart[item].qty) {
res.status(500).json({ success: false, error: "err2" });
return;
}
}
//console.log("sumtotal = ",sumtotal," and subtotal = ",req.body.subtotal)
if (sumtotal != req.body.subtotal) {
res.status(500).json({ success: false, error: "err5" });
//console.log("ResP - ",res)
return;
}
//TODO3: check if details are valid or not
let orderToAdd = new Order({
email: req.body.email,
orderId: req.body.oid,
address: req.body.address,
subtotal: req.body.subtotal,
products: req.body.cart,
status: "Paid",
});
await orderToAdd.save();
let ordered_products = orderToAdd.products;
console.log("Orederd pro = ", ordered_products);
console.log("complete order = ", orderToAdd)
for (let slug in ordered_products) {
await Product.findOneAndUpdate(
{ slug: slug },
{ $inc: { availableQty: -ordered_products[slug].qty } }
);
}
res.redirect('/order/' + orderToAdd.orderId, 200)
} else {
// Handle any other HTTP method
res.status(400).json({ success: false });
}
};
export default connectDB(handler);
MyOrder.js
import React from "react";
import mongoose from "mongoose";
import Order from "../models/Order";
//TODO: To change the orderdetail fecthing resource from cart,subtotal to orders DB (user specific orders)
const MyOrder = ({ order }) => {
console.log("order = ",order)
const products = order.products;
console.log(order,products);
return (
<div>
<section className="text-gray-600 body-font overflow-hidden">
<div className="container px-5 py-24 mx-auto">
<div className="lg:w-4/5 mx-auto flex flex-wrap">
<div className="lg:w-1/2 w-full lg:pr-10 lg:py-6 mb-6 lg:mb-0">
<h2 className="text-sm title-font text-gray-500 tracking-widest">
CHESSWEAR.COM
</h2>
<h1 className="text-gray-900 text-3xl title-font font-medium mb-4">
Order Id: 2242434535
</h1>
<p className="leading-relaxed mb-4 text-green-700 rounded-3xl bg-green-100 hover:bg-green-100/70 p-3 border-[1px] border-green-700 inline-flex items-center justify-center ">
Your Order has been successfully placed !
</p>
<div className="flex mb-4 justify-evenly">
<a className="flex-grow w-12 py-2 text-lg px-1">Description</a>
<a className="flex-grow py-2 text-lg px-1">Quantity</a>
<a className="flex-grow py-2 text-lg px-1">Price</a>
</div>
{Object.keys(cart).map((key) => {
return (
<div key={key} className="flex border-t border-gray-200 py-2">
<span className="text-gray-500 w-28 ">
{cart[key].name} ({cart[key].size} - {cart[key].variant})
</span>
<span className="m-auto text-gray-900">
{cart[key].qty}
</span>
<span className="mr-auto mt-auto mb-auto text-gray-900">
₹ {cart[key].price}
</span>
</div>
);
})}
<div className="flex py-2 md:py-4">
<span className="title-font font-medium text-2xl text-gray-900">
Subtotal : <span className="ml-4 text-red-900 font-bold text-3xl leading-tight">₹ {subtotal} /-</span>
</span>
<button className="flex ml-auto text-white bg-blue-500 border-0 py-2 px-6 focus:outline-none hover:bg-blue-600 rounded">
Track Order
</button>
</div>
</div>
<img
alt="ecommerce"
className="lg:w-1/2 w-full lg:h-auto h-64 object-cover object-center rounded"
src="https://dummyimage.com/400x400"
/>
</div>
</div>
</section>
</div>
);
};
export async function getServerSideProps(context) {
if (!mongoose.connections[0].readyState) {
await mongoose.connect(process.env.MONGO_URI);
}
let order = await Order.findById({orderId:context.query.orderId})
console.log("got = ", order);
return {
props: { order: JSON.parse(JSON.stringify(order)) }, // will be passed to the page component as props
};
}
export default MyOrder;
File Structure
Please help me???
I tried to add auto scrolling div for lyric when user is click play scroll button with speed. There is speed button to increase or decrease speed of scrolling.
So far I only achieved that it is scrolling to bottom.
I use useRef and trackRef.current.scrollIntoView. window.scrollto is not working.
May I know how to scroll div with speed until I can see the all hidden part of the bottom.
here is my code
import React, { useState, useEffect, useRef } from "react";
import { useParams } from "react-router-dom";
import { useQuery } from "#apollo/client";
import { getTrack, getTrackVariable } from "../../gql/track";
import ChordSheetJS from "chordsheetjs";
import { PlayIcon, PauseIcon } from "../../assets/icons/svg_icons";
import { SettingIcon } from "../../assets/icons/svg_icons";
const TrackPage = () => {
const { trackId } = useParams();
const [track, setTrack] = useState();
const [collapse, setCollapse] = useState(true);
const [play, setPlay] = useState(false);
const [speed, setSpeed] = useState(1);
const { loading, error, data } = useQuery(getTrack, {
variables: getTrackVariable(trackId),
});
const trackRef = useRef();
useEffect(() => {
if (!loading && !error) {
setTrack(data?.track);
}
}, [loading, error, data]);
useEffect(() => {
if (play) {
trackRef.current.scrollIntoView({
behavior: "smooth",
block: "end",
inline: "start",
});
}
}, [play]);
const getChordSheet = (value) => {
const parser = new ChordSheetJS.ChordProParser();
const song = parser.parse(value);
const formatter = new ChordSheetJS.HtmlTableFormatter();
const chordSheet = formatter.format(song);
return chordSheet;
};
const handleError = (e) => {
e.target.onerror = null;
e.target.src = Monk;
};
const handleMenuCollapse = (e) => {
e.preventDefault();
setCollapse(!collapse);
};
const handleSpeedUp = () => {
setSpeed(speed + 1);
};
const handleSpeedDown = () => {
setSpeed(speed - 1);
};
const handleScroll = (e) => {
e.preventDefault();
setPlay(!play);
};
return (
<>
<div id="setting">
{/** the big div */}
<div
className={` w-36 h-56 bg-primary absolute top-[calc((100vh-384px)/2)] ${
collapse ? "hidden" : "right-0"
} " bg-primary rounded-b-lg items-center justify-center`}
>
<div>
<div className="items-center justify-center mt-5">
<div className="flex text-xs items-center justify-center ">
<span className=" text-sm text-white">Scroll</span>
</div>
<div className="flex text-xs pt-0 mt-0 items-center justify-center ">
<button
className="px-2 btn-sm flex w-20 items-center bg-transparent hover:bg-accent border text-white font-semibold hover:text-white border-white hover:border-transparent rounded "
onClick={handleScroll}
>
{play ? (
<PauseIcon className="text-white mr-2" />
) : (
<PlayIcon className="text-white mr-2" />
)}
{play ? <span>Pause</span> : <span>Play</span>}
</button>
</div>
<div className="flex text-xs items-center justify-center mt-2">
<button
className="w-auto bg-transparent mr-2 hover:bg-accent text-white font-semibold hover:text-white py-1 px-2 border border-white hover:border-transparent rounded"
onClick={handleSpeedDown}
>
-1
</button>
<button
className="w-auto bg-transparent ml-2 hover:bg-accent text-white font-semibold hover:text-white py-1 px-2 border border-white hover:border-transparent rounded"
onClick={handleSpeedUp}
>
+1
</button>
</div>
</div>
</div>
</div>
{/** the icon div */}
<div
className={`flex w-12 absolute top-[calc((100vh-384px)/2)] h-12 bg-primary
${collapse ? "animate-pulse right-0" : "right-36"}
cursor-pointer bg-primary rounded-l-lg items-center justify-center`}
onClick={handleMenuCollapse}
>
{/* <div className="w-5 h-5 bg-white rounded-full " /> */}
<SettingIcon />
</div>
</div>
<div ref={trackRef}>
<div className="flex flex-col w-full py-1 my-1 items-center bg-gray-50">
<div className="relative my-6 mx-auto md:min-w-[60%] max-h-full">
{track ? (
<div className="w-full">
<pre
className="px-5 textarea"
dangerouslySetInnerHTML={{
__html: getChordSheet(track.lyric),
}}
/>
</div>
) : (
<div></div>
)}
</div>
</div>
</div>
</>
);
};
export default TrackPage;
window.scrollTo is only working with html body. document.getElementById is only working overflow div.
useEffect(() => {
if (play) {
const onScrollStep = () => {
var e = document.getElementById("content");
if (e.scrollHeight - e.scrollTop === e.clientHeight) {
clearInterval(intervalId.current);
setPlay(!play);
return;
}
e.scroll(0, e.scrollTop + speed);
};
intervalId.current = setInterval(onScrollStep, delayInMs);
} else {
clearInterval(intervalId.current);
}
},[play, speed])
I have 3 button. I want to add red background when one of them is clicked. By default the first button is active. When I click on the second or the third one first button should lose it's background styles red to gray. Also I want to add slide in animation. ex, If I'm currently on first button and click on the 2nd button, background should animate left to right.
My current code:
import React from "react";
const TopOptions = () => {
return (
<>
<div className="flex flex-col border-b shadow-lg">
<div className="flex flex-col px-4">
<p className="text-xs pl-6 pt-4">PUNE</p>
<div className="flex flex-row items-center">
<select className="pl-2">
<option value="Kothrud Outlet">Kothrud Outlet</option>
</select>
</div>
</div>
<div className=" bg-gray-100 my-5 text-center flex flex-row items-center justify-evenly text-sm rounded-md mx-4">
<button className="w-full py-3 font-bold ">DELIVERY</button>
<button className="w-full py-3 font-bold text-gray-600">
TAKEAWAY
</button>
<button className="w-full py-3 font-bold text-gray-600">
DINE IN
</button>
</div>
</div>
</>
);
};
export default TopOptions;
You can use state for each individual button's class. After clicking each button you have to change that button's state as per following:
import React, {useState} from "react";
const TopOptions = () => {
const [btnClass, setBtnClass] = useState({"delivery": "btn-danger", "takeaway": "", "dinein": ""});
const handleClick = (e) => {
// after handling the click event add following code
let updatedBtnClass = {"delivery": "", "takeaway": "", "dinein": ""};
updatedBtnClass[e.target.name] = "btn-danger"; // for red background class
setBtnClass(updatedBtnClass);
}
return (
<>
<div className="flex flex-col border-b shadow-lg">
<div className="flex flex-col px-4">
<p className="text-xs pl-6 pt-4">PUNE</p>
<div className="flex flex-row items-center">
<select className="pl-2">
<option value="Kothrud Outlet">Kothrud Outlet</option>
</select>
</div>
</div>
<div
className=" bg-gray-100 my-5 text-center flex flex-row items-center justify-evenly text-sm rounded-md mx-4">
<button name={"delivery"} className={"w-full py-3 font-bold " + btnClass.delivery} onClick={handleClick}>
DELIVERY
</button>
<button name={"takeaway"} className={"w-full py-3 font-bold " + btnClass.takeaway} onClick={handleClick}>
TAKEAWAY
</button>
<button name={"dinein"} className={"w-full py-3 font-bold " + btnClass.dinein} onClick={handleClick}>
DINE IN
</button>
</div>
</div>
</>
);
};
export default TopOptions;
I would approach this by giving each button a number, initialise a state variable with the number of the first button and change this state on each btn press:
import React, {useState} from "react";
export const TopOptions = () => {
const [clickedButton, setClickedButton] = useState(0);
const [yPos, setYPos] = useState(0);
const buttons = ["DELIVERY", "TAKEAWAY", "DINE IN"];
const speed = 10;
let direction = 0;
const updateState = () => {
setYPos(yPos + (speed * direction));
}
const clickHandler = (index) => {
direction = index > clickedButton ? 1 : -1;
let duration = Math.abs(clickedButton - index) * 1000; // 1sec
setClickedButton(index);
const animRef = requestAnimationFrame(updateState);
setTimeout(() => {
cancelAnimationFrame(animRef);
}, duration);
}
return (
<>
<div className="flex flex-col border-b shadow-lg">
<div className="flex flex-col px-4">
<p className="text-xs pl-6 pt-4">PUNE</p>
<div className="flex flex-row items-center">
<select className="pl-2">
<option value="Kothrud Outlet">Kothrud Outlet</option>
</select>
</div>
</div>
<div
className=" bg-gray-100 my-5 text-center flex flex-row items-center justify-evenly text-sm rounded-md mx-4">
{buttons.map((btn, index) => (
<button className={"w-full py-3 font-bold " + (clickedButton === index ? "" : "text-gray-600")}
onClick={() => {
clickHandler(index);
}} key={"btn" + index}
>{btn}</button>
))}
</div>
<image src={"yourbackgroundimage.jpg"}
style={"position:fixed; width: 100vw; height: 100vh; top: 0; left: " + yPos}/>
{
/*
This is not a god example, just to show how to use yPos now.
assuming left btn is curently active:
if clicked on middle button it lets the image slide from left to right for one second
if clicked on right button it lets the image slide from left to right for two second
*/
}
</div>
</>
);
};
After SignIn, my authState becomes true, which is expected. and then It redirected to the home page. But after that If I refresh the page, my state goes back to initial value. what could be the reason of this?
Login Component:
import React, { useState, useEffect } from 'react';
import { toast } from 'react-toastify';
import axios from 'axios';
import { Redirect } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { setCurrentUser } from 'src/store/reducer/authReducer';
import jwtDecode from 'jwt-decode';
import { authenticate, isAuth } from '../helpers/auth';
import Protection from '../assets/Protection.svg';
import useInputState from '../hooks/useInputState';
const Login = (props) => {
const auth = useSelector((state) => state.auth);
const [submitted, setSubmitted] = useState(false);
const [btnText, setBtnText] = useState('Sign In');
const dispatch = useDispatch();
const { location } = props;
const initialValue = { name: '', email: '', password: '' };
const [state, onChangeHandler, reset] = useInputState(initialValue);
const { email, password } = state;
// submit data to backend
const handleSubmit = async (e) => {
e.preventDefault();
try {
const res = await axios.post(`${process.env.API_URL}/api/signin`, {
email,
password,
});
reset();
setSubmitted(true);
setBtnText('Submitted');
const { token: jwtToken } = res.data;
localStorage.setItem('jwtToken', jwtToken);
const decoded = jwtDecode(jwtToken);
dispatch(setCurrentUser(decoded));
} catch (err) {
const { errors } = err.response.data;
for (const error of errors) {
const msg = Object.values(error).toString();
toast.error(msg);
}
}
};
useEffect(() => {
if (location.message) {
toast.success(location.message);
}
}, [location.message]);
if (submitted && auth.isAuthenticated) {
return <Redirect to={{ pathname: '/', message: 'You are signed in' }} />;
}
return (
<div className="min-h-screen bg-gray-100 text-gray-900 flex justify-center">
<div className="max-w-screen-xl m-0 sm:m-20 bg-white shadow sm:rounded-lg flex justify-center flex-1">
<div className="lg:w-1/2 xl:w-5/12 p-6 sm:p-12">
<div className="mt-12 flex flex-col items-center">
<h1 className="text-2xl xl:text-3xl font-extrabold">
Sign In for MernAuth
</h1>
<form
className="w-full flex-1 mt-8 text-indigo-500"
onSubmit={handleSubmit}
>
<div className="mx-auto max-w-xs relative">
<input
className="w-full px-8 py-4 rounded-lg font-medium bg-gray-100 border-gray-200 placeholder-gray-500 text-sm focus:outline-none focus:border-gray-400 focus:bg-white mt-5"
type="email"
name="email"
placeholder="Email"
onChange={onChangeHandler}
value={email}
/>
<input
className="w-full px-8 py-4 rounded-lg font-medium bg-gray-100 border-gray-200 placeholder-gray-500 text-sm focus:outline-none focus:border-gray-400 focus:bg-white mt-5"
type="password"
name="password"
placeholder="Password"
onChange={onChangeHandler}
value={password}
/>
<button
className="mt-5 tracking-wide font-semibold bg-indigo-500 text-gray-100 w-full py-4 rounded-lg hover:bg-indigo-700 transition-all duration-300 ease-in-out flex items-center justify-center focus:shadow-outline focus:outline-none border-none outline-none"
type="submit"
>
<i className="fas fa-user-plus fa 1x w-6 -ml-2" />
<span className="ml-3">{btnText}</span>
</button>
</div>
<div className="my-12 border-b text-center">
<div className="leading-node px-2 inline-block text-sm text-gray-600 tracking-wide font-medium bg-white transform tranlate-y-1/2">
Or sign in with email or social login
</div>
</div>
<div className="flex flex-col items-center">
<a
className="w-full max-w-xs font-bold shadow-sm rounded-lg py-3
bg-indigo-100 text-gray-800 flex items-center justify-center transition-all duration-300 ease-in-out focus:outline-none hover:shadow focus:shadow-sm focus:shadow-outline mt-5"
href="/login"
target="_self"
>
<i className="fas fa-sign-in-alt fa 1x w-6 -ml-2 text-indigo-500" />
<span className="ml-4">Sign Up</span>
</a>
</div>
</form>
</div>
</div>
<div className="flex-1 bg-indigo-100 text-center hidden lg:flex">
<div
className="m-12 xl:m-16 w-full bg-contain bg-center bg-no-repeat"
style={{ backgroundImage: `url(${Protection})` }}
/>
</div>
</div>
</div>
);
};
export default Login;
my home component
import React, { useEffect } from 'react';
import { toast } from 'react-toastify';
const App = (props) => {
const { location } = props;
useEffect(() => {
if (location.message) {
toast.success(location.message);
}
}, [location.message]);
return <div>App</div>;
};
export default App;
I don't want the state to go back to its initial value on page refresh.
Your data is not persisted. Save the user token in the localStorage and when the app is initializing verify the localStorage and if there have value simply populate the store with this data.
Since you want to store user token, I would suggest that you use a cookie to store it, here is a link where you can find how to create and use a cookie.