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:
@@ -43,7 +43,7 @@ export function GET() {
|
|||||||
<p>We may occasionally update this Privacy Policy, and we encourage you to periodically review it to stay informed about how we are protecting your information. We will post any changes on this page, and if the changes are significant, we will provide a more prominent notice.</p>
|
<p>We may occasionally update this Privacy Policy, and we encourage you to periodically review it to stay informed about how we are protecting your information. We will post any changes on this page, and if the changes are significant, we will provide a more prominent notice.</p>
|
||||||
|
|
||||||
<h2>How To Contact Us</h2>
|
<h2>How To Contact Us</h2>
|
||||||
<p>If you have any questions or comments about this Privacy Policy, or if you would like us to update information we have about you or your preferences, please contact us by email at <a href="mailto:contact@nepalirudraksha.com">contact@nepalirudraksha.com</a>.</p>
|
<p>If you have any questions or comments about this Privacy Policy, or if you would like us to update information we have about you or your preferences, please contact us by email at <a href="mailto:nepali.rudrakshabeads@gmail.com">nepali.rudrakshabeads@gmail.com</a>.</p>
|
||||||
|
|
||||||
<p>Your privacy is of utmost importance to us. We commit to safeguarding your personal information in accordance with legal obligations. By using our app, you are accepting the practices outlined in this Privacy Policy. If you do not agree to the terms of this Privacy Policy, please refrain from using our app.</p>
|
<p>Your privacy is of utmost importance to us. We commit to safeguarding your personal information in accordance with legal obligations. By using our app, you are accepting the practices outlined in this Privacy Policy. If you do not agree to the terms of this Privacy Policy, please refrain from using our app.</p>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { createContext, useContext, useState, useEffect } from "react";
|
import React, { createContext, useContext, useState, useEffect } from "react";
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
|
||||||
const CurrencyContext = createContext();
|
const CurrencyContext = createContext();
|
||||||
|
|
||||||
@@ -15,6 +17,7 @@ export const CurrencyProvider = ({ children }) => {
|
|||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
|
||||||
const fetchExchangeRates = async () => {
|
const fetchExchangeRates = async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@@ -23,28 +26,28 @@ export const CurrencyProvider = ({ children }) => {
|
|||||||
const cachedData = localStorage.getItem("exchangeRates");
|
const cachedData = localStorage.getItem("exchangeRates");
|
||||||
const cached = cachedData ? JSON.parse(cachedData) : null;
|
const cached = cachedData ? JSON.parse(cachedData) : null;
|
||||||
|
|
||||||
if (
|
if (cached && new Date().getTime() - cached.timestamp < 24 * 60 * 60 * 1000) {
|
||||||
cached &&
|
|
||||||
new Date().getTime() - cached.timestamp < 24 * 60 * 60 * 1000
|
|
||||||
) {
|
|
||||||
setExchangeRates(cached.rates);
|
setExchangeRates(cached.rates);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const currencies = Object.keys(SUPPORTED_CURRENCIES)
|
const currencies = Object.keys(SUPPORTED_CURRENCIES)
|
||||||
.filter((key) => key !== "INR")
|
.filter((key) => key !== "INR")
|
||||||
.join(",");
|
.join(",");
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await axios.get("https://apilayer.net/api/live", {
|
||||||
`https://apilayer.net/api/live?access_key=9bcb30907dee1cda9866f7b49f0f8def¤cies=${currencies}&source=INR&format=1`
|
params: {
|
||||||
);
|
access_key: "9bcb30907dee1cda9866f7b49f0f8def",
|
||||||
|
currencies: currencies,
|
||||||
|
source: "INR",
|
||||||
|
format: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
const data = response.data;
|
||||||
throw new Error("Failed to fetch exchange rates");
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
if (!data.success) {
|
if (!data.success) {
|
||||||
throw new Error(data.error?.info || "API request failed");
|
throw new Error(data.error?.info || "API request failed");
|
||||||
}
|
}
|
||||||
@@ -55,28 +58,42 @@ export const CurrencyProvider = ({ children }) => {
|
|||||||
acc[currency] = 1;
|
acc[currency] = 1;
|
||||||
} else {
|
} else {
|
||||||
const rate = data.quotes?.[`INR${currency}`];
|
const rate = data.quotes?.[`INR${currency}`];
|
||||||
acc[currency] = rate || null;
|
if (!rate) {
|
||||||
|
throw new Error(`Rate not found for ${currency}`);
|
||||||
|
}
|
||||||
|
acc[currency] = rate;
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
const ratesData = {
|
localStorage.setItem(
|
||||||
rates,
|
"exchangeRates",
|
||||||
timestamp: data.timestamp * 1000,
|
JSON.stringify({ rates, timestamp: new Date().getTime() })
|
||||||
};
|
);
|
||||||
|
|
||||||
localStorage.setItem("exchangeRates", JSON.stringify(ratesData));
|
|
||||||
setExchangeRates(rates);
|
setExchangeRates(rates);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
setError("Error fetching exchange rates");
|
console.error("Error fetching exchange rates:", error);
|
||||||
console.error("Exchange rate fetch error:", err);
|
|
||||||
} finally {
|
setError("Failed to load currency conversion rates. Please try again later.");
|
||||||
|
|
||||||
|
if (cached) {
|
||||||
|
console.log("Using older cached rates as fallback");
|
||||||
|
setExchangeRates(cached.rates);
|
||||||
|
} else {
|
||||||
|
setExchangeRates(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in exchange rate handling:", error);
|
||||||
|
setError("Failed to load currency conversion rates");
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const convertPrice = (price) => {
|
const convertPrice = (price) => {
|
||||||
if (!price || typeof price !== "number") return price;
|
if (!price || typeof price !== "number") return price;
|
||||||
if (!exchangeRates || !exchangeRates[selectedCurrency]) return price;
|
if (!exchangeRates || !exchangeRates[selectedCurrency]) return price;
|
||||||
|
|||||||
25
components/common/CurrencyTooltip.jsx
Normal file
25
components/common/CurrencyTooltip.jsx
Normal 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;
|
||||||
57
components/common/Price.jsx
Normal file
57
components/common/Price.jsx
Normal 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;
|
||||||
@@ -1,43 +1,88 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { ChevronDown } from 'lucide-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 [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 (
|
return (
|
||||||
<div className="relative w-32">
|
<div className="relative w-auto md:w-32">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsOpen(!isOpen)}
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
className="w-full px-4 py-2.5 bg-white border border-gray-200 rounded-lg shadow-sm
|
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-gray-700 text-sm font-medium
|
flex items-center justify-between text-sm font-medium
|
||||||
hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500
|
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'}`}
|
||||||
>
|
>
|
||||||
|
{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}
|
{SUPPORTED_CURRENCIES[selectedCurrency]?.country}
|
||||||
|
{error && selectedCurrency !== 'INR' && (
|
||||||
|
<AlertCircle className="inline ml-1 w-3 h-3" />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<ChevronDown
|
<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`}
|
${isOpen ? 'rotate-180' : ''} group-hover:text-gray-600`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div
|
<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"
|
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">
|
<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
|
<button
|
||||||
key={code}
|
key={code}
|
||||||
onClick={() => {
|
onClick={() => handleCurrencyChange(code)}
|
||||||
setSelectedCurrency(code);
|
|
||||||
setIsOpen(false);
|
|
||||||
}}
|
|
||||||
className={`w-full px-4 py-2.5 text-left text-sm transition-colors duration-150
|
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
|
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 ? 'bg-blue-50 text-blue-600 font-medium' : 'text-gray-700'}
|
||||||
${selectedCurrency === code ? 'hover:bg-blue-50' : 'hover:bg-gray-50'}`}
|
${selectedCurrency === code ? 'hover:bg-blue-50' : 'hover:bg-gray-50'}`}
|
||||||
>
|
>
|
||||||
|
<span className="mr-2">{symbol}</span>
|
||||||
{country}
|
{country}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
@@ -47,5 +92,4 @@ const CurrencySelect = ({ selectedCurrency, setSelectedCurrency, SUPPORTED_CURRE
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CurrencySelect;
|
export default CurrencySelect;
|
||||||
@@ -18,30 +18,42 @@ const Navbar = () => {
|
|||||||
const { category } = useContext(ProductContext);
|
const { category } = useContext(ProductContext);
|
||||||
const { token } = useContext(MainContext);
|
const { token } = useContext(MainContext);
|
||||||
const [cartItemCount, setCartItemCount] = useState(0);
|
const [cartItemCount, setCartItemCount] = useState(0);
|
||||||
const { selectedCurrency, setSelectedCurrency, SUPPORTED_CURRENCIES } =
|
const { selectedCurrency, setSelectedCurrency, SUPPORTED_CURRENCIES, error } =
|
||||||
useCurrency();
|
useCurrency();
|
||||||
|
|
||||||
const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen);
|
const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
setIsScrolled(window.scrollY > 60);
|
const isMobile = window.innerWidth < 768;
|
||||||
|
|
||||||
|
setIsScrolled(isMobile || window.scrollY > 60);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleScroll();
|
||||||
|
|
||||||
window.addEventListener("scroll", 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 () => {
|
const getCart = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await authAxios.get("/orders/cart/");
|
const response = await authAxios.get("/orders/cart/");
|
||||||
const cartData = response.data;
|
const cartData = response.data;
|
||||||
console.log(cartData)
|
console.log(cartData);
|
||||||
console.log(cartData.length)
|
console.log(cartData.length);
|
||||||
|
|
||||||
// Calculate total items in cart
|
// Calculate total items in cart
|
||||||
if (cartData && cartData.length > 0 && cartData[0].items) {
|
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);
|
setCartItemCount(totalItems);
|
||||||
} else {
|
} else {
|
||||||
setCartItemCount(0);
|
setCartItemCount(0);
|
||||||
@@ -91,10 +103,9 @@ const Navbar = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
<header className="fixed top-0 left-0 right-0 z-50 bg-white shadow-md">
|
<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="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">
|
<button onClick={toggleSidebar} className="z-50">
|
||||||
{isSidebarOpen ? (
|
{isSidebarOpen ? (
|
||||||
<IoMdClose size={28} />
|
<IoMdClose size={28} />
|
||||||
@@ -106,9 +117,9 @@ const Navbar = () => {
|
|||||||
<Image
|
<Image
|
||||||
src="/logo1.jpg"
|
src="/logo1.jpg"
|
||||||
alt="Logo"
|
alt="Logo"
|
||||||
width={56}
|
width={60}
|
||||||
height={56}
|
height={60}
|
||||||
className="object-contain"
|
className="!max-w-[60px]"
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -138,6 +149,7 @@ const Navbar = () => {
|
|||||||
selectedCurrency={selectedCurrency}
|
selectedCurrency={selectedCurrency}
|
||||||
setSelectedCurrency={setSelectedCurrency}
|
setSelectedCurrency={setSelectedCurrency}
|
||||||
SUPPORTED_CURRENCIES={SUPPORTED_CURRENCIES}
|
SUPPORTED_CURRENCIES={SUPPORTED_CURRENCIES}
|
||||||
|
error={error}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -169,7 +181,10 @@ const Navbar = () => {
|
|||||||
<div className="fixed inset-0 bg-black bg-opacity-50 z-40">
|
<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="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 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
|
||||||
|
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">
|
<div className="mb-6 border-t md:border-0">
|
||||||
<h2 className="text-lg font-semibold mb-4">Quick Links</h2>
|
<h2 className="text-lg font-semibold mb-4">Quick Links</h2>
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ const Hero = ({ data }) => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-[45vh] bg-background">
|
<div className="relative w-full h-[45vh] md:h-[80vh] bg-background">
|
||||||
<Carousel
|
<Carousel
|
||||||
plugins={[plugin.current]}
|
plugins={[plugin.current]}
|
||||||
className="w-full h-full"
|
className="w-full h-full"
|
||||||
@@ -77,7 +77,7 @@ const Hero = ({ data }) => {
|
|||||||
<CarouselContent>
|
<CarouselContent>
|
||||||
{heroData.map((item, index) => (
|
{heroData.map((item, index) => (
|
||||||
<CarouselItem key={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
|
<Image
|
||||||
src={item.src || "/placeholder.svg"}
|
src={item.src || "/placeholder.svg"}
|
||||||
alt={`Slide ${index + 1}`}
|
alt={`Slide ${index + 1}`}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const PaymentComponent = ({ amount, onSuccess }) => {
|
|||||||
|
|
||||||
const response = await axios.get("https://apilayer.net/api/live", {
|
const response = await axios.get("https://apilayer.net/api/live", {
|
||||||
params: {
|
params: {
|
||||||
access_key: "ytguhijok",
|
access_key: "9bcb30907dee1cda9866f7b49f0f8def",
|
||||||
currencies: "USD",
|
currencies: "USD",
|
||||||
source: "INR",
|
source: "INR",
|
||||||
format: 1,
|
format: 1,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useState } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import { ChevronRight, Gem } from "lucide-react";
|
import { ChevronRight, Gem, Search, ChevronDown } from "lucide-react";
|
||||||
import authAxios from "@/utils/axios";
|
import authAxios from "@/utils/axios";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
@@ -13,14 +13,62 @@ const PremiumBanner = ({ data }) => {
|
|||||||
header_quote3,
|
header_quote3,
|
||||||
header_quote4,
|
header_quote4,
|
||||||
} = data || {};
|
} = data || {};
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
first_name: "",
|
first_name: "",
|
||||||
last_name: "",
|
last_name: "",
|
||||||
email: "",
|
email: "",
|
||||||
phone_number: "",
|
phone_number: "",
|
||||||
|
country: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const [message, setMessage] = useState("");
|
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 handleInputChange = (e) => {
|
||||||
const { name, value } = e.target;
|
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) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!formData.country) {
|
||||||
|
setMessage("Please select your country.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await authAxios.post(
|
const response = await authAxios.post(
|
||||||
"/consultation/booking/create/",
|
"/consultation/booking/create/",
|
||||||
@@ -43,6 +107,7 @@ const PremiumBanner = ({ data }) => {
|
|||||||
last_name: "",
|
last_name: "",
|
||||||
email: "",
|
email: "",
|
||||||
phone_number: "",
|
phone_number: "",
|
||||||
|
country: "",
|
||||||
});
|
});
|
||||||
setMessage("Consultation experts will contact you shortly.");
|
setMessage("Consultation experts will contact you shortly.");
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -61,7 +126,6 @@ const PremiumBanner = ({ data }) => {
|
|||||||
<div className="min-h-screen bg-white p-2 md:p-8 flex items-center justify-center">
|
<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="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">
|
<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]">
|
<h2 className="text-4xl font-serif text-center text-[#AC8C6B]">
|
||||||
Book a Free Consultation
|
Book a Free Consultation
|
||||||
@@ -108,8 +172,51 @@ const PremiumBanner = ({ data }) => {
|
|||||||
required
|
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 && (
|
{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}
|
{message}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -123,7 +230,6 @@ const PremiumBanner = ({ data }) => {
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right: Benefits & App Download */}
|
|
||||||
<div className="p-14 flex flex-col justify-center">
|
<div className="p-14 flex flex-col justify-center">
|
||||||
<h2 className="text-4xl font-serif text-[#AC8C6B] text-center">
|
<h2 className="text-4xl font-serif text-[#AC8C6B] text-center">
|
||||||
{header1 ?? "Why Choose Our Consultation?"}
|
{header1 ?? "Why Choose Our Consultation?"}
|
||||||
@@ -149,7 +255,7 @@ const PremiumBanner = ({ data }) => {
|
|||||||
</li>
|
</li>
|
||||||
<li className="flex items-center">
|
<li className="flex items-center">
|
||||||
<ChevronRight className="text-[#AC8C6B] mr-2" />
|
<ChevronRight className="text-[#AC8C6B] mr-2" />
|
||||||
{header_quote3 ?? "Free initial text-based consultation"}
|
{header_quote4 ?? "Free initial text-based consultation"}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ const SearchComponent = ({ isScrolled }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={searchRef} className="relative">
|
<div ref={searchRef} className="relative flex justify-center items-center">
|
||||||
{isScrolled ? (
|
{isScrolled ? (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const CategoryHero = ({ params }) => {
|
|||||||
const fallbackCategoryName = "Spiritual Essentials";
|
const fallbackCategoryName = "Spiritual Essentials";
|
||||||
|
|
||||||
return (
|
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">
|
<div className="absolute inset-0 z-0">
|
||||||
<Image
|
<Image
|
||||||
src={`${
|
src={`${
|
||||||
|
|||||||
Reference in New Issue
Block a user