refactor: improvements in UI and book consultion option

- Login is not responsive
- Add country field in book consultant any country
- correct form submit of book consultant in mobile view
- updatr email in privacy page
- make home page slider responsive
- redirect login whentokenexpired
This commit is contained in:
2025-05-09 20:09:27 +05:30
parent 8050065359
commit 7e33e61d65
11 changed files with 405 additions and 141 deletions

View File

@@ -0,0 +1,25 @@
// Create a new component for currency conversion explanation
import React from 'react';
import { Info } from 'lucide-react';
import { useCurrency } from '@/app/contexts/currencyContext';
const CurrencyTooltip = () => {
const { selectedCurrency, SUPPORTED_CURRENCIES } = useCurrency();
// Only show for non-INR currencies
if (selectedCurrency === 'INR') return null;
return (
<div className="relative inline-block group ml-2">
<Info className="w-4 h-4 text-gray-400 cursor-help" />
<div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 w-64 p-2 bg-white border rounded shadow-lg
text-xs text-gray-700 opacity-0 group-hover:opacity-100 transition-opacity duration-200 z-50">
Prices are converted from Indian Rupees () to {SUPPORTED_CURRENCIES[selectedCurrency]?.name}
({SUPPORTED_CURRENCIES[selectedCurrency]?.symbol}) based on current exchange rates.
Actual charges may vary at checkout.
</div>
</div>
);
};
export default CurrencyTooltip;

View File

@@ -0,0 +1,57 @@
"use client";
import React from 'react';
import { useCurrency } from '@/app/contexts/currencyContext';
const Price = ({ amount, className = "" }) => {
const { selectedCurrency, exchangeRates, isLoading, error, SUPPORTED_CURRENCIES } = useCurrency();
const convertPrice = (priceInINR) => {
if (!priceInINR || isNaN(priceInINR)) return 0;
if (selectedCurrency === 'INR') return priceInINR;
if (!exchangeRates || isLoading) {
return priceInINR;
}
const rate = exchangeRates[selectedCurrency];
if (rate) {
const convertedPrice = priceInINR * rate;
return convertedPrice.toFixed(2);
}
return priceInINR;
};
const formatPrice = (price) => {
const symbol = SUPPORTED_CURRENCIES[selectedCurrency]?.symbol || '₹';
const convertedPrice = convertPrice(price);
if (error && selectedCurrency !== 'INR') {
return `${symbol} ${convertedPrice} (approx)`;
}
switch (selectedCurrency) {
case 'MYR':
return `${symbol} ${convertedPrice}`;
case 'NPR':
return `${symbol} ${convertedPrice}`;
case 'INR':
default:
return `${symbol} ${price}`;
}
};
if (isLoading && selectedCurrency !== 'INR') {
return <span className={className}>Loading price...</span>;
}
return (
<span className={className}>
{formatPrice(amount)}
</span>
);
};
export default Price;

View File

@@ -1,43 +1,88 @@
import React, { useState } from 'react';
import { ChevronDown } from 'lucide-react';
import React, { useState, useEffect } from 'react';
import { ChevronDown, AlertCircle } from 'lucide-react';
const CurrencySelect = ({ selectedCurrency, setSelectedCurrency, SUPPORTED_CURRENCIES }) => {
const CurrencySelect = ({ selectedCurrency, setSelectedCurrency, SUPPORTED_CURRENCIES, error }) => {
const [isOpen, setIsOpen] = useState(false);
const [isMobile, setIsMobile] = useState(false);
const [selectedCurrencyName, setSelectedCurrencyName] = useState('');
useEffect(() => {
const savedCurrency = localStorage.getItem('selectedCurrency');
if (savedCurrency && SUPPORTED_CURRENCIES[savedCurrency]) {
setSelectedCurrency(savedCurrency);
}
setSelectedCurrencyName(SUPPORTED_CURRENCIES[selectedCurrency]?.country || '');
const checkMobile = () => {
setIsMobile(window.innerWidth < 768);
};
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, [SUPPORTED_CURRENCIES, setSelectedCurrency]);
useEffect(() => {
setSelectedCurrencyName(SUPPORTED_CURRENCIES[selectedCurrency]?.country || '');
}, [selectedCurrency, SUPPORTED_CURRENCIES]);
const handleCurrencyChange = (code) => {
localStorage.setItem('selectedCurrency', code);
setSelectedCurrency(code);
setSelectedCurrencyName(SUPPORTED_CURRENCIES[code]?.country || '');
setIsOpen(false);
};
return (
<div className="relative w-32">
<div className="relative w-auto md:w-32">
<button
onClick={() => setIsOpen(!isOpen)}
className="w-full px-4 py-2.5 bg-white border border-gray-200 rounded-lg shadow-sm
flex items-center justify-between text-gray-700 text-sm font-medium
className={`w-full px-2 md:px-4 py-2 md:py-2.5 bg-white border border-gray-200 rounded-lg shadow-sm
flex items-center justify-between text-sm font-medium
hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500
transition-all duration-200 group"
transition-all duration-200 group
${error && selectedCurrency !== 'INR' ? 'text-amber-600' : 'text-gray-700'}`}
>
{SUPPORTED_CURRENCIES[selectedCurrency]?.country}
{isMobile ? (
<span className="text-base font-medium">
{SUPPORTED_CURRENCIES[selectedCurrency]?.symbol || ''}
{error && selectedCurrency !== 'INR' && (
<AlertCircle className="inline ml-1 w-3 h-3" />
)}
</span>
) : (
<>
{SUPPORTED_CURRENCIES[selectedCurrency]?.country}
{error && selectedCurrency !== 'INR' && (
<AlertCircle className="inline ml-1 w-3 h-3" />
)}
</>
)}
<ChevronDown
className={`w-4 h-4 text-gray-400 transition-transform duration-200
className={`w-4 h-4 ml-1 text-gray-400 transition-transform duration-200
${isOpen ? 'rotate-180' : ''} group-hover:text-gray-600`}
/>
</button>
{isOpen && (
<div
className="absolute w-full mt-2 bg-white border border-gray-200 rounded-lg shadow-lg
className="absolute right-0 w-48 mt-2 bg-white border border-gray-200 rounded-lg shadow-lg
overflow-hidden z-50 animate-in fade-in slide-in-from-top-2 duration-200"
>
<div className="max-h-60 overflow-auto scrollbar-thin scrollbar-thumb-gray-200 hover:scrollbar-thumb-gray-300">
{Object.entries(SUPPORTED_CURRENCIES).map(([code, { country }]) => (
{Object.entries(SUPPORTED_CURRENCIES).map(([code, { country, symbol }]) => (
<button
key={code}
onClick={() => {
setSelectedCurrency(code);
setIsOpen(false);
}}
onClick={() => handleCurrencyChange(code)}
className={`w-full px-4 py-2.5 text-left text-sm transition-colors duration-150
hover:bg-gray-50 focus:outline-none focus:bg-gray-50
${selectedCurrency === code ? 'bg-blue-50 text-blue-600 font-medium' : 'text-gray-700'}
${selectedCurrency === code ? 'hover:bg-blue-50' : 'hover:bg-gray-50'}`}
>
<span className="mr-2">{symbol}</span>
{country}
</button>
))}
@@ -47,5 +92,4 @@ const CurrencySelect = ({ selectedCurrency, setSelectedCurrency, SUPPORTED_CURRE
</div>
);
};
export default CurrencySelect;

View File

@@ -18,30 +18,42 @@ const Navbar = () => {
const { category } = useContext(ProductContext);
const { token } = useContext(MainContext);
const [cartItemCount, setCartItemCount] = useState(0);
const { selectedCurrency, setSelectedCurrency, SUPPORTED_CURRENCIES } =
const { selectedCurrency, setSelectedCurrency, SUPPORTED_CURRENCIES, error } =
useCurrency();
const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen);
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 60);
const isMobile = window.innerWidth < 768;
setIsScrolled(isMobile || window.scrollY > 60);
};
handleScroll();
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
window.addEventListener("resize", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
window.removeEventListener("resize", handleScroll);
};
}, []);
const getCart = async () => {
try {
const response = await authAxios.get("/orders/cart/");
const cartData = response.data;
console.log(cartData)
console.log(cartData.length)
console.log(cartData);
console.log(cartData.length);
// Calculate total items in cart
if (cartData && cartData.length > 0 && cartData[0].items) {
const totalItems = cartData[0].items.reduce((sum, item) => sum + item.quantity, 0);
const totalItems = cartData[0].items.reduce(
(sum, item) => sum + item.quantity,
0
);
setCartItemCount(totalItems);
} else {
setCartItemCount(0);
@@ -66,10 +78,10 @@ const Navbar = () => {
if (token) {
// Initial fetch
getCart();
// Set up interval to check for updates every 3 seconds
const intervalId = setInterval(getCart, 3000);
// Clean up interval on component unmount
return () => clearInterval(intervalId);
}
@@ -91,10 +103,9 @@ const Navbar = () => {
return (
<>
<header className="fixed top-0 left-0 right-0 z-50 bg-white shadow-md">
<div className="container mx-auto flex justify-between items-center p-4">
<div className="flex items-center space-x-4">
<div className="flex items-center md:space-x-4">
<button onClick={toggleSidebar} className="z-50">
{isSidebarOpen ? (
<IoMdClose size={28} />
@@ -106,9 +117,9 @@ const Navbar = () => {
<Image
src="/logo1.jpg"
alt="Logo"
width={56}
height={56}
className="object-contain"
width={60}
height={60}
className="!max-w-[60px]"
/>
</Link>
</div>
@@ -117,9 +128,9 @@ const Navbar = () => {
<SearchComponent isScrolled={isScrolled} />
<Link href="/pages/shopping-cart" className="relative">
<ShoppingBag size={20} className="cursor-pointer" />
<span className="absolute -top-2 -right-2 bg-red-600 font-bold text-white text-xs rounded-full h-5 w-5 flex items-center justify-center">
{cartItemCount}
</span>
<span className="absolute -top-2 -right-2 bg-red-600 font-bold text-white text-xs rounded-full h-5 w-5 flex items-center justify-center">
{cartItemCount}
</span>
</Link>
<div className="cursor-pointer">
{token ? (
@@ -138,6 +149,7 @@ const Navbar = () => {
selectedCurrency={selectedCurrency}
setSelectedCurrency={setSelectedCurrency}
SUPPORTED_CURRENCIES={SUPPORTED_CURRENCIES}
error={error}
/>
</div>
</div>
@@ -169,65 +181,68 @@ const Navbar = () => {
<div className="fixed inset-0 bg-black bg-opacity-50 z-40">
<div className="fixed top-0 left-0 w-full h-full bg-white shadow-lg z-50 pt-32">
<div className="p-4 h-full w-full md:w-1/2">
<div style={{ justifyContent: "start" }} className="flex md:items-start md:!justify-between h-full flex-col flex-col-reverse md:flex-row" >
<div className="mb-6 border-t md:border-0">
<h2 className="text-lg font-semibold mb-4">Quick Links</h2>
<ul className="space-y-2">
<li>
<Link
href="/products/premium-rudraksha-consultation-astrology"
onClick={toggleSidebar}
className="text-sm font-medium hover:text-[#AC8C6B] transition-colors"
>
Get a Consultation
</Link>
</li>
<li>
<Link
href="/pages/certification-and-guarantee"
onClick={toggleSidebar}
className="text-sm font-medium hover:text-[#AC8C6B] transition-colors"
>
Certification and Guarantee
</Link>
</li>
<li>
<Link
href="/pages/contact-us"
onClick={toggleSidebar}
className="text-sm font-medium hover:text-[#AC8C6B] transition-colors"
>
Contact Us
</Link>
</li>
<li>
<Link
href="/blogs/blog"
onClick={toggleSidebar}
className="text-sm font-medium hover:text-[#AC8C6B] transition-colors"
>
Blogs
</Link>
</li>
</ul>
</div>
<div className="mb-6 md:mb-0">
<h2 className="text-lg font-semibold mb-4">Categories</h2>
<ul className="space-y-2">
{categoryItems.map((item) => (
<li key={item.label}>
<div
style={{ justifyContent: "start" }}
className="flex md:items-start md:!justify-between h-full flex-col flex-col-reverse md:flex-row"
>
<div className="mb-6 border-t md:border-0">
<h2 className="text-lg font-semibold mb-4">Quick Links</h2>
<ul className="space-y-2">
<li>
<Link
href={item.url}
href="/products/premium-rudraksha-consultation-astrology"
onClick={toggleSidebar}
className="text-sm font-medium hover:text-[#AC8C6B] transition-colors"
>
{item.label}
Get a Consultation
</Link>
</li>
))}
</ul>
</div>
<li>
<Link
href="/pages/certification-and-guarantee"
onClick={toggleSidebar}
className="text-sm font-medium hover:text-[#AC8C6B] transition-colors"
>
Certification and Guarantee
</Link>
</li>
<li>
<Link
href="/pages/contact-us"
onClick={toggleSidebar}
className="text-sm font-medium hover:text-[#AC8C6B] transition-colors"
>
Contact Us
</Link>
</li>
<li>
<Link
href="/blogs/blog"
onClick={toggleSidebar}
className="text-sm font-medium hover:text-[#AC8C6B] transition-colors"
>
Blogs
</Link>
</li>
</ul>
</div>
<div className="mb-6 md:mb-0">
<h2 className="text-lg font-semibold mb-4">Categories</h2>
<ul className="space-y-2">
{categoryItems.map((item) => (
<li key={item.label}>
<Link
href={item.url}
onClick={toggleSidebar}
className="text-sm font-medium hover:text-[#AC8C6B] transition-colors"
>
{item.label}
</Link>
</li>
))}
</ul>
</div>
</div>
</div>
</div>

View File

@@ -67,7 +67,7 @@ const Hero = ({ data }) => {
];
return (
<div className="relative w-full h-[45vh] bg-background">
<div className="relative w-full h-[45vh] md:h-[80vh] bg-background">
<Carousel
plugins={[plugin.current]}
className="w-full h-full"
@@ -77,7 +77,7 @@ const Hero = ({ data }) => {
<CarouselContent>
{heroData.map((item, index) => (
<CarouselItem key={index}>
<div className="relative w-full h-[45vh] overflow-hidden">
<div className="relative w-full h-[45vh] md:h-[80vh] overflow-hidden">
<Image
src={item.src || "/placeholder.svg"}
alt={`Slide ${index + 1}`}

View File

@@ -35,7 +35,7 @@ const PaymentComponent = ({ amount, onSuccess }) => {
const response = await axios.get("https://apilayer.net/api/live", {
params: {
access_key: "ytguhijok",
access_key: "9bcb30907dee1cda9866f7b49f0f8def",
currencies: "USD",
source: "INR",
format: 1,

View File

@@ -1,6 +1,6 @@
"use client";
import React, { useState } from "react";
import { ChevronRight, Gem } from "lucide-react";
import React, { useState, useEffect, useRef } from "react";
import { ChevronRight, Gem, Search, ChevronDown } from "lucide-react";
import authAxios from "@/utils/axios";
import Image from "next/image";
@@ -13,14 +13,62 @@ const PremiumBanner = ({ data }) => {
header_quote3,
header_quote4,
} = data || {};
const [formData, setFormData] = useState({
first_name: "",
last_name: "",
email: "",
phone_number: "",
country: "",
});
const [message, setMessage] = useState("");
const [countries, setCountries] = useState([]);
const [filteredCountries, setFilteredCountries] = useState([]);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
const dropdownRef = useRef(null);
useEffect(() => {
const fetchCountries = async () => {
try {
const response = await fetch("https://countriesnow.space/api/v0.1/countries/");
const data = await response.json();
if (!data.error && data.data) {
setCountries(data.data);
setFilteredCountries(data.data);
}
} catch (error) {
console.error("Error fetching countries:", error);
}
};
fetchCountries();
}, []);
useEffect(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setIsDropdownOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
useEffect(() => {
if (searchTerm) {
const filtered = countries.filter(country =>
country.country.toLowerCase().includes(searchTerm.toLowerCase())
);
setFilteredCountries(filtered);
} else {
setFilteredCountries(countries);
}
}, [searchTerm, countries]);
const handleInputChange = (e) => {
const { name, value } = e.target;
@@ -30,8 +78,24 @@ const PremiumBanner = ({ data }) => {
}));
};
const handleCountrySearch = (e) => {
setSearchTerm(e.target.value);
};
const selectCountry = (countryName) => {
setFormData(prev => ({ ...prev, country: countryName }));
setIsDropdownOpen(false);
setSearchTerm("");
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!formData.country) {
setMessage("Please select your country.");
return;
}
try {
const response = await authAxios.post(
"/consultation/booking/create/",
@@ -43,6 +107,7 @@ const PremiumBanner = ({ data }) => {
last_name: "",
email: "",
phone_number: "",
country: "",
});
setMessage("Consultation experts will contact you shortly.");
setTimeout(() => {
@@ -61,8 +126,7 @@ const PremiumBanner = ({ data }) => {
<div className="min-h-screen bg-white p-2 md:p-8 flex items-center justify-center">
<div className="max-w-6xl w-full bg-white shadow-lg rounded-xl overflow-hidden p-4 md:p-12">
<div className="grid md:grid-cols-2 gap-8">
{/* Left: Form Section */}
<div className="bg-[#EDE8E0] p-2 md:p-14 flex flex-col justify-center rounded-lg ">
<div className="bg-[#EDE8E0] p-2 md:p-14 flex flex-col justify-center rounded-lg">
<h2 className="text-4xl font-serif text-center text-[#AC8C6B]">
Book a Free Consultation
</h2>
@@ -107,9 +171,52 @@ const PremiumBanner = ({ data }) => {
className="w-full p-4 border rounded-md focus:ring-[#AC8C6B] focus:border-[#AC8C6B]"
required
/>
<div className="relative" ref={dropdownRef}>
<div
className="w-full p-4 border rounded-md flex justify-between items-center cursor-pointer bg-white"
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
>
<span className={formData.country ? "text-black" : "text-gray-400"}>
{formData.country || "Select Country"}
</span>
<ChevronDown className={`transition-transform duration-200 ${isDropdownOpen ? 'rotate-180' : ''}`} />
</div>
{isDropdownOpen && (
<div className="absolute z-10 w-full mt-1 bg-white border rounded-md shadow-lg max-h-60 overflow-y-auto">
<div className="sticky top-0 bg-white p-2 border-b">
<div className="relative">
<input
type="text"
placeholder="Search countries..."
value={searchTerm}
onChange={handleCountrySearch}
className="w-full p-2 pl-8 border rounded-md focus:ring-[#AC8C6B] focus:border-[#AC8C6B]"
/>
<Search className="absolute left-2 top-2.5 h-4 w-4 text-gray-400" />
</div>
</div>
{filteredCountries.length > 0 ? (
filteredCountries.map((country, index) => (
<div
key={country.iso2 + index}
className="p-3 hover:bg-gray-100 cursor-pointer"
onClick={() => selectCountry(country.country)}
>
{country.country}
</div>
))
) : (
<div className="p-3 text-center text-gray-500">No countries found</div>
)}
</div>
)}
</div>
{message && (
<p className="text-green-700 bg-green-100 py-2 text-center rounded-md">
<p className={`py-2 text-center rounded-md ${message.includes("error") ? "text-red-700 bg-red-100" : "text-green-700 bg-green-100"}`}>
{message}
</p>
)}
@@ -123,7 +230,6 @@ const PremiumBanner = ({ data }) => {
</form>
</div>
{/* Right: Benefits & App Download */}
<div className="p-14 flex flex-col justify-center">
<h2 className="text-4xl font-serif text-[#AC8C6B] text-center">
{header1 ?? "Why Choose Our Consultation?"}
@@ -149,7 +255,7 @@ const PremiumBanner = ({ data }) => {
</li>
<li className="flex items-center">
<ChevronRight className="text-[#AC8C6B] mr-2" />
{header_quote3 ?? "Free initial text-based consultation"}
{header_quote4 ?? "Free initial text-based consultation"}
</li>
</ul>
</div>

View File

@@ -120,7 +120,7 @@ const SearchComponent = ({ isScrolled }) => {
};
return (
<div ref={searchRef} className="relative">
<div ref={searchRef} className="relative flex justify-center items-center">
{isScrolled ? (
<>
<button

View File

@@ -25,7 +25,7 @@ const CategoryHero = ({ params }) => {
const fallbackCategoryName = "Spiritual Essentials";
return (
<section className="relative h-[50vh] flex items-center justify-center overflow-hidden">
<section className="relative h-[50vh] md:h-[80vh] flex items-center justify-center overflow-hidden">
<div className="absolute inset-0 z-0">
<Image
src={`${