Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
Luson045 committed Oct 28, 2024
2 parents 8dbd4bb + de2162d commit c289f25
Show file tree
Hide file tree
Showing 14 changed files with 10,390 additions and 15,072 deletions.
25,000 changes: 10,001 additions & 14,999 deletions client/package-lock.json

Large diffs are not rendered by default.

66 changes: 63 additions & 3 deletions client/src/components/Footer.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { Link, useNavigate } from 'react-router-dom';
// import playstore from "../assets/favicon2.png";
import {
FaGithub,
Expand All @@ -18,6 +18,10 @@ const Footer = () => {
const currentYear = new Date().getFullYear();
const [showScrollTop, setShowScrollTop] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const [messageType, setMessageType] = useState('');
const navigate = useNavigate();

const handleScroll = () => {
if (window.scrollY > 200) {
Expand All @@ -41,6 +45,36 @@ const Footer = () => {
};
}, []);

const handleSubscribe = async () => {
try {
const response = await fetch('http://localhost:8080/otherroutes/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
});
if (response.ok) {
setMessage('Subscription successful! Confirmation mail sent');
setMessageType('success');
setEmail('');
} else {
setMessage('Subscription failed. Try again.');
setMessageType('error');
}
setTimeout(() => {
setMessage('');
setEmail('');
}, 5000); // Clear the message and input after 5 seconds
} catch (error) {
console.error('Subscription error:', error);
setMessage('An error occurred. Please try again later.');
setMessageType('error');

setTimeout(() => setMessage(''), 5000);
}
};

// Define company links with distinct paths
const aboutLinks = [
{ name: 'Our Hospital', path: '/our-hospital' },
Expand Down Expand Up @@ -72,6 +106,7 @@ const Footer = () => {
{ name: 'Business', path: '/business' },
{ name: 'Support Us', path: '/support-us' },
{ name: 'Customer Care', path: '/customer-care' },
{ name: 'Newsletter', path: '/newsletter-dashboard' },
];

// const handleRating = (value) => {
Expand All @@ -88,6 +123,31 @@ const Footer = () => {
return (
<footer className="bg-gradient-to-r from-[#b6dbfc] via-[#8faed9] to-[#b6dbfc] p-8 text-white shadow-lg shadow-black">
<div className="container mx-auto">
{/* Newsletter Subscription Section */}
<div className="text-center md:col-span-2 lg:col-span-4 my-4">
<h3 className="text-2xl font-semibold mb-4">Subscribe to our Newsletter</h3>
<div className="flex flex-col md:flex-row justify-center items-center gap-2 md:gap-4">
<input
type="email"
placeholder="Enter your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="p-2 rounded border border-gray-300 text-black w-full max-w-[300px]"
/>
<button
onClick={handleSubscribe}
className="bg-blue-600 hover:bg-blue-700 text-white p-2 rounded w-full max-w-[150px]"
>
Subscribe
</button>
</div>
{message && (
<p className={`text-2xl mt-2 ${messageType === 'success' ? 'text-green-500' : 'text-red-500'}`}>
{message}
</p>
)}
</div>

<div className="flex flex-wrap justify-between space-x-4">
{/* Med Space Section */}
<div className="space-y-4 w-full md:w-auto">
Expand Down Expand Up @@ -279,8 +339,8 @@ const Footer = () => {
</button>
</div>
</div>
</footer>
</footer >
);
};

export default Footer;
export default Footer;
6 changes: 4 additions & 2 deletions client/src/components/Layout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import HospitalAppointments from '../pages/HospitalPanal';
import BusinessContactForm from "./BusinessContactForm";
import ForgotPassword from "./ForgotPassword";
import PrivateRoute from '../privateroute/privateroute';

import Newsletters from '../pages/Newsletters';

function Layout() {
const location = useLocation();
Expand All @@ -49,7 +49,8 @@ function Layout() {
path === '/Labtest' ||
path === '/blog' ||
path === '/business'||
path === '/forgot-password'
path === '/forgot-password'||
path === '/newsletter-dashboard'
) {
showNavAndFooter = true;
}
Expand Down Expand Up @@ -96,6 +97,7 @@ function Layout() {
<Route path="/business" element={<BusinessContactForm />}></Route>
<Route path="/not-found" element={<NotFound />} />
<Route path="*" element={<Navigate to="/not-found" />} />
<Route path="/newsletter-dashboard" element={<Newsletters/>} />
</Routes>
</div>
<Chatbot />
Expand Down
90 changes: 40 additions & 50 deletions client/src/components/Review.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useState, useEffect, useCallback } from 'react';
import { ChevronLeft, ChevronRight, Star } from 'lucide-react';
import { useMediaQuery } from 'react-responsive';
import { useRecoilValue } from 'recoil';
import { mode } from '../store/atom'; // Importing the atom for mode
import { mode } from '../store/atom';

const reviews = [
{
Expand Down Expand Up @@ -78,24 +78,28 @@ const infiniteReviews = [reviews[reviews.length - 1], ...reviews, reviews[0]];

const ReviewCard = ({ review, dark }) => (
<div
className={` rounded-lg hover:shadow-xl p-4 flex flex-col justify-between h-full transition-shadow duration-300 transform hover:scale-105 shadow-[0_2px_10px_rgba(0,0,0,0.3)] ${dark === 'dark'
? 'bg-gray-800 '
: 'bg-white'
}`}>
className={`rounded-lg hover:shadow-xl p-4 flex flex-col justify-between h-full transition-shadow duration-300 transform hover:scale-105 shadow-[0_2px_10px_rgba(0,0,0,0.3)] ${dark === 'dark' ? 'bg-gray-800' : 'bg-white'}`}
>
<div>
<div className="flex items-center mb-4">
<img
src={review.image}
alt={review.name}
className="w-16 h-16 rounded-full border-2 mr-4 object-cover"
className="w-16 h-16 rounded-full border-2 mr-4 object-cover"
/>
<div>
<h3 className={`font-semibold text-lg ${dark === 'dark' ? 'text-gray-300' : 'text-gray-800'}`}>{review.name}</h3>
<p className={` text-sm ${dark === 'dark' ? 'text-yellow-400' : 'text-blue-500'}`}>{review.role}</p>
<h3 className={`font-semibold text-lg ${dark === 'dark' ? 'text-gray-300' : 'text-gray-800'}`}>
{review.name}
</h3>
<p className={`text-sm ${dark === 'dark' ? 'text-yellow-400' : 'text-blue-500'}`}>
{review.role}
</p>
</div>
</div>
<p className={`text-xl font-bold mb-2 ${dark === 'dark' ? 'text-gray-300' : 'text-gray-800'}`}>"{review.quote}"</p>
<p className={` mb-4 ${dark === 'dark' ? 'text-gray-200' : 'text-gray-600'}`}>{review.review}</p>
<p className={`text-xl font-bold mb-2 ${dark === 'dark' ? 'text-gray-300' : 'text-gray-800'}`}>
"{review.quote}"
</p>
<p className={`mb-4 ${dark === 'dark' ? 'text-gray-200' : 'text-gray-600'}`}>{review.review}</p>
</div>
<div className="flex items-center">
{[...Array(5)].map((_, i) => (
Expand All @@ -109,87 +113,73 @@ const ReviewCard = ({ review, dark }) => (
);

const Review = () => {
const [currentIndex, setCurrentIndex] = useState(1); // Start at the first real review
const [currentIndex, setCurrentIndex] = useState(1);
const [isTransitioning, setIsTransitioning] = useState(false);
const isSmallScreen = useMediaQuery({ query: '(max-width: 640px)' });
const isLargeScreen = useMediaQuery({ query: '(min-width: 641px)' });
const dark = useRecoilValue(mode);

const reviewWidth = isSmallScreen ? 100 : 100 / 3;

const nextReview = useCallback(() => {
setCurrentIndex((prevIndex) => (prevIndex + 1) % infiniteReviews.length);
setIsTransitioning(true);
setCurrentIndex((prevIndex) => prevIndex + 1);
}, []);

const prevReview = useCallback(() => {
setCurrentIndex(
(prevIndex) =>
(prevIndex - 1 + infiniteReviews.length) % infiniteReviews.length,
);
setIsTransitioning(true);
setCurrentIndex((prevIndex) => prevIndex - 1);
}, []);

const handleTransitionEnd = () => {
setIsTransitioning(false);
if (currentIndex === infiniteReviews.length - 1) {
setCurrentIndex(1); // Reset to the first real review
} else if (currentIndex === 0) {
setCurrentIndex(infiniteReviews.length - 2); // Reset to the last real review
}
};

useEffect(() => {
const timer = setInterval(nextReview, 5000);
return () => clearInterval(timer);
}, [nextReview]);

// Calculate the width of each review based on screen size
const reviewWidth = isSmallScreen ? 100 : 100 / 3; // Adjust width for small screens or large screens

return (
<section className="py-16 px-4 overflow-hidden">
<div className="max-w-7xl mx-auto">
<h2 className={`text-4xl font-bold text-center mb-12 text-gray-800 ${dark === 'dark' ? 'text-yellow-400' : 'text-blue-600'
}`}>
<h2 className={`text-4xl font-bold text-center mb-12 text-gray-800 ${dark === 'dark' ? 'text-yellow-400' : 'text-blue-600'}`}>
What Our Community Says
</h2>
<div className="relative">
<div className="overflow-visible">
<div
className="flex transition-transform duration-500 ease-in-out"
style={{
transform: `translateX(-${(currentIndex - 1) * reviewWidth}%)`,
}} // Adjust index for translation
transform: `translateX(-${currentIndex * reviewWidth}%)`,
transition: isTransitioning ? 'transform 0.5s ease-in-out' : 'none',
}}
onTransitionEnd={handleTransitionEnd}
>
{infiniteReviews.map((review, index) => (
<div
key={index}
className={`w-full ${isSmallScreen ? 'flex-shrink-0' : 'sm:w-1/3 flex-shrink-0'} px-2`}
>
<div key={index} className={`w-full ${isSmallScreen ? 'flex-shrink-0' : 'sm:w-1/3 flex-shrink-0'} px-2`}>
<ReviewCard review={review} dark={dark} />
</div>
))}
</div>
</div>
<button
onClick={prevReview}
className={`absolute top-1/2 -left-4 transform -translate-y-1/2 rounded-full p-2 shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200 hover:shadow-lg ml-1 ${dark === 'dark' ? 'bg-gray-600 hover:bg-gray-500' : 'bg-white hover:bg-gray-100'}`}
className={`absolute top-1/2 -left-4 transform -translate-y-1/2 rounded-full p-2 shadow-md ml-1 ${dark === 'dark' ? 'bg-gray-600 hover:bg-gray-500' : 'bg-white hover:bg-gray-100'}`}
>
<ChevronLeft className={`w-6 h-6 ${dark === 'dark' ? 'text-white' : 'text-gray-600'}`} />
</button>
<button
onClick={nextReview}
className={`absolute top-1/2 -right-4 transform -translate-y-1/2 rounded-full p-2 shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200 hover:shadow-lg mr-1 ${dark === 'dark' ? 'bg-gray-600 hover:bg-gray-500' : 'bg-white hover:bg-gray-100'}`}
className={`absolute top-1/2 -right-4 transform -translate-y-1/2 rounded-full p-2 shadow-md mr-1 ${dark === 'dark' ? 'bg-gray-600 hover:bg-gray-500' : 'bg-white hover:bg-gray-100'}`}
>
<ChevronRight className={`w-6 h-6 ${dark === 'dark' ? 'text-white' : 'text-gray-600'} `} />
<ChevronRight className={`w-6 h-6 ${dark === 'dark' ? 'text-white' : 'text-gray-600'}`} />
</button>
</div>
<div className="flex justify-center mt-8">
{/* Show all dots for smaller screens, and minus two for larger screens */}
{infiniteReviews
.slice(
1,
isLargeScreen
? infiniteReviews.length
: infiniteReviews.length - 1,
)
.map((_, index) => (
<div
key={index}
onClick={() => setCurrentIndex(index + 1)}
className={`w-2 h-2 mx-1 rounded-full cursor-pointer transition-colors duration-200 ${
index + 1 === currentIndex ? 'bg-blue-600' : 'bg-gray-300'
}`}
/>
))}
</div>
</div>
</section>
);
Expand Down
98 changes: 98 additions & 0 deletions client/src/pages/Newsletters.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React, { useState, useEffect, useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import { UserContext } from '../store/userContext'; // Import the UserContext

const Newsletters = () => {
const { user, isAuthenticated, loading } = useContext(UserContext); // Access user and loading from UserContext
const navigate = useNavigate();

// Redirect if not admin or not authenticated
useEffect(() => {
if (!loading && (!isAuthenticated || !user?.isAdmin)) {
navigate('/login'); // Redirect to login page if not admin
}
}, [isAuthenticated, user, loading, navigate]);

const [subject, setSubject] = useState('');
const [message, setMessage] = useState('');
const [status, setStatus] = useState('');

const handleSendNewsletter = async () => {
try {
const credentials = btoa(`${user.email}:${user.password}`);

const response = await fetch('http://localhost:8080/otherroutes/admin/send-mail', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Basic ${credentials}`,
},
body: JSON.stringify({ subject, message }),
});

const result = await response.json();
setStatus(response.ok ? { type: 'success', message: result.message } : { type: 'error', message: result.message });

// Clear inputs and status message after 7 seconds
setTimeout(() => {
setSubject('');
setMessage('');
setStatus('');
}, 7000); // 7 seconds in milliseconds
} catch (error) {
setStatus({ type: 'error', message: 'An error occurred. Please try again.' });

// Clear the error message after 7 seconds
setTimeout(() => {
setStatus('');
}, 7000);
}
};

// Show loading message if user data is still being fetched
if (loading) return <p>Loading...</p>;

return isAuthenticated && user?.isAdmin ? (
<div className="flex flex-col items-center p-6 bg-gray-100 min-h-screen mt-10">
<div className="w-full max-w-2xl bg-white shadow-lg rounded-lg p-6">
<h2 className="text-2xl font-bold text-center mb-6">Newsletter Dashboard</h2>

<div className="mb-4">
<label className="block text-gray-700 font-semibold mb-2">Subject</label>
<input
type="text"
placeholder="Enter the subject"
value={subject}
onChange={(e) => setSubject(e.target.value)}
className="w-full p-3 border border-gray-300 rounded focus:outline-none focus:border-blue-500"
/>
</div>

<div className="mb-4">
<label className="block text-gray-700 font-semibold mb-2">Message</label>
<textarea
placeholder="Write your message here"
value={message}
onChange={(e) => setMessage(e.target.value)}
className="w-full h-32 p-3 border border-gray-300 rounded resize-none focus:outline-none focus:border-blue-500"
/>
</div>

<button
onClick={handleSendNewsletter}
className="w-full bg-blue-600 text-white p-3 rounded hover:bg-blue-700 transition duration-300"
>
Send Newsletter
</button>

{status && (
<div className={`mt-4 p-3 rounded ${status.type === 'success' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>
{status.message}
</div>
)}
</div>
</div>
) : null;
};

export default Newsletters;
Loading

0 comments on commit c289f25

Please sign in to comment.