Initial commit of project without large files

This commit is contained in:
2025-12-29 15:45:02 +05:30
parent 82ad17a88a
commit 2a58e4af62
79 changed files with 26524 additions and 0 deletions

3
.env Normal file
View File

@@ -0,0 +1,3 @@
REACT_APP_API_BASE_URL=http://localhost:8000
HOST=0.0.0.0

6
.gitattributes vendored Normal file
View File

@@ -0,0 +1,6 @@
build/static/media/* filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
build/static/js/*.js filter=lfs diff=lfs merge=lfs -text
build/static/css/*.css filter=lfs diff=lfs merge=lfs -text
*.psd filter=lfs diff=lfs merge=lfs -text

70
README.md Normal file
View File

@@ -0,0 +1,70 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
The page will reload when you make changes.\
You may also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

0
git Normal file
View File

19803
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

67
package.json Normal file
View File

@@ -0,0 +1,67 @@
{
"name": "lcepl-website",
"version": "0.1.0",
"private": true,
"proxy": "",
"dependencies": {
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.11.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"framer-motion": "^12.23.12",
"helmet": "^8.1.0",
"morgan": "^1.10.1",
"react": "^18.2.0",
"react-countup": "^6.5.3",
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
"react-icons": "^5.5.0",
"react-intersection-observer": "^10.0.0",
"react-router-dom": "^7.7.1",
"react-scripts": "^5.0.1",
"react-simple-maps": "^3.0.0",
"react-slick": "^0.31.0",
"react-toastify": "^11.0.5",
"slick-carousel": "^1.8.1",
"web-vitals": "^2.1.4",
"webpack-dev-server": "^5.2.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"server": "node server.js",
"server:dev": "nodemon server.js",
"dev": "concurrently -k -n server,client -p \"[{name}]\" \"npm:server:dev\" \"npm:start\""
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.11",
"autoprefixer": "^10.4.21",
"concurrently": "*",
"nodemon": "*",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.17"
}
}

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

3
public/gallery/cert1.jpg Normal file
View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6151d682778ae9a8794065adc611381fda940841aacbc1aa9aeb6d5dea51ffea
size 771090

3
public/gallery/cert2.jpg Normal file
View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c498106d312c3e6df6b7cee5575dc4c535bc6f2c965dc7338abf93fc16863125
size 945980

3
public/gallery/cert3.jpg Normal file
View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:aebbabebc80ebf16465b20f24b3ce88153c6c9d8fbb0b042cb492d2ad959a185
size 387688

3
public/gallery/cert4.jpg Normal file
View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c6756c2a1f395f71f0bea7b12fec08f0e6c39a74e4ce41beac8e40bd32cdd71d
size 653272

3
public/gallery/cert5.jpg Normal file
View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:00f9b20bb8f70db9d7ccd87a223729c8bfca846f8def0e3084448b0a0a564734
size 203639

3
public/gallery/cert6.jpg Normal file
View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:35d31b3aadbcfae40b3dba305cd501ed994e76c797ef27e977868f787804be75
size 61054

3
public/gallery/cert7.jpg Normal file
View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7e8b393b7f4278e5584cdde6579574980c685a561a72b269132e91d01622ff82
size 151321

3
public/gallery/cert8.jpg Normal file
View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b3b9897d820f46be4bf2166dd72017a9ad54bb1bc0fd63c92ceb4e7960cf7aee
size 226604

32
public/index.html Normal file
View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<!-- Updated favicon with larger sizes -->
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" sizes="256x256 128x128 70x70 34x34 26x26 18x18" type="image/x-icon" />
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap" rel="stylesheet" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<!-- Updated description -->
<Helmet>
<title>Laxmi Civil Engineering Services Pvt. Ltd. (LCEPL)</title>
<meta name="description" content="Laxmi Civil Engineering Services Pvt. Ltd. experts in infrastructure, water supply, roads, tunnels, and wastewater projects." />
</Helmet>
<!-- Updated Apple Touch Icon -->
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!-- Manifest for PWA -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Laxmi Civil Engineering Services Pvt. Ltd. (LCEPL)</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

3
public/logo192.png Normal file
View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3090d5d57128fe707c6021b851437e1f0a084f5f65e5b81528fe30305ae717d1
size 38136

3
public/logo512.png Normal file
View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3090d5d57128fe707c6021b851437e1f0a084f5f65e5b81528fe30305ae717d1
size 38136

25
public/manifest.json Normal file
View File

@@ -0,0 +1,25 @@
{
"short_name": "LCEPL",
"name": "Laxmi Civil Engineering Services Pvt. Ltd.",
"icons": [
{
"src": "favicon_large.ico",
"sizes": "256x256 128x128 70x70 34x34 26x26 18x18",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
public/robots.txt Normal file
View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

38
src/App.css Normal file
View File

@@ -0,0 +1,38 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

194
src/App.js Normal file
View File

@@ -0,0 +1,194 @@
// src/App.js
import React, { useEffect } from 'react';
import './index.css';
import { Routes, Route, useLocation } from 'react-router-dom';
import Header from './components/Header';
import Footer from './components/Footer';
import Home from './pages/Home';
import AboutUs from './pages/AboutUs';
import Careers from './pages/Careers';
import Office from './pages/Office';
import ContactUs from './pages/ContactUs';
import Gallery from './pages/Gallery';
import Projects from './pages/Projects';
import AddProjects from './pages/AddProjects';
import GalleryAdmin from './pages/GalleryAdmin';
import BlogList from './pages/BlogList';
import BlogDetail from './pages/BlogDetail';
import JobApplicationForm from './pages/JobApplicationForm';
// Admin
import ProtectedRoute from './components/ProtectedRoute';
import AdminLogin from './pages/AdminLogin';
import AdminLayout from './components/AdminLayout';
import HRAdmin from './pages/HRAdmin';
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
// ScrollToTop component
const ScrollToTop = () => {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return null;
};
const App = () => {
return (
<>
<ScrollToTop /> {/* ensures scroll starts at top on route change */}
<Routes>
{/* Admin Login */}
<Route path="/admin-login" element={<AdminLogin />} />
{/* Protected Admin Routes with Layout */}
<Route
path="/admin"
element={
<ProtectedRoute>
<AdminLayout />
</ProtectedRoute>
}
>
<Route path="projects" element={<AddProjects />} />
<Route path="gallery" element={<GalleryAdmin />} />
<Route path="hr" element={<HRAdmin />} />
</Route>
{/* Public Routes */}
<Route
path="/"
element={
<>
<Header />
<Home />
<Footer />
</>
}
/>
<Route
path="/job-application"
element={
<>
<Header />
<JobApplicationForm />
<Footer />
</>
}
/>
<Route
path="/about-us"
element={
<>
<Header />
<AboutUs />
<Footer />
</>
}
/>
<Route
path="/careers"
element={
<>
<Header />
<Careers />
<Footer />
</>
}
/>
<Route
path="/careers"
element={
<>
<Header />
<Careers />
<Footer />
</>
}
/>
<Route
path="/office"
element={
<>
<Header />
<Office />
<Footer />
</>
}
/>
<Route
path="/contact-us"
element={
<>
<Header />
<ContactUs />
<Footer />
</>
}
/>
<Route
path="/gallery"
element={
<>
<Header />
<Gallery />
<Footer />
</>
}
/>
<Route
path="/projects"
element={
<>
<Header />
<Projects />
<Footer />
</>
}
/>
<Route
path="/blog"
element={
<>
<Header />
<BlogList />
<Footer />
</>
}
/>
<Route
path="/blog/:slug"
element={
<>
<Header />
<BlogDetail />
<Footer />
</>
}
/>
{/* Fallback Route */}
<Route
path="*"
element={
<>
<Header />
<Home />
<Footer />
</>
}
/>
</Routes>
<ToastContainer />
</>
);
};
export default App;

8
src/App.test.js Normal file
View File

@@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View File

@@ -0,0 +1,97 @@
import React, { useState } from "react";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
import PasswordPopup from "./PasswordPopup";
const AdminLayout = () => {
const location = useLocation();
const navigate = useNavigate();
const [showPopup, setShowPopup] = useState(false);
const [popupFor, setPopupFor] = useState("");
const PASSWORDS = {
Projects: "Lcepl@2026", // ← set your Projects password
Gallery: "Lcepl#2026", // ← set your Gallery password
};
const menuItems = [
{ name: "Projects", path: "/admin/projects" },
{ name: "Gallery", path: "/admin/gallery" },
{ name: "HR", path: "/admin/hr" },
];
// When user clicks menu
const handleMenuClick = (item) => {
if (item.name === "Projects" || item.name === "Gallery") {
setPopupFor(item.name);
setShowPopup(true);
} else {
navigate(item.path);
}
};
// Validate password
const handlePasswordSubmit = (password) => {
if (password === PASSWORDS[popupFor]) {
setShowPopup(false);
navigate(`/admin/${popupFor.toLowerCase()}`);
} else {
alert("❌ Incorrect password");
}
};
const handleLogout = () => {
sessionStorage.removeItem("admin");
navigate("/admin-login");
};
return (
<div className="flex min-h-screen">
{/* Sidebar */}
<div className="w-64 bg-gray-800 text-white flex flex-col">
<div className="text-2xl font-bold p-4 border-b border-gray-700">
Admin Panel
</div>
<nav className="flex-1 p-4">
{menuItems.map((item) => (
<button
key={item.name}
onClick={() => handleMenuClick(item)}
className={`w-full text-left px-4 py-2 rounded mb-2 hover:bg-gray-700 transition-colors ${
location.pathname === item.path ? "bg-gray-700 font-semibold" : ""
}`}
>
{item.name}
</button>
))}
</nav>
</div>
{/* Main Content */}
<div className="flex-1 bg-gray-100 p-4 relative">
<button
onClick={handleLogout}
className="absolute top-4 right-4 bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded"
>
Logout
</button>
<Outlet />
</div>
{/* Password Popup */}
{showPopup && (
<PasswordPopup
title={`${popupFor} Access`}
onSubmit={handlePasswordSubmit}
onClose={() => setShowPopup(false)}
/>
)}
</div>
);
};
export default AdminLayout;

View File

@@ -0,0 +1,23 @@
import '../styles/AwardsCard.css';
const AwardsCard = () => {
return (
<section className="awards-section">
<div className="awards-container">
<h2 className="highlight-title">Awards & Certifications</h2>
<p>
We are proudly certified under <strong>ISO 9001:2015</strong>, <strong>ISO 14001:2015</strong>, and
<strong> ISO 45001:2018</strong>, showcasing our unwavering commitment to quality, environmental
responsibility, and occupational safety.
</p>
<p>
Our achievements and accolades reflect our drive for excellence, innovation, and industry leadership over the
past four decades.
</p>
</div>
</section>
);
};
export default AwardsCard;

View File

@@ -0,0 +1,13 @@
import React from 'react';
import '../styles/AboutUs.css';
const ClientLogo = ({ logo }) => {
return (
<div className="client-logo">
<img src={logo} alt="Client Logo" className="client-logo-img" />
</div>
);
};
export default ClientLogo;

View File

@@ -0,0 +1,214 @@
import { motion } from 'framer-motion';
import React, { useState } from 'react';
const clients = [
{ name: 'Public Works Department', logo: require('../assets/clientLogos/pwd.png') },
{ name: 'Water Resource Department', logo: require('../assets/clientLogos/wrd.png') },
{ name: 'Maharashtra Jeevan Pradhikaran', logo: require('../assets/clientLogos/mjp.png') },
{ name: 'The City & Industrial Development Corporation of Maharashtra Limited', logo: require('../assets/clientLogos/cidco.png') },
{ name: 'Maharashtra Industrial Development Corporation', logo: require('../assets/clientLogos/midc.png') },
{ name: 'Bengaluru Water Supply and Sewerage Board', logo: require('../assets/clientLogos/bwssb.png') },
{ name: 'Karnataka Urban Infrastructure Development and Finance Corporation', logo: require('../assets/clientLogos/kuidfc.png') },
{ name: 'Krishna Bhagya Jal Nigam Limited', logo: require('../assets/clientLogos/kbjnl.png') },
{ name: 'Karnataka Urban Water Supply And Drainage Board', logo: require('../assets/clientLogos/kuwsdb.png') },
{ name: 'Kerala Water Authority', logo: require('../assets/clientLogos/kwa.png') },
{ name: 'Gail India Limited', logo: require('../assets/clientLogos/gail.png') },
{ name: 'Municipal Corporation', logo: require('../assets/clientLogos/municipal.png') },
{ name: 'Narmada Valley Development Authority', logo: require('../assets/clientLogos/nvda.png') },
{ name: 'Rural Water Supply & Sanitation Department', logo: require('../assets/clientLogos/rwss.png') },
{ name: 'Madhya Pradesh Jal Nigam Maryadit', logo: require('../assets/clientLogos/mpjnm.png') },
{ name: 'Public Health Engineering Department', logo: require('../assets/clientLogos/phed.png') },
{ name: 'Goa Tourism Development Corporation Limited', logo: require('../assets/clientLogos/gtdcl.png') },
{ name: 'Central Railway', logo: require('../assets/clientLogos/cr.png') },
{ name: 'Gujarat Water Infrastructure Limited', logo: require('../assets/clientLogos/gwil.png') },
{ name: 'JUIDCO', logo: require('../assets/clientLogos/judaico.png') },
{ name: 'Hindustan Aeronautics Limited', logo: require('../assets/clientLogos/hal.png') },
{ name: 'Municipal Councils', logo: require('../assets/clientLogos/municipal-council.png') },
{ name: 'Solapur Smart City', logo: require('../assets/clientLogos/solapur.png') },
{ name: 'Vadodara Smart City', logo: require('../assets/clientLogos/vadodara.png') },
];
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
},
},
};
const cardVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
type: 'spring',
stiffness: 100,
damping: 20,
},
},
};
const hoverVariants = {
hover: {
y: -10,
scale: 1.05,
boxShadow: "0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
transition: {
type: "spring",
stiffness: 400,
damping: 10
}
},
tap: {
y: -15,
scale: 1.1,
boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
transition: {
type: "spring",
stiffness: 400,
damping: 10
}
}
};
const ClientsSection = () => {
const [selectedCard, setSelectedCard] = useState(null);
const isMobile = typeof window !== 'undefined' && window.innerWidth < 768;
const isTablet = typeof window !== 'undefined' && window.innerWidth >= 768 && window.innerWidth < 1024;
let row1, row2, row3;
if (isMobile) {
row1 = clients.slice(0, 8);
} else if (isTablet) {
const chunkSize = Math.ceil(clients.length / 2);
row1 = clients.slice(0, chunkSize);
row2 = clients.slice(chunkSize);
} else {
const chunkSize = Math.ceil(clients.length / 3);
row1 = clients.slice(0, chunkSize);
row2 = clients.slice(chunkSize, chunkSize * 2);
row3 = clients.slice(chunkSize * 2);
}
const handleCardClick = (clientKey) => {
setSelectedCard(selectedCard === clientKey ? null : clientKey);
};
const renderScrollingRow = (rowItems, rowKey) => (
<motion.div
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
variants={containerVariants}
key={rowKey}
className="w-full"
>
<div className="flex overflow-hidden w-full">
<div className="flex animate-[scroll_30s_linear_infinite] hover:animation-pause">
{[...rowItems, ...rowItems].map((client, index) => {
const clientKey = `${rowKey}-${index}`;
return (
<motion.div
key={clientKey}
variants={cardVariants}
whileHover="hover"
whileTap="tap"
animate={selectedCard === clientKey ? "tap" : "visible"}
onClick={() => handleCardClick(clientKey)}
className="inline-flex flex-col items-center justify-center mx-2 md:mx-4 bg-white p-3 md:p-4 rounded-lg shadow-md w-36 h-32 md:w-48 md:h-40 flex-shrink-0 cursor-pointer relative z-0"
style={{
zIndex: selectedCard === clientKey ? 10 : 1
}}
>
<motion.div
variants={hoverVariants}
className="w-full h-full flex flex-col items-center justify-center"
>
<div className="h-12 md:h-16 flex items-center mb-2 md:mb-3">
<img
src={client.logo}
alt={client.name}
className="max-h-full max-w-full object-contain"
loading="lazy"
/>
</div>
<p className="text-xs md:text-sm font-medium text-blue-900 text-center break-words w-full px-1 md:px-2">
{client.name}
</p>
</motion.div>
</motion.div>
);
})}
</div>
</div>
</motion.div>
);
return (
<section className="bg-blue-900 py-12 md:py-20 px-0 overflow-hidden relative">
{/* Backdrop that appears when a card is selected */}
{selectedCard && (
<div
className="fixed inset-0 bg-black bg-opacity-30 z-20"
onClick={() => setSelectedCard(null)}
/>
)}
<div className="max-w-7xl mx-auto px-4 relative z-10">
<motion.h2
className="text-3xl md:text-4xl font-bold text-white mb-3 md:mb-4 text-center"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
Clients who trust us
</motion.h2>
<motion.h3
className="text-xl md:text-2xl font-semibold text-center text-blue-200 mb-4 md:mb-6"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2, duration: 0.5 }}
>
RISING TOGETHER WITH ELITE COLLABORATIONS
</motion.h3>
<motion.p
className="text-center text-blue-100 max-w-4xl mx-auto mb-8 md:mb-12 px-4 text-sm md:text-base"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4, duration: 0.5 }}
>
At LCEPL, we take pride in unveiling our elite portfolio a testament to our commitment to elevating horizons and seamlessly connecting the landmarks that shape the world around us.
</motion.p>
</div>
<div className="space-y-4 md:space-y-6 w-full relative z-10">
{renderScrollingRow(row1, 'row1')}
{!isMobile && renderScrollingRow(row2, 'row2')}
{!isMobile && !isTablet && renderScrollingRow(row3, 'row3')}
</div>
<style jsx global>{`
@keyframes scroll {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
.animate-\\[scroll_30s_linear_infinite\\] {
animation: scroll 30s linear infinite;
}
@media (max-width: 768px) {
.animate-\\[scroll_30s_linear_infinite\\] {
animation: scroll 30s linear infinite;
}
}
`}</style>
</section>
);
};
export default ClientsSection;

View File

@@ -0,0 +1,35 @@
import React from 'react';
import CountUp from 'react-countup';
import { useInView } from 'react-intersection-observer';
import '../styles/CounterCard.css';
const CounterCard = ({ count, label, icon }) => {
// Extract numeric part from count string (e.g. "200+" => 200)
const numericCount = typeof count === 'string' ? parseInt(count, 10) : count;
// Check if original count string ends with '+'
const hasPlus = typeof count === 'string' && count.trim().endsWith('+');
// Intersection Observer to detect when the card is visible
const { ref, inView } = useInView({
triggerOnce: true, // animate only once
threshold: 0.3 // 30% of the card visible triggers animation
});
return (
<div className="counter-card" ref={ref}>
{icon && (
<div className="icon" style={{ fontSize: '3rem', marginBottom: '15px' }}>
{icon}
</div>
)}
<h3>
{inView ? <CountUp end={numericCount} duration={2.5} /> : 0}
{hasPlus && '+'}
</h3>
<p>{label}</p>
</div>
);
};
export default CounterCard;

View File

@@ -0,0 +1,13 @@
import React from 'react';
import '../styles/AboutUs.css';
const DirectorCard = ({ image, name }) => {
return (
<div className="director-card">
<img src={image} alt={name} className="director-photo" />
<p>{name}</p>
</div>
);
};
export default DirectorCard;

13
src/components/Footer.jsx Normal file
View File

@@ -0,0 +1,13 @@
import React from "react";
const Footer = () => {
return (
<footer className="bg-gray-900 py-4">
<div className="container mx-auto px-4 text-center text-sm text-white">
&copy; {new Date().getFullYear()} Laxmi Civil Engineering Services Pvt. Ltd. All Rights Reserved.
</div>
</footer>
);
};
export default Footer;

84
src/components/Header.jsx Normal file
View File

@@ -0,0 +1,84 @@
import { Link } from 'react-router-dom';
import { useState } from 'react';
import { FiMenu, FiX } from 'react-icons/fi';
import logo from '../assets/lcepl_logo.png';
const Header = () => {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const navLinks = [
{ path: "/", label: "Home" },
{ path: "/about-us", label: "About Us" },
{ path: "/projects", label: "Projects" },
{ path: "/careers", label: "Careers" },
// { path: "/office", label: "Office" },
{ path: "/gallery", label: "Awards" },
{ path: "/contact-us", label: "Contact Us" },
];
return (
<header className="bg-white border-b border-gray-200 shadow-sm sticky top-0 z-50 font-sans">
<div className="max-w-7xl mx-auto px-4">
<div className="flex justify-between items-center h-20">
{/* Logo and Company Name */}
<Link to="/" className="flex items-center space-x-3 shrink-0">
<img
src={logo}
alt="LCEPL Logo"
className="h-20 w-20 object-contain"
/>
<div className="leading-tight hidden sm:block">
<h1 className="text-lg md:text-xl font-semibold text-blue-900 whitespace-nowrap">
Laxmi Civil Engineering Services Pvt Ltd
</h1>
</div>
</Link>
{/* Desktop Navigation */}
<nav className="hidden md:flex flex-wrap justify-end gap-x-4 items-center">
{navLinks.map((link) => (
<Link
key={link.path}
to={link.path}
className="relative px-3 py-2 text-base font-medium text-gray-700 hover:text-blue-800 transition-colors duration-200 group whitespace-nowrap"
>
{link.label}
<span className="absolute bottom-0 left-1/2 transform -translate-x-1/2 w-0 h-0.5 bg-blue-800 transition-all duration-300 group-hover:w-2/3"></span>
</Link>
))}
</nav>
{/* Mobile Menu Toggle */}
<button
className="md:hidden text-gray-700 focus:outline-none"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
{mobileMenuOpen ? (
<FiX className="h-6 w-6" />
) : (
<FiMenu className="h-6 w-6" />
)}
</button>
</div>
{/* Mobile Navigation */}
{mobileMenuOpen && (
<div className="md:hidden bg-white py-3 px-4 shadow-md rounded-b-lg space-y-1">
{navLinks.map((link) => (
<Link
key={link.path}
to={link.path}
className="block py-2 px-3 text-base font-medium text-gray-700 hover:text-blue-800 transition"
onClick={() => setMobileMenuOpen(false)}
>
{link.label}
</Link>
))}
</div>
)}
</div>
</header>
);
};
export default Header;

160
src/components/IndiaMap.jsx Normal file
View File

@@ -0,0 +1,160 @@
import React, { useState } from "react";
import {
ComposableMap,
Geographies,
Geography,
Marker
} from "react-simple-maps";
import { geoCentroid } from "d3-geo";
import indiaMap from "../assets/india.topo.json"; // Make sure this file path is correct
const IndiaMap = () => {
const [hoveredState, setHoveredState] = useState(null);
const projectStates = [
"Jammu and Kashmir",
"Punjab",
"Uttarakhand",
"Uttar Pradesh",
"Bihar",
"Jharkhand",
"West Bengal",
"Odisha",
"Chhattisgarh",
"Madhya Pradesh",
"Gujarat",
"Maharashtra",
"Goa",
"Karnataka",
"Telangana",
"Andhra Pradesh",
"Tamil Nadu",
"Kerala"
];
const markerCoordinates = {
"Jammu and Kashmir": [75.34, 33.75],
"Punjab": [75.5, 31.3],
"Uttarakhand": [79.0, 30.1],
"Uttar Pradesh": [80.5, 26.5],
"Bihar": [85.3, 25.6],
"Jharkhand": [85.3, 23.6],
"West Bengal": [87.9, 23.2],
"Odisha": [85.1, 20.3],
"Chhattisgarh": [81.5, 21.3],
"Madhya Pradesh": [78.6, 23.4],
"Gujarat": [71.2, 22.3],
"Maharashtra": [75.7, 19.5],
"Goa": [74.0, 15.3],
"Karnataka": [76.5, 14.5],
"Telangana": [78.4, 17.5],
"Andhra Pradesh": [80.6, 15.9],
"Tamil Nadu": [78.6, 10.5],
"Kerala": [76.2, 10.1],
};
return (
<div className="text-center mt-10">
<div className="map-wrapper">
<ComposableMap
projection="geoMercator"
width={1850} // increased width
height={1100} // increased height
projectionConfig={{ scale: 1850, center: [82.8, 22.5] }} // increased scale
>
<Geographies geography={indiaMap}>
{({ geographies }) =>
geographies.map((geo) => {
const stateName = geo.properties.st_nm;
const isHighlighted = projectStates.includes(stateName);
const centroid = geoCentroid(geo);
return (
<React.Fragment key={geo.rsmKey}>
<Geography
geography={geo}
onMouseEnter={() => setHoveredState({ name: stateName, coords: centroid })}
onMouseLeave={() => setHoveredState(null)}
style={{
default: {
fill: isHighlighted ? "#E0E0E0" : "#E0E0E0",
stroke: "#607D8B",
strokeWidth: 1,
outline: "none",
},
hover: {
fill: "#1e40af",
cursor: "pointer",
outline: "none",
},
pressed: {
fill: "#1e3a8a",
outline: "none",
},
}}
/>
</React.Fragment>
);
})
}
</Geographies>
{/* Project markers */}
{projectStates.map((state) => {
const coords = markerCoordinates[state];
if (!coords) return null;
return (
<Marker key={state} coordinates={coords}>
<circle r={12} fill="#0ba512ff" stroke="#fff" strokeWidth={2} />
<text
textAnchor="middle"
y={-12}
style={{
fontFamily: "sans-serif",
fontSize: 22,
fill: "#000",
fontWeight: "bold",
pointerEvents: "none",
}}
>
{state}
</text>
</Marker>
);
})}
{/* Tooltip/label for hovered state */}
{hoveredState?.name && hoveredState?.coords && (
<Marker coordinates={hoveredState.coords}>
<text
textAnchor="middle"
y={-15}
style={{
fontFamily: "sans-serif",
fontSize: 22,
fontWeight: "bold",
fill: "#111827",
pointerEvents: "none",
paintOrder: "stroke",
stroke: "#fff",
strokeWidth: 3,
strokeLinejoin: "round",
}}
>
{hoveredState.name}
</text>
</Marker>
)}
</ComposableMap>
</div>
</div>
);
};
export default IndiaMap;

View File

View File

@@ -0,0 +1,65 @@
import React, { useState } from "react";
const PasswordPopup = ({ title, onSubmit, onClose }) => {
const [password, setPassword] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
onSubmit(password);
};
return (
<div className="fixed inset-0 bg-black bg-opacity-40 flex items-center justify-center z-50">
<div className="bg-white w-full max-w-md rounded-lg shadow-lg p-8 animate-fadeIn">
{/* Title */}
<div className="text-center">
<h2 className="text-2xl font-bold text-gray-800">{title}</h2>
<p className="text-gray-600 mt-2">Enter the password to continue</p>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="mt-6 space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Password
</label>
<input
type="password"
placeholder="Enter password"
className="w-full px-4 py-2 border border-gray-300 rounded-md
shadow-sm focus:ring-blue-500 focus:border-blue-500"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
{/* Buttons */}
<div className="flex justify-between">
<button
type="button"
onClick={onClose}
className="px-4 py-2 bg-gray-300 text-gray-800 rounded-md hover:bg-gray-400"
>
Cancel
</button>
<button
type="submit"
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
>
Submit
</button>
</div>
</form>
</div>
</div>
);
};
export default PasswordPopup;

View File

@@ -0,0 +1,14 @@
import React from "react";
import { Navigate, useLocation } from "react-router-dom";
export default function ProtectedRoute({ children }) {
const isAdmin = sessionStorage.getItem("admin"); // session-based login
const location = useLocation(); // current path
if (!isAdmin) {
// redirect to login and remember attempted page
return <Navigate to="/admin-login" replace state={{ from: location }} />;
}
return children;
}

View File

@@ -0,0 +1,31 @@
import { FaArrowDown } from "react-icons/fa";
const ScrollDownArrow = ({ targetId }) => {
const scrollToSection = () => {
const target = document.getElementById(targetId);
if (target) {
target.scrollIntoView({ behavior: "smooth" });
}
};
return (
<div
onClick={scrollToSection}
style={{
position: "absolute",
bottom: "20px",
left: "50%",
transform: "translateX(-50%)",
cursor: "pointer",
zIndex: 50,
fontSize: "2rem",
color: "white",
animation: "bounce 1.5s infinite",
}}
>
<FaArrowDown />
</div>
);
};
export default ScrollDownArrow;

View File

@@ -0,0 +1,138 @@
import React, { useState } from "react";
import { motion } from "framer-motion";
import waterIcon from "../assets/water.png";
import stormIcon from "../assets/storm.png";
import electroIcon from "../assets/electro.png";
import realEstateIcon from "../assets/real-estate.png";
import tunnelIcon from "../assets/tunnel.png";
import roadsIcon from "../assets/roads.png";
import wastewaterIcon from "../assets/wastewater.png";
import irrigationIcon from "../assets/irrigation.png";
import renewableIcon from "../assets/renewable.png";
import "../styles/SectorsSlider.css";
const sectors = [
{
title: "Water Supply",
icon: waterIcon,
description: "LCEPL delivers end-to-end water infrastructure solutions, including 24x7 water supply networks, treatment plants, irrigation systems, wastewater management, and underground drainage for urban and rural needs. With over 1,50,000 km of pipelines laid, the company has executed major projects across multiple states, covering design, construction, commissioning, and operation of pumping stations, reservoirs, and SCADA-based automation systems.",
},
{
title: "Storm Water",
icon: stormIcon,
description: "LCEPL provides efficient storm water solutions to prevent flooding, protect infrastructure, and ensure environmental safety. Our expertise includes designing and constructing drainage systems, retention structures, and rainwater harvesting facilities. We integrate sustainable methods like permeable pavements and bioswales to manage runoff effectively. With advanced engineering and proven execution, we deliver reliable solutions for urban and industrial needs.S",
},
{
title: "Electromechanical & Instrumentation",
icon: electroIcon,
description: "LCEPL offers end-to-end solutions in pump systems—from project design and consultation to installation, operation, and maintenance of horizontal split casing, vertical turbine, and submersible pumps. The company handles complex installations, including floating pontoon setups and high-voltage turnkey projects, delivering complete electrical substation installations. Expertise spans pumping machinery up to 30,000 HP, high-voltage switchyards, transformers up to 26 MVA, and advanced surge protection systems for diverse schemes.",
},
{
title: "Real Estate / Buildings",
icon: realEstateIcon,
description: "LCEPL delivers end-to-end civil engineering solutions for residential, commercial, and industrial real estate / buildings projects. Our expertise spans land development, structural construction, and infrastructure integration with a focus on quality and timely delivery. We ensure sustainable design, robust engineering, and adherence to safety and regulatory standards to create lasting value for our clients.",
},
{
title: "Tunnel",
icon: tunnelIcon,
description: "LCEPL specializes in the design and construction of tunnels for transportation, irrigation, and utility purposes. We leverage advanced engineering techniques and modern tunneling equipment to ensure safety, precision, and durability. Our expertise covers geological assessment, structural stability, and efficient project execution, even in challenging terrains.",
},
{
title: "Roads",
icon: roadsIcon,
description: "LCEPL excels in the design, construction, and maintenance of roads and bridges, delivering high-quality infrastructure that meets all standard norms and guidelines. The company has successfully executed projects covering about 700 km of national highways, state highways, and village roads across Maharashtra, Madhya Pradesh, Chhattisgarh, and Bihar.",
},
{
title: "Wastewater / Sewerage",
icon: wastewaterIcon,
description: "LCEPL delivers sustainable wastewater management solutions, ensuring safe disposal while protecting health and the environment. The company has executed over 1,000 km of underground drainage pipelines and designed and built sewage treatment plants (STPs) of various capacities, using advanced processes such as SBR, MBR, FAL, and oxidation ponds, along with pumping stations for efficient wastewater handling.",
},
{
title: "Irrigation",
icon: irrigationIcon,
description: "LCEPL specializes in Lift Irrigation Systems, enabling water to be pumped from lower to higher elevations for agriculture, landscaping, and soil restoration in dry regions. With extensive experience in building dams, canals, and related infrastructure in collaboration with the government, the company has executed projects involving over 1,00,000 km of pipelines, pumping stations, earthen dams, barrages, weirs, and lift irrigation schemes across multiple states, supported by advanced SCADA-based automation systems.",
},
{
title: "Renewable Energy",
icon: renewableIcon,
description: "LCEPL offers high-capacity pumped storage systems, with solutions reaching up to 300 MW. The system primarily consists of reversible Francis turbines, reversible pump-turbine motor-generators operating as a combined unit, and a grid interface with speed governors and control systems.",
},
];
const SectorsSlider = () => {
const [activeIndex, setActiveIndex] = useState(0);
const goToSlide = (index) => setActiveIndex(index);
const goToPrev = () => setActiveIndex((prev) => (prev === 0 ? sectors.length - 1 : prev - 1));
const goToNext = () => setActiveIndex((prev) => (prev === sectors.length - 1 ? 0 : prev + 1));
return (
<section className="infraCont">
<div className="max-w-7xl mx-auto px-4 text-center relative z-10 pt-0">
<h2 className="text-4xl font-bold text-blue-900 mb-2 relative inline-block">
<span className="relative z-10">
Our Core Sectors
<motion.span
initial={{ scaleX: 0 }}
whileInView={{ scaleX: 1 }}
transition={{ duration: 0.6, delay: 0.3 }}
viewport={{ once: true }}
className="absolute bottom-0 left-0 w-full h-1 bg-red-600 z-0 transform origin-left"
/>
</span>
</h2>
<p className="text-xl text-gray-600 max-w-3xl mx-auto mt-2">
Explore our expertise across diverse infrastructure sectors
</p>
</div>
<div className="custom">
{sectors.map((sector, index) => (
<div key={index} className={`infraWrap ${index === activeIndex ? "active" : ""}`}>
<div className="slideTing">
<div className="sector-card">
<img src={sector.icon} alt={sector.title} className="sector-image" />
</div>
</div>
<div className="infraright">
<div className="infraInfo">
<h2>{sector.title}</h2>
<h3>
{sector.title === "Roads"
? "TRUSTED PATHWAYS TO TOMORROW"
: `REDEFINING ${sector.title.toUpperCase()} FOR THE FUTURE`}
</h3>
<p>{sector.description}</p>
</div>
</div>
</div>
))}
</div>
<div className="slider-nav">
<button onClick={goToPrev} className="nav-arrow prev" aria-label="Previous sector">
&lt;
</button>
<button onClick={goToNext} className="nav-arrow next" aria-label="Next sector">
&gt;
</button>
</div>
<div className="indicators">
{sectors.map((_, index) => (
<button
key={index}
onClick={() => goToSlide(index)}
className={`indicator ${activeIndex === index ? "active" : ""}`}
aria-label={`Go to sector ${index + 1}`}
/>
))}
</div>
</section>
);
};
export default SectorsSlider;

View File

@@ -0,0 +1,14 @@
import React from 'react';
import '../styles/AboutUs.css';
const TeamCard = ({ image, name, position }) => {
return (
<div className="team-card">
<img src={image} alt={name} className="team-photo" />
<h4>{name}</h4>
<p>{position}</p>
</div>
);
};
export default TeamCard;

View File

@@ -0,0 +1,13 @@
import React from 'react';
import '../styles/VisionCard.css'; // ✅ Adjust path if needed
const VisionCard = ({ title, text }) => {
return (
<div className="vision-card">
<h3>{title}</h3>
<p>{text}</p>
</div>
);
};
export default VisionCard;

View File

@@ -0,0 +1,23 @@
import React, { createContext, useState, useEffect } from 'react';
export const ApplicationContext = createContext();
export const ApplicationProvider = ({ children }) => {
const [applications, setApplications] = useState(() => {
const saved = localStorage.getItem('applications');
return saved ? JSON.parse(saved) : [];
});
// Save to localStorage whenever applications change
useEffect(() => {
localStorage.setItem('applications', JSON.stringify(applications));
}, [applications]);
return (
<ApplicationContext.Provider value={{ applications, setApplications }}>
{children}
</ApplicationContext.Provider>
);
};

View File

@@ -0,0 +1,39 @@
import React, { createContext, useContext, useState } from "react";
// 1. Create Context
const ProjectContext = createContext();
// 2. Custom hook to use the context
export const useProjects = () => useContext(ProjectContext);
// 3. Provider Component
export const ProjectProvider = ({ children }) => {
const [projects, setProjects] = useState([]);
// Add new project
const addProject = (project) => {
setProjects((prev) => [...prev, project]);
};
// Edit existing project by ID
const editProject = (updatedProject) => {
setProjects((prev) =>
prev.map((proj) =>
proj.id === updatedProject.id ? updatedProject : proj
)
);
};
// Delete project by ID
const deleteProject = (id) => {
setProjects((prev) => prev.filter((proj) => proj.id !== id));
};
return (
<ProjectContext.Provider
value={{ projects, addProject, editProject, deleteProject }}
>
{children}
</ProjectContext.Provider>
);
};

28
src/data/blogs.js Normal file
View File

@@ -0,0 +1,28 @@
const blogs = [
{
slug: "construction-company-in-india",
title: "Construction Company in India Complete Guide 2025",
description: "Explore top construction companies in India, their projects, and future trends.",
content: `
<h2>Introduction</h2>
<p>India is home to many leading construction companies...</p>
<h2>Top Construction Projects</h2>
<p>Infrastructure, road construction, tunnels, water supply, LCEPL, irrigation, wastewater, India, Infrastructure, Building, sewerage, building construction, road construction, water supply, water treatment plant, commercial construction...</p>
<h2>Conclusion</h2>
<p>Choosing the right construction company ensures quality and trust...</p>
`
},
{
slug: "water-treatment-plant",
title: "Importance of Water Treatment Plants",
description: "Understand the role of water treatment plants in urban development.",
content: `
<h2>Why Water Treatment is Important</h2>
<p>Clean water is essential for sustainable cities...</p>
`
}
];
export default blogs;

6
src/index.css Normal file
View File

@@ -0,0 +1,6 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
@apply font-sans;
}

20
src/index.js Normal file
View File

@@ -0,0 +1,20 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import './index.css';
import 'react-toastify/dist/ReactToastify.css';
import { ApplicationProvider } from './context/ApplicationContext';
import { ProjectProvider } from './context/ProjectContext';
ReactDOM.createRoot(document.getElementById('root')).render(
<BrowserRouter>
<ApplicationProvider>
<ProjectProvider>
<App />
</ProjectProvider>
</ApplicationProvider>
</BrowserRouter>
);

1
src/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

169
src/pages/AboutUs.jsx Normal file
View File

@@ -0,0 +1,169 @@
import React from 'react';
import CounterCard from '../components/CounterCard';
import '../styles/AboutUs.css';
import { motion } from "framer-motion";
// Import background images
import civilBg from "../assets/civil-bg.jpg";
import mechanicalBg from "../assets/mechanical-bg.jpg";
import electricalBg from "../assets/electrical-bg.jpg";
import electromechanicalBg from "../assets/electromechanical-bg.jpg";
import itBg from "../assets/it-bg.jpg";
import renewableBg from "../assets/renewable-bg.jpg"; // renamed image
// Animation variants
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: 0.2 }
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 }
};
const AboutUs = () => {
return (
<>
{/* HERO SECTION */}
<section className="about-hero">
<div className="hero-overlay">
<h1>About Us</h1>
<p className="hero-subtitle">Building the Nation with Precision & Excellence</p>
<p className="mt-4 text-white text-lg italic">Engineering the future, one project at a time.</p>
</div>
</section>
{/* SLOGAN BANNER */}
<div className="bg-gradient-to-r from-blue-400 to-red-500 text-white text-center py-4 font-semibold text-lg tracking-wide shadow-md">
Empowering Infrastructure. Enabling Progress. Elevating Lives.
</div>
{/* MAIN ABOUT SECTION */}
<section className="about-section px-6 lg:px-20 py-16">
<div className="grid md:grid-cols-2 gap-12 items-start">
{/* LEFT TEXT */}
<div>
<h2 className="text-3xl font-bold text-gray-800 mb-6">
About Laxmi Civil Engineering Services Pvt. Ltd.
</h2>
<p className="text-gray-700 leading-relaxed mb-4">
Laxmi Civil Engineering Services Pvt. Ltd. was established on <strong>15th August 1980</strong> as a partnership firm and later
converted into a Private Limited Company on <strong>31st March 2000</strong>.
</p>
<p className="text-gray-700 leading-relaxed mb-4">
For over four decades, LCEPL has been delivering exceptional engineering and construction services, making us one of the leading civil engineering solution providers in India.
</p>
<p className="text-gray-700 leading-relaxed mb-4">
We specialize in major infrastructure projects such as portable water supply, irrigation, pumping stations, electromechanical works, and operation & maintenance.
</p>
<p className="text-gray-700 leading-relaxed mb-4">
Our leadership in these unique services has allowed us to establish a strong foothold in the central, western, and southern regions of India, while also expanding into the northern region.
</p>
<p className="text-gray-700 leading-relaxed">
With a workforce of <strong>1300+ professionals</strong>, supported by advanced technology and an inspiring work environment, we continue to move forward with confidence.
</p>
</div>
{/* COUNTERS */}
<motion.div
variants={containerVariants}
initial="hidden"
animate="visible"
className="grid grid-cols-1 gap-6"
>
<motion.div variants={itemVariants}>
<CounterCard count="45+" label="Years of Experience" icon="🎖️" className="bg-white shadow-lg border border-gray-200 px-4 py-3 rounded-lg" />
</motion.div>
<motion.div variants={itemVariants}>
<CounterCard count="18+" label="Number of States" icon="🗺️" className="bg-white shadow-lg border border-gray-200 px-4 py-3 rounded-lg" />
</motion.div>
<motion.div variants={itemVariants}>
<CounterCard count="50+" label="Clients" icon="🏭" className="bg-white shadow-lg border border-gray-200 px-4 py-3 rounded-lg" />
</motion.div>
</motion.div>
</div>
</section>
{/* QUOTE BANNER */}
<div className="bg-blue-100 text-blue-800 text-center py-6 text-xl font-semibold italic tracking-wide">
Driven by innovation, guided by integrity, delivering with passion.
</div>
{/* VISION & MISSION SECTION */}
<motion.section initial={{ opacity: 0 }} whileInView={{ opacity: 1 }} transition={{ duration: 0.6 }} viewport={{ once: true }} className="relative py-20 px-4 md:px-8 lg:px-20 bg-gradient-to-br from-blue-900 to-blue-800 overflow-hidden">
<motion.div initial={{ y: 30, opacity: 0 }} whileInView={{ y: 0, opacity: 1 }} transition={{ duration: 0.6 }} viewport={{ once: true }} className="max-w-7xl mx-auto relative z-10">
<h2 className="text-4xl font-bold text-center text-white mb-16 relative pb-2">
<span className="relative inline-block">
Vision & Mission
<motion.span initial={{ scaleX: 0 }} whileInView={{ scaleX: 1 }} transition={{ duration: 0.6 }} viewport={{ once: true }} className="absolute bottom-0 left-0 w-full h-1 bg-red-500 z-0 transform origin-center" />
</span>
</h2>
<div className="grid md:grid-cols-2 gap-8 mb-16">
{/* Vision */}
<motion.div initial={{ x: -50, opacity: 0 }} whileInView={{ x: 0, opacity: 1 }} transition={{ duration: 0.5 }} viewport={{ once: true }} className="bg-white p-8 rounded-xl shadow-lg hover:shadow-xl transition-all border-l-4 border-blue-500 hover:border-blue-400">
<div className="flex items-start mb-6">
<div className="bg-blue-100 p-3 rounded-full mr-4">
<svg className="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
</div>
<div>
<h3 className="text-xl font-bold text-blue-900 mb-2">Vision</h3>
<p className="text-gray-700">To be a company at the forefront of engineering and construction, renowned for excellence, quality, performance, and reliability.</p>
</div>
</div>
</motion.div>
{/* Mission */}
<motion.div initial={{ x: 50, opacity: 0 }} whileInView={{ x: 0, opacity: 1 }} transition={{ duration: 0.5 }} viewport={{ once: true }} className="bg-white p-8 rounded-xl shadow-lg hover:shadow-xl transition-all border-l-4 border-red-500 hover:border-red-400">
<div className="flex items-start mb-6">
<div className="bg-blue-100 p-3 rounded-full mr-4">
<svg className="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<div>
<h3 className="text-xl font-bold text-blue-900 mb-2">Mission</h3>
<p className="text-gray-700">To complete every project undertaken with sincerity, excellence, and in a time-bound manner, meeting all expectations of the client.</p>
</div>
</div>
</motion.div>
</div>
</motion.div>
</motion.section>
{/* WORKING FIELDS SECTION */}
<section className="working-fields px-6 lg:px-20 py-16 bg-gray-50">
<h2 className="highlight-title text-4xl font-bold text-center mb-4 text-black">Expertise Across Multiple Sectors</h2>
<p className="text-center text-gray-600 mb-12">Our multidisciplinary approach helps us lead across multiple domains.</p>
<div className="grid sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{[
{ title: "Civil Engineering", icon: "🏗️", desc: "Urban infrastructure, roadworks, dams, WTPs, STPs, stormwater management, and structural development.", bg: civilBg },
{ title: "Mechanical Engineering", icon: "⚙️", desc: "Fabrication, installation, and maintenance of industrial systems, heavy machinery, and automated solutions including SCADA.", bg: mechanicalBg },
{ title: "Electrical Engineering", icon: "💡", desc: "Reliable installations, power distribution, and maintenance services.", bg: electricalBg },
{ title: "Electromechanical & Instrumentation", icon: "🔌", desc: "Integration of mechanical and electrical systems for pumping stations and automation.", bg: electromechanicalBg },
{ title: "Information Technology", icon: "💻", desc: "Project planning, GIS, data systems, and automation tools for smart engineering.", bg: itBg },
{ title: "Renewable Energy", icon: "☀️", desc: "Solar, wind and sustainable energy solutions with advanced engineering support.", bg: renewableBg },
].map((field, idx) => (
<div key={idx} className="relative field-card rounded-xl shadow-lg overflow-hidden hover:shadow-2xl transition-all cursor-pointer bg-cover bg-center" style={{ backgroundImage: `url(${field.bg})` }}>
<div className="absolute inset-0 bg-black/50"></div>
<div className="relative p-6 flex flex-col items-center text-center text-white z-10">
<div className="text-5xl mb-4">{field.icon}</div>
<h3 className="text-xl font-bold mb-2">{field.title}</h3>
<p>{field.desc}</p>
</div>
</div>
))}
</div>
</section>
</>
);
};
export default AboutUs;

200
src/pages/AddProjects.jsx Normal file
View File

@@ -0,0 +1,200 @@
import React, { useState, useEffect } from "react";
import axios from "axios";
import { toast } from "react-toastify";
const AddProjects = () => {
const [projects, setProjects] = useState([]);
const [sector, setSector] = useState("");
const [image, setImage] = useState(null);
const [imagePreview, setImagePreview] = useState(null);
const [editMode, setEditMode] = useState(false);
const [editProjectId, setEditProjectId] = useState(null);
const sectors = [
{ label: "Water Supply", value: "water supply" },
{ label: "Storm Water", value: "storm water" },
{ label: "Electromechanical", value: "electromechanical" },
{ label: "Real Estate / Buildings", value: "real estate / buildings" },
{ label: "Tunnel", value: "tunnel" },
{ label: "Roads", value: "roads" },
{ label: "Wastewater / Sewerage ", value: "wastewater / sewerage " },
{ label: "Irrigation", value: "irrigation" },
{ label: "Renewable Energy", value: "renewable energy" }
];
// Fetch all projects
const fetchProjects = async () => {
try {
const res = await axios.get(`${process.env.REACT_APP_API_BASE_URL}/api/projects`);
setProjects(res.data);
} catch (err) {
console.error("Error fetching projects", err);
}
};
useEffect(() => {
fetchProjects();
}, []);
// Reset the form
const resetForm = () => {
setSector("");
setImage(null);
setImagePreview(null);
setEditMode(false);
setEditProjectId(null);
};
// Handle image selection
const handleImageChange = (e) => {
const file = e.target.files[0];
setImage(file);
setImagePreview(file ? URL.createObjectURL(file) : null);
};
// Handle form submit (add or update)
const handleSubmit = async (e) => {
e.preventDefault();
if (!sector || (!editMode && !image)) {
toast.error("Sector and image are required!");
return;
}
const formData = new FormData();
formData.append("sector", sector);
if (image) formData.append("image", image);
try {
if (editMode) {
await axios.post(`${process.env.REACT_APP_API_BASE_URL}/api/projects/update/${editProjectId}`, formData);
toast.success("Project updated successfully!");
} else {
await axios.post(`${process.env.REACT_APP_API_BASE_URL}/api/projects`, formData);
toast.success("Project added successfully!");
}
resetForm();
fetchProjects();
} catch (err) {
console.error("Failed to save project", err);
toast.error("Error saving project");
}
};
// Load project into form for editing
const handleEdit = (project) => {
setSector(project.sector);
setImage(null);
setImagePreview(`${process.env.REACT_APP_API_BASE_URL}${project.image}`);
setEditMode(true);
setEditProjectId(project.id);
};
// Delete a project
const handleDelete = async (id) => {
if (!window.confirm("Are you sure you want to delete this project?")) return;
try {
await axios.delete(`${process.env.REACT_APP_API_BASE_URL}/api/projects/${id}`);
toast.success("Project deleted");
fetchProjects();
} catch (err) {
console.error("Failed to delete", err);
toast.error("Failed to delete project");
}
};
return (
<div className="p-6 min-h-screen bg-gray-50">
<h1 className="text-3xl font-bold text-blue-900 mb-6">
{editMode ? "Edit Project" : "Add New Project"}
</h1>
{/* FORM */}
<form onSubmit={handleSubmit} className="space-y-4 max-w-md mx-auto bg-white p-6 rounded-lg shadow">
<select
value={sector}
onChange={(e) => setSector(e.target.value)}
className="w-full border p-2 rounded"
>
<option value="">Select Sector</option>
{sectors.map((s) => (
<option key={s.value} value={s.value}>
{s.label}
</option>
))}
</select>
<input
type="file"
accept="image/*"
onChange={handleImageChange}
className="w-full"
/>
{imagePreview && (
<img
src={imagePreview}
alt="Preview"
className="h-48 w-full object-cover rounded"
/>
)}
<div className="flex justify-between gap-4">
<button
type="submit"
className="flex-1 bg-blue-900 text-white py-2 rounded hover:bg-blue-800"
>
{editMode ? "Update Project" : "Save Project"}
</button>
{editMode && (
<button
type="button"
onClick={resetForm}
className="flex-1 bg-gray-300 text-black py-2 rounded hover:bg-gray-400"
>
Cancel
</button>
)}
</div>
</form>
{/* PROJECT LIST */}
<div className="mt-10">
<h2 className="text-2xl font-semibold text-blue-800 mb-4">All Projects</h2>
<div className="grid md:grid-cols-3 sm:grid-cols-2 gap-6">
{projects.map((proj) => (
<div
key={proj.id}
className="bg-white p-4 rounded-xl shadow hover:shadow-lg transition-all relative"
>
<img
src={`${process.env.REACT_APP_API_BASE_URL}${proj.image}`}
alt={proj.sector}
className="h-56 w-full object-cover rounded mb-3"
/>
<p className="text-sm text-gray-700 capitalize mb-1">Sector: {proj.sector}</p>
<div className="flex justify-between mt-2">
<button
onClick={() => handleEdit(proj)}
className="text-blue-600 hover:underline"
>
Edit
</button>
<button
onClick={() => handleDelete(proj.id)}
className="text-red-600 hover:underline"
>
Delete
</button>
</div>
</div>
))}
</div>
</div>
</div>
);
};
export default AddProjects;

View File

@@ -0,0 +1,25 @@
import React from 'react';
const AdminGallery = () => {
return (
<div className="max-w-6xl mx-auto px-4 py-16">
<h2 className="text-3xl font-bold text-blue-900 mb-4">Admin - Gallery</h2>
<p className="text-gray-600 mb-6">Manage gallery images and media files here.</p>
{/* Example: Gallery cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
{/* Replace with API dynamic mapping */}
<div className="bg-gray-100 p-4 rounded-lg shadow">
<img src="https://via.placeholder.com/300" alt="Gallery 1" className="rounded-md mb-2" />
<p className="text-gray-700 font-medium">Project Image 1</p>
<div className="mt-2 flex gap-2">
<button className="bg-blue-600 text-white px-3 py-1 rounded-md">Edit</button>
<button className="bg-red-600 text-white px-3 py-1 rounded-md">Delete</button>
</div>
</div>
</div>
</div>
);
};
export default AdminGallery;

97
src/pages/AdminHR.jsx Normal file
View File

@@ -0,0 +1,97 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const AdminHR = () => {
const [submissions, setSubmissions] = useState([]);
const [loading, setLoading] = useState(true);
// Fetch HR submissions from API
useEffect(() => {
axios
.get(`${process.env.REACT_APP_API_BASE_URL}/api/hr-submissions`)
.then((res) => {
setSubmissions(res.data || []);
setLoading(false);
})
.catch((err) => {
console.error('Error fetching HR submissions:', err);
setLoading(false);
});
}, []);
const handleDelete = (id) => {
if (window.confirm('Are you sure you want to delete this submission?')) {
axios
.delete(`${process.env.REACT_APP_API_BASE_URL}/api/hr-submissions/${id}`)
.then(() => {
setSubmissions(submissions.filter((sub) => sub.id !== id));
})
.catch((err) => console.error('Error deleting submission:', err));
}
};
return (
<div className="max-w-6xl mx-auto px-4 py-16">
<h2 className="text-3xl font-bold text-blue-900 mb-4">Admin - HR</h2>
<p className="text-gray-600 mb-6">Manage HR contacts and career submissions.</p>
{loading ? (
<p className="text-gray-500">Loading submissions...</p>
) : submissions.length === 0 ? (
<p className="text-gray-500">No submissions found.</p>
) : (
<div className="overflow-x-auto">
<table className="w-full border-collapse border border-gray-200">
<thead>
<tr className="bg-blue-100 text-left">
<th className="p-2 border">ID</th>
<th className="p-2 border">Name</th>
<th className="p-2 border">Email</th>
<th className="p-2 border">Phone</th>
<th className="p-2 border">Resume</th>
<th className="p-2 border">Actions</th>
</tr>
</thead>
<tbody>
{submissions.map((sub, index) => (
<tr key={sub.id || index} className="hover:bg-gray-50">
<td className="p-2 border">{sub.id}</td>
<td className="p-2 border">{sub.name}</td>
<td className="p-2 border">{sub.email}</td>
<td className="p-2 border">{sub.phone}</td>
<td className="p-2 border">
{sub.resume ? (
<a href={sub.resume} target="_blank" rel="noopener noreferrer" className="text-blue-600 underline">
View Resume
</a>
) : (
'N/A'
)}
</td>
<td className="p-2 border flex space-x-2">
<a
href={sub.resume || '#'}
target="_blank"
rel="noopener noreferrer"
className="bg-green-600 text-white px-3 py-1 rounded-md"
>
View
</a>
<button
onClick={() => handleDelete(sub.id)}
className="bg-red-600 text-white px-3 py-1 rounded-md"
>
Delete
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
};
export default AdminHR;

45
src/pages/AdminLogin.jsx Normal file
View File

@@ -0,0 +1,45 @@
import React, { useState } from "react";
import { useNavigate, useLocation } from "react-router-dom";
export default function AdminLogin() {
const [password, setPassword] = useState("");
const navigate = useNavigate();
const location = useLocation();
const from = location.state?.from?.pathname || "/admin"; // redirect after login
const handleLogin = (e) => {
e.preventDefault();
if (password === "Laxmi@#$2026") {
sessionStorage.setItem("admin", "true"); // session-based login
navigate(from, { replace: true });
} else {
alert("Wrong password");
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<form
onSubmit={handleLogin}
className="bg-white p-8 rounded shadow-md w-full max-w-sm"
>
<h2 className="text-xl font-bold mb-4">LCEPL ADMIN LOGIN</h2>
<input
type="password"
placeholder="Enter LCEPL ADMIN password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full border px-3 py-2 rounded mb-4"
required
/>
<button
type="submit"
className="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700"
>
Login
</button>
</form>
</div>
);
}

8
src/pages/AdminPanel.jsx Normal file
View File

@@ -0,0 +1,8 @@
import React from "react";
import { Outlet } from "react-router-dom";
const AdminPanel = () => {
return <Outlet />;
};
export default AdminPanel;

View File

@@ -0,0 +1,38 @@
import React from 'react';
const AdminProjects = () => {
return (
<div className="max-w-6xl mx-auto px-4 py-16">
<h2 className="text-3xl font-bold text-blue-900 mb-4">Admin - Projects</h2>
<p className="text-gray-600 mb-6">Manage all your projects here.</p>
{/* Example: Projects Table */}
<table className="w-full border-collapse border border-gray-200">
<thead>
<tr className="bg-blue-100 text-left">
<th className="p-2 border">ID</th>
<th className="p-2 border">Title</th>
<th className="p-2 border">Location</th>
<th className="p-2 border">Status</th>
<th className="p-2 border">Actions</th>
</tr>
</thead>
<tbody>
{/* Replace with dynamic map from API */}
<tr>
<td className="p-2 border">1</td>
<td className="p-2 border">Bridge Project</td>
<td className="p-2 border">Mumbai</td>
<td className="p-2 border">Ongoing</td>
<td className="p-2 border">
<button className="bg-blue-600 text-white px-3 py-1 rounded-md mr-2">Edit</button>
<button className="bg-red-600 text-white px-3 py-1 rounded-md">Delete</button>
</td>
</tr>
</tbody>
</table>
</div>
);
};
export default AdminProjects;

414
src/pages/Apply.jsx Normal file
View File

@@ -0,0 +1,414 @@
import React, { useState, useContext, useEffect } from 'react';
import axios from 'axios';
import { ApplicationContext } from '../context/ApplicationContext';
const Apply = () => {
const { applications, setApplications } = useContext(ApplicationContext);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [credentials, setCredentials] = useState({ id: '', password: '' });
const [formData, setFormData] = useState({
positionName: '',
qualification: '',
experience: '',
location: '',
expertise: '',
skills: [],
numberOfOpenings: '',
jobDescription: '',
postingDate: new Date().toISOString().split('T')[0],
isActive: true,
closingDate: '',
});
const [showApplications, setShowApplications] = useState(false);
const [editIndex, setEditIndex] = useState(null);
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
axios
.get(`${process.env.REACT_APP_API_BASE_URL}/api/jobs`)
.then((res) => {
const currentDate = new Date();
const updatedJobs = res.data.map(job => {
if (job.closingDate && new Date(job.closingDate) < currentDate) {
return { ...job, isActive: false };
}
return job;
});
setApplications(updatedJobs);
})
.catch((err) => console.error('Error fetching jobs:', err));
}, [setApplications]);
const handleLogin = (e) => {
e.preventDefault();
if (credentials.id === 'HR' && credentials.password === 'HR@#$2026') {
setIsAuthenticated(true);
} else {
alert('Invalid credentials. Please try again.');
}
};
const handleFormChange = (e) => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
[name]: value,
...(name === 'positionName' ? { skills: [] } : {}),
}));
};
const resetForm = () => {
setFormData({
positionName: '',
qualification: '',
experience: '',
location: '',
expertise: '',
skills: [],
numberOfOpenings: '',
jobDescription: '',
postingDate: new Date().toISOString().split('T')[0],
isActive: true,
closingDate: '',
});
setEditIndex(null);
};
const handleApplicationSubmit = (e) => {
e.preventDefault();
const currentDate = new Date();
const closingDate = formData.closingDate ? new Date(formData.closingDate) : null;
const isActive = closingDate ? closingDate >= currentDate : formData.isActive;
const jobData = {
...formData,
isActive
};
if (editIndex !== null) {
const jobId = applications[editIndex].id;
axios
.put(`${process.env.REACT_APP_API_BASE_URL}/api/jobs/${jobId}`, jobData)
.then((res) => {
const updatedApps = [...applications];
updatedApps[editIndex] = res.data.job;
setApplications(updatedApps);
resetForm();
})
.catch((err) => console.error('Error updating job:', err));
} else {
axios
.post(`${process.env.REACT_APP_API_BASE_URL}/api/jobs`, jobData)
.then((res) => {
setApplications([...applications, res.data.job]);
resetForm();
})
.catch((err) => console.error('Error creating job:', err));
}
};
const handleDelete = (index) => {
const jobId = applications[index].id;
if (window.confirm('Are you sure you want to delete this job posting?')) {
axios
.delete(`${process.env.REACT_APP_API_BASE_URL}/api/jobs/${jobId}`)
.then(() => {
const updated = [...applications];
updated.splice(index, 1);
setApplications(updated);
})
.catch((err) => console.error('Error deleting job:', err));
}
};
const handleEdit = (index) => {
setEditIndex(index);
setFormData(applications[index]);
window.scrollTo({ top: 0, behavior: 'smooth' });
};
const toggleActiveStatus = (index) => {
const job = applications[index];
const updatedJob = { ...job, isActive: !job.isActive };
axios
.put(`${process.env.REACT_APP_API_BASE_URL}/api/jobs/${job.id}`, updatedJob)
.then((res) => {
const updatedApps = [...applications];
updatedApps[index] = res.data.job;
setApplications(updatedApps);
})
.catch((err) => console.error('Error updating job status:', err));
};
const filteredApplications = applications.filter((app) =>
app.positionName.toLowerCase().includes(searchTerm.toLowerCase()) ||
app.location.toLowerCase().includes(searchTerm.toLowerCase()) ||
(Array.isArray(app.skills) && app.skills.some((skill) => skill.toLowerCase().includes(searchTerm.toLowerCase())))
);
const handleLogout = () => {
setIsAuthenticated(false);
setCredentials({ id: '', password: '' });
resetForm();
};
return (
<div className="min-h-screen bg-gray-50 py-8 px-4 sm:px-6 lg:px-8">
{!isAuthenticated ? (
<div className="max-w-md mx-auto bg-white p-8 rounded-xl shadow-lg border border-gray-200">
<div className="text-center mb-6">
<h2 className="text-2xl font-bold text-gray-800">HR Portal Login</h2>
<p className="text-gray-600 mt-2">Enter your credentials to access the job application system</p>
</div>
<form onSubmit={handleLogin} className="space-y-6">
<div>
<label htmlFor="id" className="block text-sm font-medium text-gray-700 mb-1">
Username
</label>
<input
id="id"
type="text"
placeholder="Username"
autoComplete="off"
className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
value={credentials.id}
onChange={(e) => setCredentials({ ...credentials, id: e.target.value })}
required
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
Password
</label>
<input
id="password"
type="password"
placeholder="Password"
autoComplete="new-password"
className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
value={credentials.password}
onChange={(e) => setCredentials({ ...credentials, password: e.target.value })}
required
/>
</div>
<div>
<button
type="submit"
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700"
>
Sign in
</button>
</div>
</form>
</div>
) : (
<div className="max-w-7xl mx-auto">
<div className="flex justify-between items-center mb-8">
<h1 className="text-3xl font-bold text-gray-900">Job Application Management</h1>
<button
onClick={handleLogout}
className="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-colors text-sm font-medium"
>
Logout
</button>
</div>
{/* Job Form */}
<div className="bg-white shadow-lg rounded-lg overflow-hidden mb-8">
<div className="p-6 border-b border-gray-200">
<h2 className="text-xl font-semibold text-gray-800">
{editIndex !== null ? 'Edit Job Posting' : 'Create New Job Posting'}
</h2>
</div>
<form onSubmit={handleApplicationSubmit} className="p-6 grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Position */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Position *</label>
<input
type="text"
name="positionName"
placeholder="Enter position name"
className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm"
value={formData.positionName}
onChange={handleFormChange}
required
/>
</div>
{/* Qualification */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Qualification *</label>
<input
type="text"
name="qualification"
placeholder="e.g. Bachelor's Degree"
className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm"
value={formData.qualification}
onChange={handleFormChange}
required
/>
</div>
{/* Experience */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Experience *</label>
<input
type="text"
name="experience"
placeholder="e.g. 3-5 years"
className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm"
value={formData.experience}
onChange={handleFormChange}
required
/>
</div>
{/* Location */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Location *</label>
<input
type="text"
name="location"
placeholder="e.g. New York, NY"
className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm"
value={formData.location}
onChange={handleFormChange}
required
/>
</div>
{/* Number of Openings */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Number of Openings *</label>
<input
type="number"
name="numberOfOpenings"
placeholder="e.g. 2"
className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm"
value={formData.numberOfOpenings}
onChange={handleFormChange}
required
min="1"
/>
</div>
{/* Posting Date */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Posting Date *</label>
<input
type="date"
name="postingDate"
className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm"
value={formData.postingDate}
onChange={handleFormChange}
required
/>
</div>
{/* Closing Date */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Closing Date (optional)</label>
<input
type="date"
name="closingDate"
className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm"
value={formData.closingDate}
onChange={handleFormChange}
min={formData.postingDate}
/>
<p className="mt-1 text-xs text-gray-500">
If set, position will automatically deactivate after this date
</p>
</div>
{/* Active Status */}
<div className="flex items-center">
<input
type="checkbox"
id="isActive"
name="isActive"
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
checked={formData.isActive}
onChange={(e) => setFormData({...formData, isActive: e.target.checked})}
disabled={!!formData.closingDate}
/>
<label htmlFor="isActive" className="ml-2 block text-sm text-gray-700">
Active Position
</label>
</div>
{/* Skills Multi-select */}
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-1">Required Skills *</label>
<input
type="text"
name="skills"
placeholder="Enter skills separated by commas (e.g. AutoCAD, Billing, Teamwork)"
className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm"
value={formData.skills.join(", ")}
onChange={(e) =>
setFormData({
...formData,
skills: e.target.value.split(",").map((s) => s.trim())
})
}
/>
{formData.skills.length > 0 && (
<p className="mt-2 text-sm text-gray-500">
Selected: <span className="font-medium">{formData.skills.join(', ')}</span>
</p>
)}
</div>
{/* Job Description */}
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-1">Job Description *</label>
<textarea
name="jobDescription"
placeholder="Detailed description of the job..."
className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm h-32"
value={formData.jobDescription}
onChange={handleFormChange}
required
/>
</div>
{/* Form Buttons */}
<div className="md:col-span-2 flex justify-end space-x-3">
{editIndex !== null && (
<button
type="button"
onClick={resetForm}
className="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white"
>
Cancel
</button>
)}
<button
type="submit"
className="px-4 py-2 border border-transparent rounded-md text-sm font-medium text-white bg-blue-600 hover:bg-blue-700"
>
{editIndex !== null ? 'Update Job Posting' : 'Create Job Posting'}
</button>
</div>
</form>
</div>
{/* Job Postings List */}
{/* Keep the table as before, just remove Salary column if needed */}
</div>
)}
</div>
);
};
export default Apply;

24
src/pages/BlogDetail.jsx Normal file
View File

@@ -0,0 +1,24 @@
import { useParams } from "react-router-dom";
import { Helmet } from "react-helmet";
import blogs from "../data/blogs";
function BlogDetail() {
const { slug } = useParams();
const blog = blogs.find((b) => b.slug === slug);
if (!blog) return <h2>Blog not found</h2>;
return (
<div className="p-6">
<Helmet>
<title>{blog.title}</title>
<meta name="description" content={blog.description} />
</Helmet>
<h1>{blog.title}</h1>
<div dangerouslySetInnerHTML={{ __html: blog.content }} />
</div>
);
}
export default BlogDetail;

0
src/pages/BlogList.jsx Normal file
View File

693
src/pages/Careers.jsx Normal file
View File

@@ -0,0 +1,693 @@
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import careersBanner from '../assets/careers-banner.jpg';
import careerImg from '../assets/career.jpg';
const Careers = () => {
const navigate = useNavigate();
const [jobs, setJobs] = useState([]);
const [selectedIndex, setSelectedIndex] = useState(null);
const [showContactForm, setShowContactForm] = useState(false);
const [formData, setFormData] = useState({
fullName: '',
email: '',
phone: '',
resume: null,
});
const [loading, setLoading] = useState(false);
const [jobsLoading, setJobsLoading] = useState(true);
const [jobsError, setJobsError] = useState(null);
const [searchFilters, setSearchFilters] = useState({
position: '',
skills: '',
location: ''
});
const [filteredJobs, setFilteredJobs] = useState([]);
const [activeOnly, setActiveOnly] = useState(true);
// useEffect(() => {
// const handleShortcut = (e) => {
// if (e.altKey && e.key.toLowerCase() === "n") {
// navigate("/Apply");
// }
// };
// document.addEventListener("keydown", handleShortcut);
// // Cleanup event listener on unmount
// return () => {
// document.removeEventListener("keydown", handleShortcut);
// };
// }, [navigate]);
// Fetch jobs from backend API
useEffect(() => {
const fetchJobs = async () => {
try {
setJobsLoading(true);
const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/api/jobs`);
if (!response.ok) {
throw new Error(`Failed to fetch jobs: ${response.status}`);
}
const jobsData = await response.json();
setJobs(jobsData);
setJobsError(null);
} catch (error) {
console.error("Error fetching jobs:", error);
setJobsError("Failed to load job openings. Please try again later.");
} finally {
setJobsLoading(false);
}
};
fetchJobs();
}, []);
// Filter jobs based on search criteria
useEffect(() => {
const filtered = jobs.filter(job => {
// Check position filter
const positionMatch = searchFilters.position === '' ||
job.positionName?.toLowerCase().includes(searchFilters.position.toLowerCase());
// Check skills filter
const skillsMatch = searchFilters.skills === '' ||
(Array.isArray(job.skills) &&
job.skills.some(skill =>
skill.toLowerCase().includes(searchFilters.skills.toLowerCase())
));
// Check location filter
const locationMatch = searchFilters.location === '' ||
job.location?.toLowerCase().includes(searchFilters.location.toLowerCase());
// Check active status
const isActive = !job.closingDate || new Date(job.closingDate) >= new Date();
return positionMatch && skillsMatch && locationMatch &&
(!activeOnly || (activeOnly && job.isActive && isActive));
});
setFilteredJobs(filtered);
}, [jobs, searchFilters, activeOnly]);
const getDaysAgo = (dateString) => {
if (!dateString) return 'Recently';
const postedDate = new Date(dateString);
const currentDate = new Date();
const timeDiff = currentDate - postedDate;
const daysDiff = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
if (daysDiff === 0) return 'Today';
if (daysDiff === 1) return '1 day ago';
if (daysDiff < 7) return `${daysDiff} days ago`;
if (daysDiff < 30) {
const weeks = Math.floor(daysDiff / 7);
return weeks === 1 ? '1 week ago' : `${weeks} weeks ago`;
}
if (daysDiff < 365) {
const months = Math.floor(daysDiff / 30);
return months === 1 ? '1 month ago' : `${months} months ago`;
}
const years = Math.floor(daysDiff / 365);
return years === 1 ? '1 year ago' : `${years} years ago`;
};
const handleToggle = (index) => {
setSelectedIndex((prevIndex) => (prevIndex === index ? null : index));
};
// const handleContactClick = () => {
// setShowContactForm(!showContactForm);
// };
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
[name]: value,
}));
};
const handleFileChange = (e) => {
setFormData((prev) => ({
...prev,
resume: e.target.files[0] || null,
}));
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!formData.resume) {
alert("Please upload your resume.");
return;
}
setLoading(true);
try {
const data = new FormData();
data.append("fullName", formData.fullName);
data.append("email", formData.email);
data.append("phone", formData.phone);
data.append("resume", formData.resume);
const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/api/careers/contact`, {
method: "POST",
body: data,
});
const result = await response.json();
if (response.ok) {
alert(result.message || "Thank you for contacting our HR team. We will get back to you soon!");
setFormData({
fullName: "",
email: "",
phone: "",
resume: null,
});
setShowContactForm(false);
} else {
alert(result.error || "Failed to submit the form. Please try again.");
}
} catch (error) {
console.error("Error submitting form:", error);
alert("Server error. Please try again later.");
}
setLoading(false);
};
const benefits = [
{ title: "Competitive Compensation", icon: "💰", description: "Industry-leading salaries and performance bonuses" },
{ title: "Professional Growth", icon: "📈", description: "Continuous learning opportunities and career advancement paths" },
{ title: "Work-Life Balance", icon: "⚖️", description: "Flexible work arrangements and generous leave policies" },
{ title: "Health & Wellness", icon: "🏥", description: "Comprehensive health insurance and wellness programs" },
{ title: "Innovative Culture", icon: "💡", description: "Work with cutting-edge technologies and industry experts" },
// { title: "Global Impact", icon: "🌍", description: "Opportunity to work on projects with international reach" },
];
return (
<div className="bg-white text-gray-900 min-h-screen">
{/* Hero Banner */}
<div
className="relative w-full h-[60vh] bg-center bg-cover flex items-center justify-center"
style={{ backgroundImage: `url(${careersBanner})` }}
>
<div className="absolute inset-0 bg-gradient-to-r from-blue-900/80 to-blue-700/80 z-0" />
<div className="relative z-10 text-center px-4 text-white max-w-4xl">
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold mb-4 drop-shadow-lg">
Build Your Future With Us
</h1>
<p className="text-lg md:text-2xl font-medium drop-shadow mb-8">
Shape India's infrastructure landscape while advancing your professional journey.
</p>
<button
onClick={() => document.getElementById('openings').scrollIntoView({ behavior: 'smooth' })}
className="bg-white text-blue-900 px-8 py-3 rounded-md font-bold hover:bg-gray-100 transition shadow-lg"
>
Explore Opportunities
</button>
</div>
</div>
{/* Why Join Us Section */}
<div className="bg-gray-50 py-16">
<div className="max-w-6xl mx-auto px-4">
<h2 className="text-3xl font-bold text-blue-900 mb-2 text-center">
Why Build Your Career With Us?
</h2>
<p className="text-gray-600 text-center max-w-3xl mx-auto mb-12">
We're committed to fostering an environment where professionals can thrive, innovate,
and make meaningful contributions to India's infrastructure development.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{benefits.map((benefit, index) => (
<div key={index} className="bg-white p-6 rounded-lg shadow-md hover:shadow-xl transition">
<div className="text-4xl mb-4">{benefit.icon}</div>
<h3 className="text-xl font-semibold text-blue-800 mb-2">{benefit.title}</h3>
<p className="text-gray-600">{benefit.description}</p>
</div>
))}
</div>
</div>
</div>
{/* Our Culture Section */}
<div className="py-16 bg-white">
<div className="max-w-6xl mx-auto px-4">
<div className="flex flex-col lg:flex-row gap-12 items-center">
<div className="lg:w-1/2">
<h2 className="text-3xl font-bold text-blue-900 mb-6">Our Professional Culture</h2>
<p className="text-gray-700 mb-4 leading-relaxed">
At our company, we cultivate a culture of excellence, collaboration, and continuous
improvement. We believe that our people are our greatest asset, and we invest in their
professional development.
</p>
<p className="text-gray-700 mb-6 leading-relaxed">
Our teams work on challenging projects that push the boundaries of infrastructure
development, while maintaining the highest standards of professional ethics and quality.
</p>
<div className="flex flex-wrap gap-4">
<span className="bg-blue-100 text-blue-800 px-4 py-2 rounded-full text-sm font-medium">Innovation</span>
<span className="bg-blue-100 text-blue-800 px-4 py-2 rounded-full text-sm font-medium">Integrity</span>
<span className="bg-blue-100 text-blue-800 px-4 py-2 rounded-full text-sm font-medium">Excellence</span>
<span className="bg-blue-100 text-blue-800 px-4 py-2 rounded-full text-sm font-medium">Collaboration</span>
</div>
</div>
<div className="lg:w-1/2 bg-gray-100 rounded-xl overflow-hidden">
<img
src={careerImg}
alt="Company Culture"
className="w-full h-80 object-cover"
/>
</div>
</div>
</div>
</div>
{/* Current Openings Section */}
<div id="openings" className="max-w-6xl mx-auto px-4 py-16">
<div className="text-center mb-12">
<h2 className="text-3xl font-bold text-blue-900 mb-2">
Current Professional Opportunities
</h2>
<p className="text-gray-600 max-w-2xl mx-auto">
Explore our current openings and find the perfect position to advance your professional career.
</p>
</div>
{/* Loading state */}
{jobsLoading && (
<div className="text-center py-12">
<div className="inline-block animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mb-4"></div>
<p className="text-gray-600">Loading job openings...</p>
</div>
)}
{/* Error state */}
{jobsError && (
<div className="text-center py-12 bg-red-50 rounded-lg">
<p className="text-red-600 mb-4">{jobsError}</p>
<button
onClick={() => window.location.reload()}
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md font-medium"
>
Try Again
</button>
</div>
)}
{/* Jobs listing */}
{!jobsLoading && !jobsError && (
<>
{/* Enhanced Search Filters */}
<div className="mb-8 grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="relative">
<input
type="text"
placeholder="Filter by position..."
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
value={searchFilters.position}
onChange={(e) => setSearchFilters({...searchFilters, position: e.target.value})}
/>
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg className="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
</div>
<div className="relative">
<input
type="text"
placeholder="Filter by skills..."
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
value={searchFilters.skills}
onChange={(e) => setSearchFilters({...searchFilters, skills: e.target.value})}
/>
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg className="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
</div>
</div>
<div className="relative">
<input
type="text"
placeholder="Filter by location..."
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
value={searchFilters.location}
onChange={(e) => setSearchFilters({...searchFilters, location: e.target.value})}
/>
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg className="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</div>
</div>
<div className="md:col-span-3 flex justify-between items-center">
<div className="flex items-center">
<input
type="checkbox"
id="activeOnly"
checked={activeOnly}
onChange={() => setActiveOnly(!activeOnly)}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label htmlFor="activeOnly" className="ml-2 text-sm text-gray-700">
Show active positions only
</label>
</div>
{(searchFilters.position || searchFilters.skills || searchFilters.location) && (
<button
onClick={() => setSearchFilters({ position: '', skills: '', location: '' })}
className="text-blue-600 hover:text-blue-800 font-medium text-sm"
>
Clear all filters
</button>
)}
</div>
</div>
{filteredJobs.length > 0 ? (
<div className="grid grid-cols-1 gap-6">
{filteredJobs.map((job, index) => {
const closingDate = job.closingDate ? new Date(job.closingDate) : null;
const isExpired = closingDate ? new Date() > closingDate : false;
const actualStatus = isExpired ? false : job.isActive;
return (
<div
key={job.id}
className={`bg-white rounded-xl border border-gray-200 shadow-sm hover:shadow-md transition p-6 relative overflow-hidden ${!actualStatus ? 'opacity-80' : ''}`}
>
<div className={`absolute top-0 left-0 w-1 h-full ${actualStatus ? 'bg-blue-700' : 'bg-gray-400'}`}></div>
<div className="absolute -left-6 top-6 text-5xl text-blue-100 font-extrabold opacity-30 select-none">
{String(index + 1).padStart(2, '0')}
</div>
<div className="flex flex-col md:flex-row justify-between md:items-center mb-4 pl-4">
<div>
<h3 className="text-blue-900 font-bold text-xl md:text-2xl">
{job.positionName}
</h3>
<div className="flex flex-wrap gap-2 mt-2">
<span className="bg-blue-100 text-blue-800 px-3 py-1 rounded-full text-xs font-medium">
{job.location}
</span>
<span className="bg-green-100 text-green-800 px-3 py-1 rounded-full text-xs font-medium">
{job.experience}
</span>
<span className="bg-purple-100 text-purple-800 px-3 py-1 rounded-full text-xs font-medium">
{getDaysAgo(job.postingDate)}
</span>
{!actualStatus && (
<span className="bg-red-100 text-red-800 px-3 py-1 rounded-full text-xs font-medium">
Position Closed
</span>
)}
</div>
</div>
<div className="flex gap-2 mt-4 md:mt-0">
<button
onClick={() => handleToggle(index)}
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md font-medium shadow transition flex items-center gap-2 text-sm"
>
{selectedIndex === index ? (
<>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 15l7-7 7 7" />
</svg>
Hide
</>
) : (
<>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
Details
</>
)}
</button>
{actualStatus && (
<button
onClick={() => navigate('/job-application', { state: { jobId: job.id } })}
className="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md font-medium shadow transition flex items-center gap-2 text-sm"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
Apply
</button>
)}
</div>
</div>
{selectedIndex === index && (
<div className="bg-gray-50 p-5 rounded-md border border-blue-100 mt-4 ml-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h4 className="font-semibold text-blue-800 mb-3">Position Overview</h4>
<div className="space-y-4 text-sm text-gray-700">
<div>
<p className="font-medium text-gray-500">Qualification</p>
<p>{job.qualification || 'Not specified'}</p>
</div>
<div>
<p className="font-medium text-gray-500">Experience</p>
<p>{job.experience || 'Not specified'}</p>
</div>
<div>
<p className="font-medium text-gray-500">Location</p>
<p>{job.location || 'Not specified'}</p>
</div>
<div>
<p className="font-medium text-gray-500">Posted</p>
<p>{getDaysAgo(job.postingDate)}</p>
</div>
{job.closingDate && (
<div>
<p className="font-medium text-gray-500">Closing Date</p>
<p>{new Date(job.closingDate).toLocaleDateString()}</p>
</div>
)}
</div>
</div>
<div>
<h4 className="font-semibold text-blue-800 mb-3">Position Details</h4>
<div className="space-y-4 text-sm text-gray-700">
<div>
<p className="font-medium text-gray-500">Openings</p>
<p>{job.numberOfOpenings || '1'}</p>
</div>
<div>
<p className="font-medium text-gray-500">Key Skills</p>
<div className="flex flex-wrap gap-1 mt-1">
{Array.isArray(job.skills) && job.skills.length > 0 ? (
job.skills.map((skill, i) => (
<span
key={i}
className="inline-flex px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800"
>
{skill}
</span>
))
) : (
<span className="text-sm text-gray-500">Not specified</span>
)}
</div>
</div>
</div>
</div>
<div className="md:col-span-2">
<h4 className="font-semibold text-blue-800 mb-3">Job Description</h4>
<div className="prose prose-sm max-w-none text-gray-700">
<p>{job.jobDescription || 'No description provided.'}</p>
</div>
</div>
</div>
{actualStatus && (
<div className="flex justify-end mt-6">
<button
onClick={() => navigate('/job-application', { state: { jobId: job.id } })}
className="bg-green-600 hover:bg-green-700 text-white px-8 py-3 rounded-md font-medium shadow-md transition flex items-center gap-2"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
Apply for this Position
</button>
</div>
)}
</div>
)}
</div>
);
})}
</div>
) : (
!jobsLoading && (
<div className="text-center py-12 bg-gray-50 rounded-lg">
<p className="text-gray-600">
{jobs.length === 0
? "No positions are currently available. Please check back later."
: "No positions match your current filters."}
</p>
{(searchFilters.position || searchFilters.skills || searchFilters.location) && (
<button
onClick={() => {
setSearchFilters({ position: '', skills: '', location: '' });
setActiveOnly(true);
}}
className="mt-4 text-blue-600 hover:text-blue-800 font-medium"
>
Clear all filters
</button>
)}
</div>
)
)}
</>
)}
</div>
{/* Call to Action - Contact HR Section (Kept exactly as before) */}
<div className="bg-blue-900 text-white py-16">
<div className="max-w-4xl mx-auto px-4 text-center">
<h2 className="text-3xl font-bold mb-4">Ready to Build Your Professional Future?</h2>
<p className="text-blue-100 mb-8 text-lg">
Even if you don't see the perfect role today, we're always interested in connecting with talented professionals.
</p>
{/* <div className="flex flex-col sm:flex-row justify-center gap-4">
<button
onClick={handleContactClick}
className="bg-white text-blue-900 px-8 py-3 rounded-md font-bold hover:bg-gray-100 transition shadow-lg"
>
Stay connected with us
</button>
</div> */}
<div className="mt-8 bg-white rounded-lg shadow-xl p-6 max-w-2xl mx-auto text-left text-gray-800">
<h3 className="text-2xl font-bold text-blue-900 mb-4">HR Contact Form</h3>
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label htmlFor="fullName" className="block text-gray-700 font-medium mb-2">
Full Name <span className="text-red-500">*</span>
</label>
<input
type="text"
id="fullName"
name="fullName"
value={formData.fullName}
onChange={handleInputChange}
className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<div className="mb-4">
<label htmlFor="email" className="block text-gray-700 font-medium mb-2">
Email <span className="text-red-500">*</span>
</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleInputChange}
className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<div className="mb-4">
<label htmlFor="phone" className="block text-gray-700 font-medium mb-2">
Phone Number
</label>
<input
type="tel"
id="phone"
name="phone"
value={formData.phone}
onChange={handleInputChange}
className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="mb-6">
<label htmlFor="resume" className="block text-gray-700 font-medium mb-2">
Upload Resume (PDF) <span className="text-red-500">*</span>
</label>
<input
type="file"
id="resume"
name="resume"
onChange={handleFileChange}
className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
accept=".pdf,.doc,.docx"
required
/>
</div>
<div className="flex justify-end gap-4">
{/* <button
type="button"
onClick={handleContactClick}
className="px-6 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-100 transition"
disabled={loading}
>
Cancel
</button> */}
<button
type="submit"
className={`px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition flex items-center justify-center gap-2 ${loading ? 'opacity-50 cursor-not-allowed' : ''}`}
disabled={loading}
>
{loading && (
<svg
className="animate-spin h-5 w-5 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8v8H4z"
></path>
</svg>
)}
Submit
</button>
</div>
</form>
</div>
</div>
</div>
</div>
);
};
export default Careers;

245
src/pages/ContactUs.jsx Normal file
View File

@@ -0,0 +1,245 @@
import React, { useState } from "react";
import "../styles/ContactUs.css";
import contactHeroImg from "../assets/contact-hero.jpg";
import centerImg from "../assets/contact-center.jpg";
const ContactUs = () => {
const [formData, setFormData] = useState({
name: "",
email: "",
contact: "",
message: "",
});
const [formErrors, setFormErrors] = useState({
name: "",
email: "",
contact: "",
message: "",
});
const [loading, setLoading] = useState(false);
const [notice, setNotice] = useState(null);
const API_BASE = (process.env.REACT_APP_API_BASE_URL || "").replace(/\/+$/, "");
const handleChange = (e) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
// Clear error when user starts typing
if (formErrors[name]) {
setFormErrors((prev) => ({ ...prev, [name]: "" }));
}
};
const validateForm = () => {
const errors = {};
let isValid = true;
// Name validation
if (!formData.name.trim()) {
errors.name = "Name is required";
isValid = false;
} else if (formData.name.trim().length < 2) {
errors.name = "Name must be at least 2 characters";
isValid = false;
}
// Email validation
if (!formData.email.trim()) {
errors.email = "Email is required";
isValid = false;
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
errors.email = "Please enter a valid email address";
isValid = false;
}
// Contact validation
const digits = (formData.contact || "").replace(/\D/g, "");
if (!formData.contact.trim()) {
errors.contact = "Contact number is required";
isValid = false;
} else if (digits.length < 7) {
errors.contact = "Please enter a valid contact number (at least 7 digits)";
isValid = false;
}
// Message validation
if (!formData.message.trim()) {
errors.message = "Message is required";
isValid = false;
} else if (formData.message.trim().length < 10) {
errors.message = "Message must be at least 10 characters";
isValid = false;
}
setFormErrors(errors);
return isValid;
};
const handleSubmit = async (e) => {
e.preventDefault();
setNotice(null);
if (!validateForm()) {
setNotice({ type: "error", text: "Please correct the errors in the form." });
return;
}
setLoading(true);
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 20000);
try {
const res = await fetch(`${API_BASE}/contact`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(formData),
signal: controller.signal,
});
const data = await res.json().catch(() => ({}));
if (res.ok) {
setNotice({ type: "success", text: data.message || "Message sent successfully! We'll get back to you soon." });
setFormData({ name: "", email: "", contact: "", message: "" });
} else {
setNotice({
type: "error",
text: data?.error || `Something went wrong (HTTP ${res.status}). Please try again.`,
});
}
} catch (err) {
if (err.name === "AbortError") {
setNotice({ type: "error", text: "Request timed out. Please try again." });
} else {
setNotice({ type: "error", text: "Network error. Please check your connection and try again." });
}
} finally {
clearTimeout(timeoutId);
setLoading(false);
}
};
return (
<div className="contact-page">
<div
className="contact-hero"
style={{ backgroundImage: `url(${contactHeroImg})` }}
role="img"
aria-label="Contact page hero"
>
<div className="contact-overlay">
<h1>CONTACT US</h1>
</div>
</div>
<div className="contact-heading">
<h2>LET'S RISE TOGETHER</h2>
<p className="subheading">We have the highest of ambitions and are here to support you rise higher!</p>
<p className="description">
Whether you are a client looking for a solution or an associate looking for a partner, feel free to connect
with us.
</p>
</div>
<div className="contact-content">
<div className="contact-info">
<h3>MEET US</h3>
<hr />
<div className="office-block">
<h4>KOLHAPUR OFFICE</h4>
<p>
Laxmi Civil Engineering Services Pvt. Ltd.<br />
1148, E. Sykes Extension, Kolhapur 416 001, Maharashtra, India.
</p>
<p>Phone No: 0231-2683900</p>
</div>
</div>
<div className="contact-center-image">
<img src={centerImg} alt="Office" />
</div>
<div className="contact-form">
<h3>SHARE YOUR THOUGHTS AND WE'LL CONTACT YOU!</h3>
{notice && (
<div className={`notice ${notice.type === "success" ? "notice-success" : "notice-error"}`} role="alert">
{notice.text}
</div>
)}
<form onSubmit={handleSubmit} noValidate>
<div className="input-group">
<input
type="text"
name="name"
placeholder="Name*"
value={formData.name}
onChange={handleChange}
required
autoComplete="name"
disabled={loading}
className={formErrors.name ? "error" : ""}
/>
{formErrors.name && <span className="error-text">{formErrors.name}</span>}
</div>
<div className="input-group">
<input
type="email"
name="email"
placeholder="Email*"
value={formData.email}
onChange={handleChange}
required
autoComplete="email"
disabled={loading}
className={formErrors.email ? "error" : ""}
/>
{formErrors.email && <span className="error-text">{formErrors.email}</span>}
</div>
<div className="input-group">
<input
type="text"
name="contact"
placeholder="Contact*"
value={formData.contact}
onChange={handleChange}
required
autoComplete="tel"
inputMode="tel"
disabled={loading}
className={formErrors.contact ? "error" : ""}
/>
{formErrors.contact && <span className="error-text">{formErrors.contact}</span>}
</div>
<div className="input-group">
<textarea
name="message"
placeholder="Message*"
value={formData.message}
onChange={handleChange}
required
rows={5}
disabled={loading}
className={formErrors.message ? "error" : ""}
/>
{formErrors.message && <span className="error-text">{formErrors.message}</span>}
</div>
<button type="submit" disabled={loading}>
{loading ? "SENDING..." : "SUBMIT"}
</button>
</form>
</div>
</div>
</div>
);
};
export default ContactUs;

240
src/pages/Gallery.jsx Normal file
View File

@@ -0,0 +1,240 @@
// src/pages/Gallery.jsx
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import heroBg from "../assets/awards-hero.jpg";
import { FiChevronLeft, FiChevronRight, FiX } from "react-icons/fi";
import "../styles/Gallery.css";
const Gallery = () => {
const [activeCategory, setActiveCategory] = useState("Awards & Achievements");
const [galleryItems, setGalleryItems] = useState([]);
const [loading, setLoading] = useState(true);
const [selectedItem, setSelectedItem] = useState(null);
const [currentIndex, setCurrentIndex] = useState(0);
const categoryRef = useRef(null);
const categories = [
{ label: "Awards & Achievements", value: "Awards & Achievements" },
{ label: "Client Testimonials", value: "Client Testimonials" },
];
// Disable right-click
useEffect(() => {
const handleContextMenu = (e) => e.preventDefault();
document.addEventListener("contextmenu", handleContextMenu);
return () => document.removeEventListener("contextmenu", handleContextMenu);
}, []);
// Fetch gallery items
useEffect(() => {
const fetchGalleryItems = async () => {
try {
const response = await axios.get(`${process.env.REACT_APP_API_BASE_URL}/api/gallery`);
setGalleryItems(Array.isArray(response.data) ? response.data : []);
} catch (error) {
console.error("Error fetching gallery items:", error);
} finally {
setLoading(false);
}
};
fetchGalleryItems();
}, []);
// Scroll to categories if hash exists
useEffect(() => {
if (window.location.hash === "#categories" && categoryRef.current) {
setTimeout(() => {
categoryRef.current.scrollIntoView({ behavior: "smooth", block: "start" });
}, 200);
}
}, []);
const filteredItems = galleryItems.filter(
(item) => item.category === activeCategory
);
const openLightbox = (item, index) => {
setSelectedItem(item);
setCurrentIndex(index);
};
const navigateLightbox = (direction) => {
const items = filteredItems;
let newIndex;
if (direction === "prev") {
newIndex = (currentIndex - 1 + items.length) % items.length;
} else {
newIndex = (currentIndex + 1) % items.length;
}
setSelectedItem(items[newIndex]);
setCurrentIndex(newIndex);
};
return (
<div className="min-h-screen bg-gray-50">
{/* Hero Section */}
<div
className="relative bg-cover bg-center h-[60vh] flex items-center justify-center"
style={{ backgroundImage: `url(${heroBg})` }}
>
<div className="absolute inset-0 bg-black bg-opacity-50"></div>
<div className="relative z-10 text-center px-4 animate-fadeIn">
<h1 className="text-3xl sm:text-4xl md:text-5xl font-extrabold text-white drop-shadow-lg">
Our Awards
</h1>
<p className="text-lg sm:text-xl md:text-2xl text-white mt-2 italic drop-shadow-md">
Celebrating Excellence & Achievements
</p>
</div>
</div>
{/* Category Buttons */}
<div
ref={categoryRef}
className="flex flex-wrap justify-center gap-4 px-4 mt-8 mb-10"
>
{categories.map((category) => {
const isSelected = activeCategory === category.value;
return (
<button
key={category.value}
onClick={() => setActiveCategory(category.value)}
className={`text-base lg:text-lg text-white font-semibold px-6 py-3 rounded-2xl shadow-lg transition-all duration-300 transform hover:scale-105
${isSelected ? "bg-gradient-to-r from-blue-600 to-blue-800 ring-4 ring-offset-2 ring-white" : "bg-gradient-to-r from-gray-600 to-gray-800"}`}
>
{category.label}
</button>
);
})}
</div>
{/* Gallery Grid */}
<div className="px-6">
{loading ? (
<p className="text-gray-500 text-xl text-center">Loading gallery items...</p>
) : filteredItems.length === 0 ? (
<p className="text-gray-600 text-xl text-center">No items available in this category.</p>
) : (
<div className="grid md:grid-cols-3 sm:grid-cols-2 gap-6">
{filteredItems.map((item, index) => (
<div
key={item.id}
className="rounded-xl shadow-lg overflow-hidden border bg-white hover:shadow-2xl transition-all duration-300"
>
{item.type === "image" ? (
<img
src={`${process.env.REACT_APP_API_BASE_URL}${item.url}`}
alt={item.caption || "Gallery Image"}
className="h-64 w-full object-cover cursor-pointer hover:scale-105 transition-transform duration-300"
onClick={() => openLightbox(item, index)}
draggable="false"
onDragStart={(e) => e.preventDefault()}
/>
) : (
<video
className="h-64 w-full object-cover cursor-pointer"
onClick={() => openLightbox(item, index)}
controls={false}
>
<source src={`${process.env.REACT_APP_API_BASE_URL}${item.url}`} type="video/mp4" />
</video>
)}
<div className="p-5 text-left">
<p className="text-sm text-gray-700 leading-relaxed">
{item.caption || "No caption available"}
</p>
{item.date && (
<p className="text-xs text-gray-500 mt-2">
{new Date(item.date).toLocaleDateString()}
</p>
)}
</div>
</div>
))}
</div>
)}
</div>
{/* Lightbox Overlay */}
{selectedItem && (
<div
className="fixed inset-0 bg-black bg-opacity-90 flex items-center justify-center z-50 p-4"
onClick={() => setSelectedItem(null)}
>
{/* Close */}
<button
className="absolute top-4 right-4 text-white text-3xl"
onClick={(e) => {
e.stopPropagation();
setSelectedItem(null);
}}
>
<FiX />
</button>
{/* Previous */}
<button
className="absolute left-4 text-white text-3xl p-2"
onClick={(e) => {
e.stopPropagation();
navigateLightbox("prev");
}}
>
<FiChevronLeft size={32} />
</button>
{/* Content */}
<div className="max-w-4xl w-full" onClick={(e) => e.stopPropagation()}>
{selectedItem.type === "image" ? (
<div className="relative">
<img
src={`${process.env.REACT_APP_API_BASE_URL}${selectedItem.url}`}
alt={selectedItem.caption || "Gallery Image"}
className="max-w-full max-h-[80vh] mx-auto rounded-lg shadow-lg"
draggable="false"
onDragStart={(e) => e.preventDefault()}
/>
{/* Watermark */}
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<div className="text-2xl font-bold text-white opacity-30" style={{
transform: 'rotate(-45deg)',
textShadow: '2px 2px 4px rgba(0,0,0,0.5)'
}}>
SAMPLE WATERMARK
</div>
</div>
</div>
) : (
<video
controls
autoPlay
className="max-w-full max-h-[80vh] mx-auto rounded-lg shadow-lg"
>
<source src={`${process.env.REACT_APP_API_BASE_URL}${selectedItem.url}`} type="video/mp4" />
</video>
)}
<div className="text-center mt-4 text-white">
<p className="text-lg">{selectedItem.caption}</p>
{selectedItem.date && (
<p className="text-sm text-gray-300">{new Date(selectedItem.date).toLocaleDateString()}</p>
)}
</div>
</div>
{/* Next */}
<button
className="absolute right-4 text-white text-3xl p-2"
onClick={(e) => {
e.stopPropagation();
navigateLightbox("next");
}}
>
<FiChevronRight size={32} />
</button>
</div>
)}
</div>
);
};
export default Gallery;

320
src/pages/GalleryAdmin.jsx Normal file
View File

@@ -0,0 +1,320 @@
import React, { useState, useEffect } from "react";
import axios from "axios";
import { toast } from "react-toastify";
import { FiX, FiEdit2, FiTrash2 } from "react-icons/fi";
const GalleryAdmin = () => {
const [galleryItems, setGalleryItems] = useState([]);
const [category, setCategory] = useState("Awards & Achievements");
const [caption, setCaption] = useState("");
const [date, setDate] = useState("");
const [media, setMedia] = useState(null);
const [mediaPreview, setMediaPreview] = useState(null);
const [mediaType, setMediaType] = useState("image");
const [editMode, setEditMode] = useState(false);
const [editItemId, setEditItemId] = useState(null);
const categories = [
{ label: "Awards & Achievements", value: "Awards & Achievements" },
{ label: "Client Testimonials", value: "Client Testimonials" },
];
// Fetch all gallery items
const fetchGalleryItems = async () => {
try {
const res = await axios.get(`${process.env.REACT_APP_API_BASE_URL}/api/gallery`);
setGalleryItems(res.data);
} catch (err) {
console.error("Error fetching gallery items", err);
toast.error("Failed to load gallery items");
}
};
useEffect(() => {
fetchGalleryItems();
}, []);
// Reset the form
const resetForm = () => {
setCategory("Awards & Achievements");
setCaption("");
setDate("");
setMedia(null);
setMediaPreview(null);
setMediaType("image");
setEditMode(false);
setEditItemId(null);
};
// Handle media selection
const handleMediaChange = (e) => {
const file = e.target.files[0];
if (!file) return;
setMedia(file);
// Check if file is video
if (file.type.includes("video")) {
setMediaType("video");
setMediaPreview(URL.createObjectURL(file));
} else {
setMediaType("image");
setMediaPreview(URL.createObjectURL(file));
}
};
// Handle form submit (add or update)
const handleSubmit = async (e) => {
e.preventDefault();
if (!category || (!editMode && !media)) {
toast.error("Category and media file are required!");
return;
}
const formData = new FormData();
formData.append("category", category);
formData.append("caption", caption);
formData.append("date", date);
formData.append("type", mediaType);
if (media) formData.append("media", media);
try {
if (editMode) {
await axios.put(
`${process.env.REACT_APP_API_BASE_URL}/api/gallery/${editItemId}`,
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
}
);
toast.success("Gallery item updated successfully!");
} else {
await axios.post(
`${process.env.REACT_APP_API_BASE_URL}/api/gallery`,
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
}
);
toast.success("Gallery item added successfully!");
}
resetForm();
fetchGalleryItems();
} catch (err) {
console.error("Failed to save gallery item", err);
toast.error("Error saving gallery item");
}
};
// Load item into form for editing
const handleEdit = (item) => {
setCategory(item.category);
setCaption(item.caption);
setDate(item.date || "");
setMedia(null);
setMediaType(item.type);
setMediaPreview(`${process.env.REACT_APP_API_BASE_URL}${item.url}`);
setEditMode(true);
setEditItemId(item.id);
};
// Delete an item
const handleDelete = async (id) => {
if (!window.confirm("Are you sure you want to delete this item?")) return;
try {
await axios.delete(`${process.env.REACT_APP_API_BASE_URL}/api/gallery/${id}`);
toast.success("Item deleted");
fetchGalleryItems();
} catch (err) {
console.error("Failed to delete", err);
toast.error("Failed to delete item");
}
};
return (
<div className="p-6 min-h-screen bg-gray-50">
<h1 className="text-3xl font-bold text-blue-900 mb-6">
{editMode ? "Edit Gallery Item" : "Add New Gallery Item"}
</h1>
{/* FORM */}
<form onSubmit={handleSubmit} className="space-y-4 max-w-md mx-auto bg-white p-6 rounded-lg shadow">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Category</label>
<select
value={category}
onChange={(e) => setCategory(e.target.value)}
className="w-full border p-2 rounded"
required
>
{categories.map((cat) => (
<option key={cat.value} value={cat.value}>
{cat.label}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Media Type</label>
<select
value={mediaType}
onChange={(e) => setMediaType(e.target.value)}
className="w-full border p-2 rounded"
disabled={editMode && !media} // Disable if editing and no new media selected
>
<option value="image">Image</option>
<option value="video">Video</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
{mediaType === "image" ? "Image" : "Video"} Upload
</label>
<input
type="file"
accept={mediaType === "image" ? "image/*" : "video/*"}
onChange={handleMediaChange}
className="w-full"
required={!editMode}
/>
</div>
{mediaPreview && (
<div className="relative">
{mediaType === "image" ? (
<img
src={mediaPreview}
alt="Preview"
className="h-48 w-full object-contain rounded border"
/>
) : (
<video
controls
className="h-48 w-full object-contain rounded border"
>
<source src={mediaPreview} type="video/mp4" />
</video>
)}
<button
type="button"
onClick={() => {
setMedia(null);
setMediaPreview(null);
}}
className="absolute top-2 right-2 bg-black bg-opacity-50 text-white p-1 rounded-full"
>
<FiX size={18} />
</button>
</div>
)}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Caption</label>
<input
type="text"
value={caption}
onChange={(e) => setCaption(e.target.value)}
className="w-full border p-2 rounded"
placeholder="Enter caption"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Date (optional)</label>
<input
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
className="w-full border p-2 rounded"
/>
</div>
<div className="flex justify-between gap-4">
<button
type="submit"
className="flex-1 bg-blue-900 text-white py-2 rounded hover:bg-blue-800"
>
{editMode ? "Update Item" : "Save Item"}
</button>
{editMode && (
<button
type="button"
onClick={resetForm}
className="flex-1 bg-gray-300 text-black py-2 rounded hover:bg-gray-400"
>
Cancel
</button>
)}
</div>
</form>
{/* GALLERY ITEMS LIST */}
<div className="mt-10">
<h2 className="text-2xl font-semibold text-blue-800 mb-4">All Gallery Items</h2>
<div className="grid md:grid-cols-3 sm:grid-cols-2 gap-6">
{galleryItems.map((item) => (
<div
key={item.id}
className="bg-white p-4 rounded-xl shadow hover:shadow-lg transition-all relative"
>
{item.type === "image" ? (
<img
src={`${process.env.REACT_APP_API_BASE_URL}${item.url}`}
alt={item.caption || "Gallery item"}
className="h-56 w-full object-cover rounded mb-3"
/>
) : (
<video
className="h-56 w-full object-cover rounded mb-3"
controls
>
<source src={`${process.env.REACT_APP_API_BASE_URL}${item.url}`} type="video/mp4" />
</video>
)}
<p className="text-sm font-medium text-gray-700 mb-1">
<span className="font-semibold">Category:</span> {item.category}
</p>
{item.caption && (
<p className="text-sm text-gray-600 mb-1 line-clamp-2">
<span className="font-semibold">Caption:</span> {item.caption}
</p>
)}
{item.date && (
<p className="text-xs text-gray-500">
{new Date(item.date).toLocaleDateString()}
</p>
)}
<div className="flex justify-between mt-3">
<button
onClick={() => handleEdit(item)}
className="flex items-center text-blue-600 hover:text-blue-800"
>
<FiEdit2 className="mr-1" /> Edit
</button>
<button
onClick={() => handleDelete(item.id)}
className="flex items-center text-red-600 hover:text-red-800"
>
<FiTrash2 className="mr-1" /> Delete
</button>
</div>
</div>
))}
</div>
</div>
</div>
);
};
export default GalleryAdmin;

14
src/pages/HRAdmin.jsx Normal file
View File

@@ -0,0 +1,14 @@
// src/pages/HRAdmin.jsx
import React from "react";
import Apply from "./Apply"; // reuse your Apply form component
const HRAdmin = () => {
return (
<div className="min-h-screen p-6 bg-gray-50">
<h1 className="text-3xl font-bold mb-6 text-blue-900">HR Applications</h1>
<Apply />
</div>
);
};
export default HRAdmin;

300
src/pages/Home.jsx Normal file
View File

@@ -0,0 +1,300 @@
import React from 'react';
import { motion } from 'framer-motion';
import videoSrc from '../assets/video.mp4';
import SectorsSlider from '../components/SectorsSlider';
import CounterCard from '../components/CounterCard';
import '../styles/Home.css';
import IndiaMap from "../components/IndiaMap";
import ClientsSection from '../components/ClientsSection';
import '../styles/CounterCard.css';
// Animation variants
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.3,
when: "beforeChildren"
}
}
};
const itemVariants = {
hidden: { y: 20, opacity: 0 },
visible: {
y: 0,
opacity: 1,
transition: {
duration: 0.5,
ease: "easeOut"
}
}
};
const fadeIn = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { duration: 0.8 }
}
};
const Home = () => {
return (
<div className="w-full bg-gradient-to-b from-blue-50 via-gray-50 to-white">
{/* Hero Video Section */}
<motion.div
initial="hidden"
animate="visible"
variants={fadeIn}
className="relative w-full h-[60vh] overflow-hidden"
>
<div className="absolute inset-0 bg-gradient-to-r from-blue-900/40 to-blue-600/30 z-10"></div>
<video
className="absolute top-0 left-0 w-full h-full object-cover z-0"
src={videoSrc}
autoPlay
loop
muted
playsInline
/>
<motion.div
variants={containerVariants}
className="absolute top-0 left-0 w-full h-full z-20 flex items-center justify-center overflow-visible"
>
<motion.div
variants={itemVariants}
className="text-center text-white px-4 max-w-4xl"
>
<h1 className="text-3xl md:text-5xl lg:text-5xl font-extrabold mb-6 drop-shadow-lg leading-snug md:leading-normal bg-clip-text text-transparent bg-gradient-to-r from-white to-blue-100">
Building the Future Restoring the Past
</h1>
<p className="text-lg md:text-xl lg:text-2xl font-medium drop-shadow max-w-2xl mx-auto bg-blue-900/30 backdrop-blur-sm rounded-lg p-4">
CONSTRUCTING WITH EXCELLENCE. DELIVERING PRIDE FOR GENERATIONS
</p>
</motion.div>
</motion.div>
</motion.div>
{/* Floating particles background */}
<div className="absolute top-0 left-0 w-full h-full overflow-hidden pointer-events-none z-0">
{[...Array(20)].map((_, i) => (
<motion.div
key={i}
className="absolute rounded-full bg-blue-200/30"
initial={{
x: Math.random() * 100,
y: Math.random() * 100,
width: Math.random() * 10 + 5,
height: Math.random() * 10 + 5,
}}
animate={{
y: [0, Math.random() * 100 - 50],
x: [0, Math.random() * 100 - 50],
transition: {
duration: Math.random() * 10 + 10,
repeat: Infinity,
repeatType: "reverse",
ease: "easeInOut",
},
}}
/>
))}
</div>
{/* Sector Slider */}
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ duration: 0.8 }}
viewport={{ once: true }}
className="relative py-12 bg-gradient-to-br from-blue-50 to-blue-100"
>
<div className="absolute inset-0 opacity-10">
<div className="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MCIgaGVpZ2h0PSI2MCIgdmlld0JveD0iMCAwIDYwIDYwIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIGZpbGw9IiMxRTg0RkYiIGZpbGwtb3BhY2l0eT0iMC4yIj48cGF0aCBkPSJNMzYgMzRjMC0yLjIgMS44LTQgNC00czQgMS44IDQgNC0xLjggNC00IDQtNC0xLjgtNC00eiIvPjwvZz48L2c+PC9zdmc+')]"></div>
</div>
<SectorsSlider />
</motion.div>
{/* About Section */}
<motion.section
initial="hidden"
whileInView="visible"
variants={containerVariants}
viewport={{ once: true, margin: "-100px" }}
className="relative py-16 px-4 md:px-8 lg:px-20 bg-white rounded-t-3xl shadow-xl overflow-hidden"
>
{/* Decorative corner */}
<div className="absolute top-0 right-0 w-64 h-64 bg-blue-600/5 clip-path-polygon-[0_0,_100%_0,_100%_100%]"></div>
</motion.section>
{/* Vision & Mission Section */}
<motion.section
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ duration: 0.6 }}
viewport={{ once: true }}
className="relative py-20 px-4 md:px-8 lg:px-20 bg-gradient-to-br from-blue-900 to-blue-800 overflow-hidden"
>
{/* Decorative elements */}
<div className="absolute top-0 left-0 w-full h-full opacity-10">
<div className="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MCIgaGVpZ2h0PSI2MCIgdmlld0JveD0iMCAwIDYwIDYwIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIGZpbGw9IiNGRkYiIGZpbGwtb3BhY2l0eT0iMC4yIj48cGF0aCBkPSJNMzYgMzRjMC0yLjIgMS44LTQgNC00czQgMS44IDQgNC0xLjggNC00IDQtNC0xLjgtNC00eiIvPjwvZz48L2c+PC9zdmc+')]"></div>
</div>
<motion.div
initial={{ y: 30, opacity: 0 }}
whileInView={{ y: 0, opacity: 1 }}
transition={{ duration: 0.6 }}
viewport={{ once: true }}
className="max-w-7xl mx-auto relative z-10"
>
<h2 className="text-4xl font-bold text-center text-white mb-16 relative pb-2">
<span className="relative inline-block">
Vision & Mission
<motion.span
initial={{ scaleX: 0 }}
whileInView={{ scaleX: 1 }}
transition={{ duration: 0.6 }}
viewport={{ once: true }}
className="absolute bottom-0 left-0 w-full h-1 bg-red-500 z-0 transform origin-center"
/>
</span>
</h2>
<div className="grid md:grid-cols-2 gap-8 mb-16">
{/* Vision */}
<motion.div
initial={{ x: -50, opacity: 0 }}
whileInView={{ x: 0, opacity: 1 }}
transition={{ duration: 0.5 }}
viewport={{ once: true }}
className="bg-white p-8 rounded-xl shadow-lg hover:shadow-xl transition-all border-l-4 border-blue-500 hover:border-blue-400"
>
<div className="flex items-start mb-6">
<div className="bg-blue-100 p-3 rounded-full mr-4">
<svg className="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
</div>
<div>
<h3 className="text-xl font-bold text-blue-900 mb-2">Vision</h3>
<p className="text-gray-700">
To be a company at the forefront of engineering and construction, renowned for excellence, quality, performance, and reliability.
</p>
</div>
</div>
</motion.div>
{/* Mission */}
<motion.div
initial={{ x: 50, opacity: 0 }}
whileInView={{ x: 0, opacity: 1 }}
transition={{ duration: 0.5 }}
viewport={{ once: true }}
className="bg-white p-8 rounded-xl shadow-lg hover:shadow-xl transition-all border-l-4 border-red-500 hover:border-red-400"
>
<div className="flex items-start mb-6">
<div className="bg-blue-100 p-3 rounded-full mr-4">
<svg className="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<div>
<h3 className="text-xl font-bold text-blue-900 mb-2">Mission</h3>
<p className="text-gray-700">
To complete every project undertaken with sincerity, excellence, and in a time-bound manner, meeting all expectations of the client.
</p>
</div>
</div>
</motion.div>
</div>
</motion.div>
</motion.section>
{/* Clients Section */}
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ duration: 0.8 }}
viewport={{ once: true }}
className="relative py-20 bg-gradient-to-br from-white to-blue-50 overflow-hidden"
>
{/* Decorative elements */}
<div className="absolute -top-20 -right-20 w-64 h-64 rounded-full bg-blue-200/20"></div>
<div className="absolute -bottom-20 -left-20 w-80 h-80 rounded-full bg-blue-300/10"></div>
<ClientsSection />
</motion.div>
{/* Map Section */}
<motion.section
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
viewport={{ once: true }}
className="relative py-20 px-4 md:px-8 lg:px-20 text-white"
>
{/* Animated background */}
<div className="absolute inset-0 overflow-hidden opacity-10">
{[...Array(10)].map((_, i) => (
<motion.div
key={i}
className="absolute rounded-full bg-white"
initial={{
x: Math.random() * 100,
y: Math.random() * 100,
width: Math.random() * 20 + 5,
height: Math.random() * 20 + 5,
opacity: Math.random() * 0.3,
}}
animate={{
y: [0, Math.random() * 100 - 50],
x: [0, Math.random() * 100 - 50],
transition: {
duration: Math.random() * 15 + 15,
repeat: Infinity,
repeatType: "reverse",
ease: "easeInOut",
},
}}
/>
))}
</div>
<div className="max-w-7xl mx-auto text-center relative z-10">
<h2 className="text-4xl font-bold mb-4 relative inline-block text-blue-900">
<span className="relative z-10">
Our Presence Across India
<motion.span
initial={{ scaleX: 0 }}
whileInView={{ scaleX: 1 }}
transition={{ duration: 0.6 }}
viewport={{ once: true }}
className="absolute bottom-0 left-0 w-full h-1 bg-red-500 z-0 transform origin-center"
/>
</span>
</h2>
<p className="text-xl text-gray-600 mb-8 max-w-2xl mx-auto">
We have successfully delivered projects in multiple states across India, with a growing national footprint.
</p>
<motion.div
initial={{ scale: 0.95 }}
whileInView={{ scale: 1 }}
transition={{ duration: 0.5 }}
viewport={{ once: true }}
className="bg-white/10 backdrop-blur-sm p-6 rounded-xl shadow-lg border border-blue-400/30"
>
<IndiaMap />
</motion.div>
</div>
</motion.section>
</div>
);
};
export default Home;

View File

@@ -0,0 +1,326 @@
import React, { useState, useEffect } from 'react';
const JobApplicationForm = () => {
const [formData, setFormData] = useState({
fullName: '',
email: '',
phone: '',
address: '',
education: '',
skill: '',
interest: '',
totalExperience: '',
expectedSalary: '',
currentCompany: '',
currentDesignation: '',
preferredLocation: '', // new field
infrastructureExpertise: '', // new field
resume: null,
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitSuccess, setSubmitSuccess] = useState(false);
const handleChange = (e) => {
const { name, value, type, files } = e.target;
setFormData((prev) => ({
...prev,
[name]: type === 'file' ? files[0] : value,
}));
};
const handleSubmit = async (e) => {
e.preventDefault();
setIsSubmitting(true);
setSubmitSuccess(false);
const form = new FormData();
for (let key in formData) {
form.append(key, formData[key]);
}
try {
const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/send-application`, {
method: 'POST',
body: form,
});
const result = await response.json();
if (response.ok) {
setSubmitSuccess(true); // show popup
setFormData({
fullName: '',
email: '',
phone: '',
address: '',
education: '',
skill: '',
interest: '',
totalExperience: '',
expectedSalary: '',
currentCompany: '',
currentDesignation: '',
preferredLocation: '',
infrastructureExpertise: '',
resume: null,
});
} else {
alert(result.message || 'Error submitting application');
}
} catch (err) {
console.error('Submission error:', err);
alert('Error submitting application. Please try again.');
} finally {
setIsSubmitting(false);
}
};
// Auto-close success popup after 2 seconds
useEffect(() => {
if (submitSuccess) {
const timer = setTimeout(() => setSubmitSuccess(false), 2000);
return () => clearTimeout(timer); // cleanup
}
}, [submitSuccess]);
return (
<div className="max-w-4xl mx-auto px-4 py-12">
<div className="bg-white rounded-lg shadow-lg overflow-hidden">
<div className="bg-blue-800 text-white p-6">
<h2 className="text-2xl font-bold">Job Application Form</h2>
<p className="text-blue-100">Please fill out the form below to apply for the position</p>
</div>
{/* Success Popup */}
{submitSuccess && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-30">
<div
className="bg-green-100 border-l-4 border-green-500 text-green-700 p-6 rounded shadow-lg max-w-md w-full animate-slide-in"
style={{ animation: 'slideIn 0.5s ease-out' }}
>
<p className="font-semibold text-lg">Application Submitted Successfully!</p>
<p className="mt-2">
Thank you for your application. We will review your information and contact you soon.
</p>
</div>
</div>
)}
<form onSubmit={handleSubmit} className="p-6 space-y-6" encType="multipart/form-data">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Personal Information */}
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-800 border-b pb-2">Personal Information</h3>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Full Name *</label>
<input
type="text"
name="fullName"
value={formData.fullName}
onChange={handleChange}
required
className="w-full border border-gray-300 px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Email *</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
required
className="w-full border border-gray-300 px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Phone *</label>
<input
type="tel"
name="phone"
value={formData.phone}
onChange={handleChange}
required
className="w-full border border-gray-300 px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Address</label>
<input
type="text"
name="address"
value={formData.address}
onChange={handleChange}
className="w-full border border-gray-300 px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Area of Expertise (Infrastructure)</label>
<input
type="text"
name="infrastructureExpertise"
value={formData.infrastructureExpertise}
onChange={handleChange}
className="w-full border border-gray-300 px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Your expertise in infrastructure field"
/>
</div>
</div>
{/* Professional Information */}
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-800 border-b pb-2">Professional Information</h3>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Current Company</label>
<input
type="text"
name="currentCompany"
value={formData.currentCompany}
onChange={handleChange}
className="w-full border border-gray-300 px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Current Designation</label>
<input
type="text"
name="currentDesignation"
value={formData.currentDesignation}
onChange={handleChange}
className="w-full border border-gray-300 px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Total Experience (Years) *</label>
<input
type="text"
name="totalExperience"
value={formData.totalExperience}
onChange={handleChange}
required
className="w-full border border-gray-300 px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Expected Salary</label>
<input
type="text"
name="expectedSalary"
value={formData.expectedSalary}
onChange={handleChange}
className="w-full border border-gray-300 px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Preferred Location</label>
<input
type="text"
name="preferredLocation"
value={formData.preferredLocation}
onChange={handleChange}
className="w-full border border-gray-300 px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="City / Region you prefer to work in"
/>
</div>
</div>
</div>
{/* Education and Skills */}
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-800 border-b pb-2">Qualifications</h3>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Education *</label>
<input
type="text"
name="education"
value={formData.education}
onChange={handleChange}
required
className="w-full border border-gray-300 px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Degrees, Certifications, etc."
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Skills *</label>
<input
type="text"
name="skill"
value={formData.skill}
onChange={handleChange}
required
className="w-full border border-gray-300 px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="List your key skills separated by commas"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Professional Interests</label>
<input
type="text"
name="interest"
value={formData.interest}
onChange={handleChange}
className="w-full border border-gray-300 px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Areas of professional interest"
/>
</div>
</div>
{/* Resume Upload */}
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-800 border-b pb-2">Resume</h3>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Upload Resume *</label>
<div className="mt-1 flex items-center">
<label className="cursor-pointer bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
Choose File
<input
type="file"
name="resume"
onChange={handleChange}
accept=".pdf,.doc,.docx"
required
className="sr-only"
/>
</label>
<span className="ml-4 text-sm text-gray-500">
{formData.resume ? formData.resume.name : "No file chosen"}
</span>
</div>
<p className="mt-1 text-xs text-gray-500">PDF, DOC, DOCX (Max. 5MB)</p>
</div>
</div>
{/* Form Submission */}
<div className="pt-4 border-t">
<div className="flex justify-end">
<button
type="submit"
disabled={isSubmitting}
className={`px-6 py-3 rounded-md text-white font-medium ${isSubmitting ? 'bg-blue-400' : 'bg-blue-600 hover:bg-blue-700'} focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors`}
>
{isSubmitting ? 'Submitting...' : 'Submit Application'}
</button>
</div>
</div>
</form>
</div>
</div>
);
};
export default JobApplicationForm;

181
src/pages/Office.jsx Normal file
View File

@@ -0,0 +1,181 @@
const Office = () => {
const offices = {
"Registered Office": [
{
address: "1148/E, Sykes Extension, Kolhapur - 416001, Maharashtra, India.",
},
],
"Corporate Office": [
{
address:
"C.S.NO. 1031/K2, E Ward, 3rd Floor, Sterling Tower, Gavat Mandai Road, Kolhapur - 416001, Maharashtra, India.",
phone: "+91 231 2686910/11, 2529199",
fax: "+91 231 2668199",
email: "career@lcepl.com",
},
],
"Regional Offices": {
Maharashtra: {
Mumbai: "141 & 142, 14th Floor, Mittal Court, 224 Nariman Point, Mumbai - 400021, India",
"Navi Mumbai":
"Flat No. F-1, Pariwar Housing Society, Above Ramdev Hotel, Sector No. 4/5, Vashi, Navi Mumbai - 400703, India",
Pune:
'"A.J. Residency" 5th Floor, Flat No. 502, S. No. 4711, Plot No. 6, Tawre Colony, Near Chougule Showroom, Pune - 411 009, India',
Nagpur:
"10-A, Samata Layout, beside NIT office, Diagonal opposite to NIT swimming Pool, Ambazari, Nagpur - 440010",
},
Karnataka: {
Bengaluru:
"198, 33rd Cross, 3rd Main Road, 7th Block, Jayanagar, Bangalore - 560070",
Belgavi:
"Plot No. 51, Teachers Colony, Vinayaknagar, Hindalga Road, Belgavi - 591 108, India",
},
"Madhya Pradesh": {
Bhopal: "E-3/165, Arera Colony, Bhopal - 462 016, Madhya Pradesh, India.",
Indore:
"Block No. 608, 6th Floor, Apollo Premier Towers, Vijay Nagar, Ring Road, Indore - 452010, India",
},
Goa: {
Panaji:
"Office No. 406, Kamat Grand, Behind Caculo Mall, Saint Intez, Panaji City 403001 (Goa), India",
},
Chhattisgarh: {
Raipur:
"Flat No. 202, Kashi Apartment, Geetanjali Nagar, Raipur - 492006, Chhattisgarh, India.",
},
"Andhra Pradesh": {
Vijayawada:
"42 2 1 202 Drwa No. 423, 1st Floor, Janaki Nilayam, Main Road, Devi Nagar, Vijayawada, Krishna, Andhra Pradesh - 520 003, India",
},
"Uttar Pradesh": {
Lucknow:
"Parth Aadyant Tower, Ashlesha A House no. 203, Near Phoenix Palassio Mall, Gomtinagar Extension, Along Shahid Path, Lucknow - 226010, India",
},
Bihar: {
Patna:
"501, Manan Mansion Apartment, Gorkhnathpath Lane, East Boring, Canal Road, Patna - 800001, Bihar, India.",
},
Gujarat: {
Ahmedabad:
"3rd Floor, A 9, 10, Sardar Mall, Near Dimond Mill, Nocol, Ahmedabad - 382350, Gujarat, India.",
},
Uttarakhand: {
Dehradun:
"House No. 12-B, Ekta Enclave, Lane No. 6, Near Success Point Global Academy, Neharu Gram Road, Nathanpur, Deharadun - 248005, Uttarakhand, India.",
},
},
};
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
{/* Header with animation */}
<div className="text-center mb-16">
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-4 relative inline-block">
<span className="relative z-10">Our Offices</span>
<span className="absolute bottom-0 left-0 w-full h-2 bg-blue-200 z-0 opacity-70 transform translate-y-1"></span>
</h1>
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
Connecting with you across multiple locations in India
</p>
</div>
{/* Main content with glassmorphism effect */}
<div className="space-y-16">
{/* Registered Office */}
<section className="relative">
<div className="absolute -inset-4 bg-blue-100 rounded-xl opacity-25 blur"></div>
<div className="relative bg-white rounded-xl shadow-lg p-6 md:p-8 backdrop-blur-sm border border-blue-100">
<div className="flex items-center mb-4">
<div className="p-2 bg-blue-100 rounded-lg mr-4">
<svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
</div>
<h2 className="text-2xl md:text-3xl font-bold text-blue-800">REGISTERED OFFICE</h2>
</div>
<div className="pl-14">
<p className="text-gray-700 text-lg">{offices["Registered Office"][0].address}</p>
</div>
</div>
</section>
{/* Corporate Office */}
<section className="relative">
<div className="absolute -inset-4 bg-blue-100 rounded-xl opacity-25 blur"></div>
<div className="relative bg-white rounded-xl shadow-lg p-6 md:p-8 backdrop-blur-sm border border-blue-100">
<div className="flex items-center mb-4">
<div className="p-2 bg-blue-100 rounded-lg mr-4">
<svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
</div>
<h2 className="text-2xl md:text-3xl font-bold text-blue-800">CORPORATE OFFICE</h2>
</div>
<div className="pl-14 space-y-3">
<p className="text-gray-700 text-lg">{offices["Corporate Office"][0].address}</p>
<div className="flex items-start">
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-blue-500 mt-1 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg>
<p className="text-gray-700 text-lg">{offices["Corporate Office"][0].phone}</p>
</div>
<div className="flex items-start">
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-blue-500 mt-1 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
</svg>
<p className="text-gray-700 text-lg">{offices["Corporate Office"][0].fax}</p>
</div>
<div className="flex items-start">
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-blue-500 mt-1 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
<p className="text-gray-700 text-lg">{offices["Corporate Office"][0].email}</p>
</div>
</div>
</div>
</section>
{/* Regional Offices */}
<section>
<div className="text-center mb-10">
<h2 className="text-3xl font-bold text-gray-900 inline-block relative">
<span className="relative z-10">REGIONAL OFFICES</span>
<span className="absolute bottom-0 left-0 w-full h-2 bg-blue-200 z-0 opacity-50 transform translate-y-1"></span>
</h2>
</div>
<div className="grid md:grid-cols-2 gap-8">
{Object.entries(offices["Regional Offices"]).map(([state, cities]) => (
<div key={state} className="relative group">
<div className="absolute -inset-1 bg-gradient-to-r from-blue-100 to-blue-50 rounded-lg opacity-75 group-hover:opacity-100 blur transition duration-200"></div>
<div className="relative bg-white rounded-lg shadow-md overflow-hidden border border-gray-100 hover:border-blue-200 transition duration-200 h-full">
<div className="bg-blue-600 px-6 py-3">
<h3 className="text-xl font-bold text-white">{state}</h3>
</div>
<div className="p-6 space-y-4">
{Object.entries(cities).map(([city, address]) => (
<div key={city} className="pb-4 border-b border-gray-100 last:border-0 last:pb-0">
<h4 className="font-bold text-blue-800 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
{city}
</h4>
<p className="text-gray-600 text-sm mt-1 pl-7">{address}</p>
</div>
))}
</div>
</div>
</div>
))}
</div>
</section>
</div>
</div>
</div>
);
};
export default Office;

284
src/pages/Projects.jsx Normal file
View File

@@ -0,0 +1,284 @@
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import Slider from "react-slick";
import heroBg from "../assets/projects-hero.jpg";
import "../styles/Projects.css";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
/* =====================
Custom Arrows
===================== */
const NextArrow = ({ className, style, onClick }) => (
<div
className={className}
style={{
...style,
display: "block",
background: "rgba(0,0,0,0.5)",
borderRadius: "50%",
padding: "10px",
right: "10px",
zIndex: 2,
}}
onClick={onClick}
/>
);
const PrevArrow = ({ className, style, onClick }) => (
<div
className={className}
style={{
...style,
display: "block",
background: "rgba(0,0,0,0.5)",
borderRadius: "50%",
padding: "10px",
left: "10px",
zIndex: 2,
}}
onClick={onClick}
/>
);
const Projects = () => {
const [selectedSector, setSelectedSector] = useState("all");
const [projects, setProjects] = useState([]);
const [loading, setLoading] = useState(true);
const [expandedDescriptions, setExpandedDescriptions] = useState({});
const [lightboxImage, setLightboxImage] = useState(null);
const sectorRef = useRef(null);
const sectors = [
{ label: "All", value: "all" },
{ label: "Water Supply", value: "water supply" },
{ label: "Storm Water", value: "storm water" },
{ label: "Electromechanical", value: "electromechanical" },
{ label: "Real Estate / Buildings", value: "real estate / buildings" },
{ label: "Tunnel", value: "tunnel" },
{ label: "Roads", value: "roads" },
{ label: "Wastewater / Sewerage", value: "wastewater / sewerage" },
{ label: "Irrigation", value: "irrigation" },
{ label: "Renewable Energy", value: "renewable energy" },
];
/* =====================
Fetch Projects
===================== */
useEffect(() => {
const fetchProjects = async () => {
try {
const res = await axios.get(
`${process.env.REACT_APP_API_BASE_URL}/api/projects`
);
setProjects(Array.isArray(res.data) ? res.data : []);
} catch (err) {
console.error("Error fetching projects:", err);
} finally {
setLoading(false);
}
};
fetchProjects();
}, []);
/* =====================
Scroll to sectors
===================== */
useEffect(() => {
if (window.location.hash === "#sectors" && sectorRef.current) {
setTimeout(() => {
sectorRef.current.scrollIntoView({ behavior: "smooth" });
}, 200);
}
}, []);
const filteredProjects = projects.filter((p) => {
if (selectedSector === "all") return true;
return (
p.sector &&
p.sector.toLowerCase().trim() === selectedSector.toLowerCase().trim()
);
});
const toggleDescription = (id) => {
setExpandedDescriptions((prev) => ({
...prev,
[id]: !prev[id],
}));
};
/* =====================
Slider Settings
===================== */
const sliderSettings = {
dots: false,
infinite: true,
speed: 600,
slidesToShow: 3,
slidesToScroll: 1,
autoplay: true,
autoplaySpeed: 1500,
arrows: true,
nextArrow: <NextArrow />,
prevArrow: <PrevArrow />,
responsive: [
{ breakpoint: 1024, settings: { slidesToShow: 3 } },
{ breakpoint: 768, settings: { slidesToShow: 2 } },
{ breakpoint: 480, settings: { slidesToShow: 1 } },
],
};
return (
<div className="min-h-screen bg-gray-50">
{/* =====================
Hero Section
====================== */}
<div
className="relative bg-cover bg-center h-[60vh] flex items-center justify-center"
style={{ backgroundImage: `url(${heroBg})` }}
>
<div className="absolute inset-0 bg-black bg-opacity-50"></div>
<div className="relative z-10 text-center px-4">
<h1 className="text-3xl sm:text-4xl md:text-5xl font-extrabold text-white">
Our Projects
</h1>
<p className="text-lg sm:text-xl md:text-2xl text-white mt-2 italic">
Stronger Partnerships, Greater Success
</p>
</div>
</div>
{/* =====================
Sector Buttons
====================== */}
<div
ref={sectorRef}
className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 px-4 mt-8 mb-10"
>
{sectors.map((sector, index) => {
const bgColors = [
"bg-gradient-to-r from-blue-500 to-indigo-500",
"bg-gradient-to-r from-green-400 to-green-600",
"bg-gradient-to-r from-yellow-400 to-yellow-600",
"bg-gradient-to-r from-pink-400 to-pink-600",
"bg-gradient-to-r from-purple-400 to-purple-600",
"bg-gradient-to-r from-red-400 to-red-600",
];
const isSelected = selectedSector === sector.value;
return (
<button
key={sector.value}
onClick={() => setSelectedSector(sector.value)}
className={`text-white font-semibold px-5 py-3 rounded-2xl shadow-lg transition-all duration-300 hover:scale-105
${bgColors[index % bgColors.length]}
${isSelected ? "ring-4 ring-white ring-offset-2" : ""}`}
>
{sector.label}
</button>
);
})}
</div>
{/* =====================
Projects Slider
====================== */}
<div className="px-6 mb-16">
{loading ? (
<p className="text-center text-xl text-gray-500">Loading projects...</p>
) : filteredProjects.length === 0 ? (
<p className="text-center text-xl text-gray-600">
No projects available.
</p>
) : (
<Slider key={selectedSector} {...sliderSettings}>
{filteredProjects.map((project) => (
<div key={project.id} className="px-2">
<div className="bg-white rounded-xl shadow-lg overflow-hidden hover:shadow-2xl transition">
{/* ✅ FIXED IMAGE SIZE (NO BIG / SMALL ISSUE) */}
{project.image && (
<div className="relative w-full aspect-[16/9] bg-gray-200 overflow-hidden">
<img
src={`${process.env.REACT_APP_API_BASE_URL}${project.image}`}
alt={project.name}
className="absolute inset-0 w-full h-full object-cover cursor-pointer"
onClick={(e) => {
e.stopPropagation();
setLightboxImage(
`${process.env.REACT_APP_API_BASE_URL}${project.image}`
);
}}
/>
</div>
)}
<div className="p-4">
<h3 className="text-lg font-bold text-blue-800">
{project.name}
</h3>
<p className="text-sm text-gray-600">
Sector: {project.sector}
</p>
{project.location && (
<p className="text-sm text-gray-600">
Location: {project.location}
</p>
)}
{project.description && (
<p className="text-sm text-gray-700 mt-2">
{expandedDescriptions[project.id]
? project.description
: project.description.slice(0, 100) + "..."}
{project.description.length > 100 && (
<button
onClick={() => toggleDescription(project.id)}
className="ml-2 text-blue-600 underline text-sm"
>
{expandedDescriptions[project.id]
? "Read Less"
: "Read More"}
</button>
)}
</p>
)}
</div>
</div>
</div>
))}
</Slider>
)}
</div>
{/* =====================
Image Lightbox
====================== */}
{lightboxImage && (
<div
className="fixed inset-0 bg-black bg-opacity-80 z-50 flex items-center justify-center"
onClick={() => setLightboxImage(null)}
>
<div className="relative" onClick={(e) => e.stopPropagation()}>
<button
onClick={() => setLightboxImage(null)}
className="absolute -top-4 -right-4 text-white text-3xl font-bold"
>
×
</button>
<img
src={lightboxImage}
alt="Project"
className="max-w-[90vw] max-h-[90vh] object-contain"
/>
</div>
</div>
)}
</div>
);
};
export default Projects;

View File

13
src/reportWebVitals.js Normal file
View File

@@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

5
src/setupTests.js Normal file
View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

324
src/styles/AboutUs.css Normal file
View File

@@ -0,0 +1,324 @@
/* ============================
About Hero Section
============================ */
.about-hero {
position: relative;
background: url('../assets/about-hero.jpg') center/cover no-repeat;
height: 60vh; /* reduced from 80vh */
width: 100%;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
overflow: hidden;
flex-direction: column;
}
.about-hero::before {
content: "";
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.5); /* semi-transparent overlay */
z-index: 1;
}
.about-hero h1,
.about-hero p {
position: relative;
z-index: 2;
color: white;
font-family: 'Roboto', sans-serif;
}
.about-hero h1 {
font-size: 4rem;
font-weight: 800;
text-transform: uppercase;
padding: 1rem 2rem;
letter-spacing: 1.5px;
}
.about-hero p {
font-size: 1.5rem;
font-weight: 400;
margin-top: 0.5rem;
}
/* ============================
Main Company Info Section
============================ */
.about-main {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 4rem 2rem;
gap: 3rem;
background-color: #f9f9f9;
}
.about-text {
flex: 1 1 50%;
font-size: 1.1rem;
line-height: 1.8;
color: #333;
}
.about-text h2.highlight-title {
font-size: 1.8rem;
color: #0d47a1;
margin-bottom: 1.5rem;
}
/* ============================
Responsive Adjustments
============================ */
@media (max-width: 1024px) {
.about-hero {
height: 50vh;
padding: 1rem;
}
.about-hero h1 {
font-size: 3rem;
}
.about-hero p {
font-size: 1.2rem;
}
.about-main {
gap: 2rem;
padding: 3rem 1.5rem;
}
}
@media (max-width: 768px) {
.about-hero {
height: 40vh;
padding: 1rem;
}
.about-hero h1 {
font-size: 2.2rem;
padding: 0.5rem;
}
.about-hero p {
font-size: 1rem;
}
.about-main {
flex-direction: column;
align-items: center;
padding: 2rem 1rem;
}
.about-text,
.counters-grid {
flex: 1 1 100%;
}
.counters-grid {
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
}
}
@media (max-width: 480px) {
.about-hero {
height: 30vh;
}
.about-hero h1 {
font-size: 1.8rem;
letter-spacing: 1px;
}
.about-hero p {
font-size: 0.9rem;
}
}
/* ============================
Vision/Mission Section
============================ */
.vision-section {
padding: 3rem 1.5rem;
background: #f9fafb;
text-align: center;
}
.vision-section h2 {
font-size: 2.8rem;
font-weight: 800;
margin-bottom: 2.5rem;
color: #111827;
}
.vision-grid {
max-width: 1100px;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 1.5rem;
}
.vision-box {
background: #ffffff;
padding: 1.5rem;
border-radius: 0.75rem;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
transition: box-shadow 0.3s ease;
border-top-width: 5px;
border-style: solid;
color: black;
}
.vision-box:hover {
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
}
.vision-box h3 {
font-weight: 700;
font-size: 1.5rem;
margin-bottom: 0.75rem;
color: black;
}
.vision-box p {
color: black;
font-size: 1.1rem;
line-height: 1.6;
}
.border-top-blue { border-top-color: #3b82f6; }
.border-top-red { border-top-color: #ef4444; }
.border-top-green { border-top-color: #10b981; }
.icon-circle {
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 9999px;
margin-bottom: 1rem;
}
.bg-blue { background-color: #e0f2fe; }
.bg-red { background-color: #fee2e2; }
.bg-green { background-color: #d1fae5; }
.icon-svg { width: 28px; height: 28px; }
.text-blue { color: #2563eb; }
.text-red { color: #dc2626; }
.text-green { color: #059669; }
/* ============================
Working Fields Section
============================ */
.working-fields {
padding: 4rem 2rem;
background-color: #f0f2f5;
text-align: center;
}
.working-fields h2 {
font-size: 2.5rem;
font-weight: 700;
color: #222;
margin-bottom: 2rem;
}
.fields-grid {
display: grid;
gap: 2rem;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
margin-top: 2rem;
}
/* Field Card Backgrounds */
.field-card.civil { background: url('../assets/civil-bg.jpg') center/cover no-repeat; }
.field-card.mechanical { background: url('../assets/mechanical-bg.jpg') center/cover no-repeat; }
.field-card.electrical { background: url('../assets/electrical-bg.jpg') center/cover no-repeat; }
.field-card.electromechanical { background: url('../assets/electromechanical-bg.jpg') center/cover no-repeat; }
.field-card.it { background: url('../assets/it-bg.jpg') center/cover no-repeat; }
.field-card.energy { background: url('../assets/banner-sustainable-energy-1150x650.png') center/cover no-repeat; }
.field-card {
position: relative;
border-radius: 16px;
overflow: hidden;
height: 280px;
display: flex;
align-items: center;
justify-content: center;
color: white;
animation: fadeInUp 0.6s ease forwards;
transform: translateY(30px);
opacity: 0;
cursor: pointer;
transition: all 0.3s ease;
}
.field-card:hover {
transform: translateY(-8px) scale(1.05);
filter: brightness(1.15);
}
.field-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.45);
z-index: 1;
transition: background 0.3s ease;
}
.field-card:hover .field-overlay {
background: rgba(0, 0, 0, 0.35);
}
.field-content {
position: relative;
z-index: 2;
text-align: center;
padding: 1rem;
}
.field-icon {
font-size: 2.8rem;
margin-bottom: 0.8rem;
display: inline-block;
transition: transform 0.3s ease;
}
.field-card:hover .field-icon {
transform: scale(1.2) rotate(5deg);
}
.field-content h3 {
font-size: 1.4rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.field-content p {
font-size: 1rem;
line-height: 1.5;
}
@keyframes fadeInUp {
to { opacity: 1; transform: translateY(0); }
}
/* Responsive Adjustments */
@media (max-width: 768px) {
.field-card { height: 200px; }
.field-content h3 { font-size: 1.2rem; }
.field-content p { font-size: 0.9rem; }
}
@media (max-width: 480px) {
.field-card { height: 180px; }
.field-content h3 { font-size: 1rem; }
.field-content p { font-size: 0.85rem; }
}

66
src/styles/AwardsCard.css Normal file
View File

@@ -0,0 +1,66 @@
/* ---------------------------- */
/* Awards Section - Base Style */
/* ---------------------------- */
.awards-section {
background-color: #f5f5f5;
padding: 60px 20px;
text-align: center;
}
.awards-container {
max-width: 1000px;
margin: auto;
}
.awards-section .highlight-title {
font-size: 2rem;
margin-bottom: 1rem;
color: #333;
}
/* Hero Image - Base */
.awards-section img.hero-image {
max-width: 100%; /* responsive width */
height: auto; /* keeps proportions */
display: block;
margin: 0 auto 2rem; /* center with some bottom space */
border-radius: 8px; /* optional: soft corners */
}
/* ---------------------------- */
/* Responsive Breakpoints */
/* ---------------------------- */
/* Small Devices (≤ 600px) */
@media (max-width: 600px) {
.awards-section {
padding: 40px 15px;
}
.awards-section .highlight-title {
font-size: 1.5rem;
}
.awards-section img.hero-image {
max-width: 90%; /* keep some padding */
margin-bottom: 1.5rem;
}
}
/* Medium Devices (Tablets: 601px - 1024px) */
@media (min-width: 601px) and (max-width: 1024px) {
.awards-section {
padding: 50px 25px;
}
.awards-section .highlight-title {
font-size: 1.75rem;
}
.awards-section img.hero-image {
max-width: 95%;
}
}
/* Large Devices (Desktop ≥ 1025px) */
/* No change needed base styles apply */

368
src/styles/ContactUs.css Normal file
View File

@@ -0,0 +1,368 @@
/* ===============================
ContactUs.css (Alternate Design)
=============================== */
:root {
--accent: #ff5722; /* Orange accent */
--accent-dark: #e64a19;
--bg: #f4f6f8;
--white: #ffffff;
--text: #222;
--muted: #555;
--border: #e0e0e0;
--radius: 14px;
--shadow: 0 10px 25px rgba(0,0,0,0.08);
}
/* Reset scope */
.contact-page * {
box-sizing: border-box;
}
/* PAGE WRAPPER */
.contact-page {
background: var(--bg);
font-family: "Segoe UI", sans-serif;
color: var(--text);
}
/* HERO */
.contact-hero {
background: url('../assets/contact-hero.jpg') center/cover no-repeat;
height: 60vh; /* same as About Hero height */
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.contact-overlay {
background: linear-gradient(to right, rgba(0,0,0,0.6), rgba(0,0,0,0.4));
backdrop-filter: blur(2px);
height: 100%;
width: 100%;
display: grid;
place-items: center;
}
.contact-overlay h1 {
color: #fff;
font-size: clamp(28px, 5vw, 46px);
text-transform: uppercase;
letter-spacing: 3px;
margin: 0;
}
/* HEADING */
.contact-heading {
text-align: center;
margin: 30px auto 10px;
padding: 0 20px;
}
.contact-heading h2 {
font-size: 30px;
color: var(--accent);
margin: 0 0 8px;
letter-spacing: 2px;
}
.contact-heading p {
color: var(--muted);
margin: 6px auto;
max-width: 700px;
}
/* GRID LAYOUT */
.contact-content {
display: grid;
gap: 30px;
grid-template-columns: 1fr 1fr 1fr;
max-width: 1200px;
margin: 20px auto 70px;
}
/* OFFICE INFO */
.contact-info {
background: var(--white);
border-radius: var(--radius);
padding: 22px;
box-shadow: var(--shadow);
}
.contact-info h3 {
color: var(--accent);
font-size: 16px;
letter-spacing: 2px;
margin-bottom: 12px;
}
.contact-info hr {
border: none;
height: 1px;
background: var(--border);
margin: 14px 0;
}
.office-block h4 {
margin: 6px 0;
font-size: 17px;
color: var(--accent-dark);
}
.office-block p {
font-size: 14px;
color: var(--text);
margin: 4px 0;
}
/* IMAGE CENTER */
.contact-center-image {
overflow: hidden;
border-radius: var(--radius);
box-shadow: var(--shadow);
background: #ddd;
min-height: 280px;
}
.contact-center-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* CONTACT FORM */
.contact-form {
background: rgba(255,255,255,0.85);
border-radius: var(--radius);
padding: 24px;
box-shadow: var(--shadow);
backdrop-filter: blur(6px);
}
.contact-form h3 {
font-size: 16px;
color: var(--accent);
letter-spacing: 1px;
margin-bottom: 14px;
}
/* NOTICE */
.notice {
border-radius: var(--radius);
padding: 10px 12px;
font-size: 14px;
}
.notice-success {
background: #e8f9f1;
color: #1b7f4e;
}
.notice-error {
background: #fdecea;
color: #c43e3e;
}
/* FORM INPUTS */
.contact-form form {
display: grid;
gap: 14px;
}
.contact-form input,
.contact-form textarea {
width: 100%;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 12px 14px;
font-size: 15px;
background: var(--white);
outline: none;
}
.contact-form input:focus,
.contact-form textarea:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(255,87,34,0.2);
}
.contact-form textarea {
resize: vertical;
min-height: 120px;
}
/* BUTTON */
.contact-form button[type="submit"] {
background: var(--accent);
border: none;
color: #fff;
font-weight: bold;
border-radius: 40px;
padding: 12px;
cursor: pointer;
transition: background 0.2s ease;
}
.contact-form button:hover {
background: var(--accent-dark);
}
.contact-form button[disabled] {
opacity: 0.7;
cursor: not-allowed;
}
/* API hint */
.api-hint {
font-size: 12px;
color: var(--muted);
margin-top: 4px;
}
/* RESPONSIVE */
@media (max-width: 1000px) {
.contact-content {
grid-template-columns: 1fr 1fr;
}
.contact-center-image {
grid-column: span 2;
min-height: 200px;
}
}
@media (max-width: 768px) {
.contact-content {
grid-template-columns: 1fr;
}
.contact-center-image {
grid-column: span 1;
}
}
/* ============================
RESPONSIVE IMPROVEMENTS
============================ */
/* Large tablets & small laptops */
@media (max-width: 1200px) {
.contact-content {
max-width: 95%;
gap: 20px;
}
.contact-hero {
height: 60vh;
}
.contact-overlay h1 {
font-size: clamp(24px, 4vw, 40px);
}
}
/* Tablets */
@media (max-width: 1000px) {
.contact-content {
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.contact-center-image {
grid-column: span 2;
min-height: 200px;
}
.contact-heading h2 {
font-size: 26px;
}
.contact-heading p {
font-size: 15px;
}
}
/* Mobile landscape & portrait */
@media (max-width: 768px) {
.contact-content {
grid-template-columns: 1fr;
gap: 18px;
margin: 15px auto 50px;
}
.contact-center-image {
grid-column: span 1;
min-height: 180px;
}
.contact-info,
.contact-form {
padding: 18px;
}
.contact-info h3,
.contact-form h3 {
font-size: 14px;
}
.office-block h4 {
font-size: 16px;
}
.office-block p {
font-size: 13px;
}
.contact-form input,
.contact-form textarea {
font-size: 14px;
padding: 10px 12px;
}
.contact-form button[type="submit"] {
font-size: 14px;
padding: 10px;
}
}
/* Small mobile devices */
@media (max-width: 480px) {
.contact-hero {
height: 50vh;
}
.contact-overlay h1 {
font-size: clamp(20px, 5vw, 28px);
letter-spacing: 2px;
}
.contact-heading {
margin: 20px auto 6px;
padding: 0 12px;
}
.contact-heading h2 {
font-size: 22px;
}
.contact-heading p {
font-size: 13px;
}
.contact-info,
.contact-form {
padding: 14px;
}
.contact-form input,
.contact-form textarea {
font-size: 13px;
}
.contact-form button[type="submit"] {
font-size: 13px;
padding: 8px;
}
}

View File

@@ -0,0 +1,81 @@
/* ============================
Counters Grid - Compact Vertical Line by Line
============================ */
.counters-grid {
display: flex;
flex-direction: column; /* vertical stacking */
gap: 1rem; /* smaller gap between cards */
padding: 15px;
max-width: 360px; /* narrower width for compact look */
margin: 0 auto; /* center horizontally */
}
/* Individual Counter Cards */
.counter-card {
position: relative;
background-color: #ffffff;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
padding: 15px 20px; /* smaller padding */
display: flex;
flex-direction: row; /* icon on left, number+label on right */
align-items: center;
gap: 12px;
transition: transform 0.2s ease, box-shadow 0.2s ease;
min-height: 70px; /* compact height */
}
/* Optional left accent bar */
.counter-card::before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 5px;
height: 100%;
background-color: #0284c7;
border-radius: 10px 0 0 10px;
}
/* Number */
.counter-card h3 {
font-size: 1.8rem;
font-weight: 700;
color: #0284c7;
margin: 0;
}
/* Description */
.counter-card p {
font-size: 1rem;
font-weight: 500;
color: #1f2937;
margin: 0;
}
/* Hover effect */
.counter-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
}
/* Responsive Adjustments */
/* Tablet */
@media (max-width: 768px) {
.counter-card h3 { font-size: 1.6rem; }
.counter-card p { font-size: 0.95rem; }
}
/* Mobile */
@media (max-width: 480px) {
.counters-grid { max-width: 100%; gap: 0.8rem; }
.counter-card {
flex-direction: column; /* icon on top, number + label below */
align-items: flex-start;
padding: 12px 15px;
min-height: 60px;
}
.counter-card h3 { font-size: 1.4rem; margin-bottom: 3px; }
.counter-card p { font-size: 0.9rem; }
}

301
src/styles/Gallery.css Normal file
View File

@@ -0,0 +1,301 @@
/* ===== Hero Section Styles ===== */
.gallery-container {
display: flex;
flex-direction: column;
min-height: 100vh;
width: 100%;
}
/* Full-width background hero */
.gallery-hero {
position: relative;
width: 100%;
height: 85vh;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
display: flex;
align-items: center;
justify-content: center;
}
.hero-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.5); /* Dark overlay */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
text-align: center;
padding: 0 20px;
}
.hero-title {
font-size: 3rem;
font-weight: 800;
margin-bottom: 1rem;
text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.6);
}
.hero-subtitle {
font-size: 1.5rem;
max-width: 600px;
font-style: italic;
color: #ffffff; /* ✅ Changed from #ffb84d (orange) to white */
text-shadow: 1px 1px 6px rgba(0, 0, 0, 0.6);
}
/* ===== Category Selector ===== */
.bannerSlider {
display: flex;
justify-content: center;
margin: 20px 0;
gap: 10px;
}
.bannerSlider button {
padding: 12px 24px;
margin: 0 5px;
border: none;
background: #f0f0f0;
cursor: pointer;
border-radius: 4px;
font-weight: 500;
transition: all 0.3s ease;
}
.bannerSlider button.active {
background: #2c3e50;
color: white;
}
.bannerSlider button:hover {
background: #ddd;
}
.bannerSlider button.active:hover {
background: #1a252f;
}
/* ===== Gallery Grid ===== */
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
padding: 15px 0;
}
.gallery-item {
border: 1px solid #ddd;
overflow: hidden;
border-radius: 8px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.gallery-item:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
.gallery-image {
width: 100%;
height: 250px;
object-fit: cover;
display: block;
}
.gallery-caption {
padding: 15px;
text-align: center;
font-size: 16px;
background: white;
}
/* ===== Lightbox ===== */
.lightbox-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.lightbox-close {
position: absolute;
top: 30px;
right: 30px;
background: none;
border: none;
color: white;
font-size: 32px;
cursor: pointer;
transition: color 0.3s ease;
}
.lightbox-close:hover {
color: #ccc;
}
.lightbox-content {
position: relative;
max-width: 90%;
max-height: 90%;
width: auto;
}
.lightbox-image {
max-height: 80vh;
max-width: 100%;
border-radius: 8px;
}
.lightbox-caption {
color: white;
text-align: center;
margin-top: 20px;
font-size: 18px;
}
.lightbox-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
font-size: 28px;
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background 0.3s ease;
}
.lightbox-nav:hover {
background: rgba(255, 255, 255, 0.3);
}
.lightbox-nav.prev {
left: -70px;
}
.lightbox-nav.next {
right: -70px;
}
/* ===== Responsive ===== */
@media (max-width: 768px) {
.gallery-hero {
height: 300px;
}
.hero-title {
font-size: 2rem;
}
.hero-subtitle {
font-size: 1.2rem;
}
.bannerSlider {
flex-wrap: wrap;
}
.bannerSlider button {
margin-bottom: 10px;
}
.grid-container {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
.lightbox-nav.prev {
left: 10px;
}
.lightbox-nav.next {
right: 10px;
}
}
/* Add these styles to your existing CSS */
.gallery-admin-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.media-preview-container {
position: relative;
margin: 15px 0;
}
.media-preview {
max-height: 250px;
max-width: 100%;
border-radius: 8px;
border: 1px solid #ddd;
}
.clear-preview-btn {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0,0,0,0.7);
color: white;
border: none;
border-radius: 50%;
width: 25px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.gallery-items-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin-top: 30px;
}
.gallery-item-card {
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
transition: transform 0.2s;
}
.gallery-item-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1);
}
/* Prevent selection and force pointer-events */
.protected-media {
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
pointer-events: none;
}
/* Overlay technique */
.media-protection-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 10;
}

36
src/styles/Home.css Normal file
View File

@@ -0,0 +1,36 @@
/* Main Company Info Section */
.about-main {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 3rem;
padding: clamp(2rem, 4vw, 4rem) clamp(1rem, 4vw, 2rem);
background-color: #f9f9f9;
}
/* About text container */
.about-text {
flex: 1 1 100%; /* Full width on small screens */
font-size: clamp(1rem, 1.5vw, 1.1rem); /* Responsive font size */
line-height: 1.8;
color: #333;
}
/* Tablet and larger: split columns */
@media (min-width: 768px) {
.about-text {
flex: 1 1 48%; /* Two columns with gap */
}
}
/* Highlighted heading */
.about-text h2.highlight-title {
font-size: clamp(1.5rem, 3vw, 1.8rem); /* Responsive heading size */
color: #0d47a1;
margin-bottom: 1.5rem;
}
/* Optional: paragraph styling */
.about-text p {
margin-bottom: 1.5rem;
}

30
src/styles/IndiaMap.css Normal file
View File

@@ -0,0 +1,30 @@
.state {
fill: #e2e8f0; /* Default gray */
stroke: #ffffff;
stroke-width: 1;
transition: fill 0.3s ease;
}
.state.highlighted {
fill: #2563eb; /* Blue for highlighted states */
}
.state:hover {
fill: #1e40af; /* Darker blue on hover */
}
/* Mobile view - scale down the entire SVG map container */
@media (max-width: 767px) {
/* Assuming your ComposableMap SVG has a wrapper with class 'map-wrapper' */
.map-wrapper {
transform: scale(0.3); /* scale down to 30% size */
transform-origin: top left; /* anchor scaling to top-left */
width: 100%; /* keep full container width */
height: auto;
}
/* Optionally adjust .state stroke width on mobile */
.state {
stroke-width: 0.7;
}
}

73
src/styles/Projects.css Normal file
View File

@@ -0,0 +1,73 @@
/* Projects.css */
/* Slider override */
.radar-like-slider .slick-slide {
padding: 0 0.75rem;
}
/* Card */
.radar-card {
text-align: center;
cursor: pointer;
}
.radar-image {
width: 100%;
height: 12rem;
object-fit: cover;
border-radius: 0.75rem;
transition: transform 0.3s ease-in-out, box-shadow 0.3s ease;
}
.radar-image:hover {
transform: translateY(-6px) scale(1.05);
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
}
.radar-title {
margin-top: 0.5rem;
font-size: 1rem;
font-weight: 600;
color: #1f2937; /* text-gray-800 */
}
/* Filter buttons */
.filter-buttons {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 1.5rem;
justify-content: center;
}
.sector-btn {
background: #2563eb;
color: #fff;
padding: 0.6rem 1rem;
border-radius: 9999px;
font-weight: 600;
transition: 0.3s;
}
.sector-btn.selected,
.sector-btn:hover {
background: #1e40af;
transform: scale(1.05);
}
/* Lightbox */
.lightbox {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.85);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.lightbox img {
max-width: 90%;
max-height: 90%;
border-radius: 0.5rem;
}

View File

@@ -0,0 +1,224 @@
/* Main Container Styles - Keep desktop styles unchanged */
.infraCont {
width: 100%;
position: relative;
overflow: hidden;
background-color: #f8fafc;
padding: 0;
text-align: justify;
}
.custom {
position: relative;
max-width: 1200px;
margin: 0 auto;
height: 600px;
}
.infraWrap {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
opacity: 0;
transition: opacity 0.8s ease, transform 0.8s ease;
transform: translateX(50px);
text-align: justify;
}
.infraWrap.active {
opacity: 1;
transform: translateX(0);
z-index: 2;
text-align: justify;
}
.slideTing {
width: 50%;
padding-right: 4rem;
display: flex;
justify-content: center;
align-items: center;
}
/* Card for Image */
/* Card for Image */
.sector-card {
width: 400px;
height: 400px;
background: #fff;
border-radius: 1rem;
box-shadow: 0 8px 20px rgba(0,0,0,0.1);
overflow: hidden; /* ensures no overflow if image is cropped */
display: flex;
justify-content: center;
align-items: center;
}
.sector-image {
width: 100%;
height: 100%;
object-fit: cover; /* fill card completely */
}
/* Right side content */
.infraright {
width: 50%;
padding-left: 2rem;
position: relative;
}
.infraInfo {
max-width: 90%;
}
.infraInfo h2 {
font-size: 2.5rem;
font-weight: 700;
color: #1e3a8a;
margin-bottom: 1rem;
line-height: 1.2;
}
.infraInfo h3 {
font-size: 1.5rem;
font-weight: 600;
color: #334155;
margin-bottom: 1.5rem;
text-transform: uppercase;
}
.infraInfo p {
font-size: 1.1rem;
color: #475569;
line-height: 1.8;
margin-bottom: 2.5rem;
}
/* Slider Navigation Arrows */
.slider-nav {
position: absolute;
top: 50%;
left: 0;
width: 100%;
display: flex;
justify-content: space-between;
transform: translateY(-50%);
padding: 0 15px;
z-index: 10;
}
.nav-arrow {
background: none;
color: #1e3a8a;
border: none;
font-size: 2.5rem;
cursor: pointer;
padding: 0;
transition: color 0.3s ease, transform 0.2s ease;
}
.nav-arrow:hover {
color: #d32f2f;
transform: scale(1.1);
}
/* Indicators */
.indicators {
display: flex;
justify-content: center;
margin-top: 1.5rem;
gap: 0.5rem;
}
.indicator {
width: 12px;
height: 12px;
background: #cbd5e1;
border-radius: 50%;
border: none;
cursor: pointer;
transition: background 0.3s;
}
.indicator.active {
background: #1e3a8a;
}
/* Mobile View - Horizontal Slider */
@media (max-width: 768px) {
.custom {
height: auto;
padding: 0;
display: block;
overflow: hidden;
}
.infraCont { padding: 2rem 0; }
.infraWrap {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: auto;
opacity: 0;
transition: opacity 0.8s ease, transform 0.8s ease;
transform: translateX(50px);
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
padding: 0 1.5rem;
gap: 1rem;
}
.infraWrap.active {
opacity: 1;
transform: translateX(0);
z-index: 2;
}
.slideTing,
.infraright {
width: 100%;
padding: 0;
margin: 0;
text-align: center;
}
.sector-card {
width: 250px;
height: 250px;
}
.infraInfo {
margin: 0;
padding: 0;
}
.infraInfo h2 { font-size: 1.8rem; }
.infraInfo h3 { font-size: 1.2rem; }
.infraInfo p { font-size: 1rem; margin-bottom: 1.5rem; }
.indicators { bottom: 1rem; }
}
/* Smaller Mobile Devices */
@media (max-width: 480px) {
.custom {
height: 650px;
}
.infraInfo h2 { font-size: 1.6rem; }
.infraInfo h3 { font-size: 1.1rem; }
.infraInfo p { font-size: 0.9rem; }
.sector-card {
width: 200px;
height: 200px;
}
}

29
src/styles/VisionCard.css Normal file
View File

@@ -0,0 +1,29 @@
/* Vision Section */
.vision-section {
background: url('../assets/background-vision.jpg') center/cover no-repeat;
color: white;
padding: 4rem 2rem;
display: flex;
justify-content: space-around;
flex-wrap: wrap;
text-align: center;
gap: 2rem;
}
.vision-card {
flex: 1 1 30%;
background: rgba(0, 0, 0, 0.5);
padding: 2rem;
border-radius: 10px;
}
.vision-card h3 {
font-size: 1.5rem;
margin-bottom: 1rem;
}
.vision-card p {
font-size: 1rem;
color: #eee;
}

22
tailwind.config.js Normal file
View File

@@ -0,0 +1,22 @@
// tailwind.config.js
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}", // Make sure this path is accurate
],
theme: {
extend: {
fontFamily: {
sans: ['Poppins', 'sans-serif'],
},
colors: {
lcepl: {
blue: '#1E3A8A',
gray: '#374151',
orange: '#F97316',
light: '#F9FAFB',
},
},
},
},
plugins: [],
};