first commit
All checks were successful
CodeAnt AI Review - Stage 1 / codeant-review (push) Successful in 58s
32
.gitea/workflows/.gitignore
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Node modules
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.next/
|
||||||
|
out/
|
||||||
|
|
||||||
|
# IDE files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# Mac / Linux / Windows system files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Temporary
|
||||||
|
*.tmp
|
||||||
11
.gitea/workflows/README.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
# KLC Public website
|
||||||
|
|
||||||
|
This is a code bundle for KLC Public website. The original project is available at https://www.figma.com/design/HKsCupXQAAtHdsmq0gcuEE/KLC-Public-website.
|
||||||
|
|
||||||
|
## Running the code
|
||||||
|
|
||||||
|
Run `npm i` to install the dependencies.
|
||||||
|
|
||||||
|
Run `npm run dev` to start the development server.
|
||||||
|
|
||||||
15
.gitea/workflows/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>KLC Public website</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
73
.gitea/workflows/package.json
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"name": "KLC Public website",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-accordion": "^1.2.3",
|
||||||
|
"@radix-ui/react-alert-dialog": "^1.1.6",
|
||||||
|
"@radix-ui/react-aspect-ratio": "^1.1.2",
|
||||||
|
"@radix-ui/react-avatar": "^1.1.3",
|
||||||
|
"@radix-ui/react-checkbox": "^1.1.4",
|
||||||
|
"@radix-ui/react-collapsible": "^1.1.3",
|
||||||
|
"@radix-ui/react-context-menu": "^2.2.6",
|
||||||
|
"@radix-ui/react-dialog": "^1.1.6",
|
||||||
|
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
||||||
|
"@radix-ui/react-hover-card": "^1.1.6",
|
||||||
|
"@radix-ui/react-label": "^2.1.2",
|
||||||
|
"@radix-ui/react-menubar": "^1.1.6",
|
||||||
|
"@radix-ui/react-navigation-menu": "^1.2.5",
|
||||||
|
"@radix-ui/react-popover": "^1.1.6",
|
||||||
|
"@radix-ui/react-progress": "^1.1.2",
|
||||||
|
"@radix-ui/react-radio-group": "^1.2.3",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.2.3",
|
||||||
|
"@radix-ui/react-select": "^2.1.6",
|
||||||
|
"@radix-ui/react-separator": "^1.1.2",
|
||||||
|
"@radix-ui/react-slider": "^1.2.3",
|
||||||
|
"@radix-ui/react-slot": "^1.1.2",
|
||||||
|
"@radix-ui/react-switch": "^1.1.3",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.3",
|
||||||
|
"@radix-ui/react-toggle": "^1.1.2",
|
||||||
|
"@radix-ui/react-toggle-group": "^1.1.2",
|
||||||
|
"@radix-ui/react-tooltip": "^1.1.8",
|
||||||
|
"@reduxjs/toolkit": "^2.9.0",
|
||||||
|
"@tailwindcss/postcss": "^4.1.12",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "*",
|
||||||
|
"cmdk": "^1.1.1",
|
||||||
|
"embla-carousel-react": "^8.6.0",
|
||||||
|
"input-otp": "^1.4.2",
|
||||||
|
"lucide-react": "^0.487.0",
|
||||||
|
"motion": "*",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-day-picker": "^8.10.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-hook-form": "^7.55.0",
|
||||||
|
"react-redux": "^9.2.0",
|
||||||
|
"react-resizable-panels": "^2.1.7",
|
||||||
|
"react-router-dom": "^7.8.2",
|
||||||
|
"react-slick": "^0.31.0",
|
||||||
|
"recharts": "^2.15.2",
|
||||||
|
"slick-carousel": "^1.8.1",
|
||||||
|
"sonner": "^2.0.3",
|
||||||
|
"tailwind-merge": "*",
|
||||||
|
"tailwindcss": "^4.1.12",
|
||||||
|
"three": "^0.183.2",
|
||||||
|
"vaul": "^1.1.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.10.0",
|
||||||
|
"@types/react": "^19.1.12",
|
||||||
|
"@types/react-dom": "^19.1.8",
|
||||||
|
"@types/react-slick": "^0.23.13",
|
||||||
|
"@types/three": "^0.183.1",
|
||||||
|
"@vitejs/plugin-react": "^5.0.2",
|
||||||
|
"@vitejs/plugin-react-swc": "^3.10.2",
|
||||||
|
"vite": "^6.3.5"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
.gitea/workflows/postcss.config.mjs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
"@tailwindcss/postcss": {},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import svgPaths from "./svg-9nb29n5e63";
|
||||||
|
|
||||||
|
function Frame1597884903() {
|
||||||
|
return (
|
||||||
|
<div className="absolute font-['Inter:Regular',_sans-serif] font-normal inset-[22%_-4.71%_22%_20.59%] leading-[0] not-italic overflow-clip text-[#ffffff] text-[20px] text-left text-nowrap">
|
||||||
|
<div className="absolute left-0 top-0">
|
||||||
|
<p className="block leading-[28px] text-nowrap whitespace-pre">
|
||||||
|
Build Your Leadership Pipeline
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="absolute left-0 top-[22px]">
|
||||||
|
<p className="block leading-[28px] text-nowrap whitespace-pre">
|
||||||
|
Build Your Leadership Pipeline
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Frame18() {
|
||||||
|
return (
|
||||||
|
<div className="absolute bg-[#04045b] bottom-0 left-0 overflow-clip right-[85.29%] top-0">
|
||||||
|
<div
|
||||||
|
className="absolute flex h-[25.953px] items-center justify-center translate-x-[-50%] translate-y-[-50%] w-[25.953px]"
|
||||||
|
style={{ top: "calc(50% - 0.226px)", left: "calc(50% - 0.645px)" }}
|
||||||
|
>
|
||||||
|
<div className="flex-none rotate-[225deg] scale-y-[-100%]">
|
||||||
|
<div className="h-5 relative w-[16.717px]" data-name="Vector">
|
||||||
|
<svg
|
||||||
|
className="block size-full"
|
||||||
|
fill="none"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
viewBox="0 0 17 20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d={svgPaths.pac4fa00}
|
||||||
|
fill="var(--fill-0, white)"
|
||||||
|
id="Vector"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="absolute flex h-[38.935px] items-center justify-center translate-x-[-50%] translate-y-[-50%] w-[38.935px]"
|
||||||
|
style={{ top: "calc(50% + 35.775px)", left: "calc(50% - 35.646px)" }}
|
||||||
|
>
|
||||||
|
<div className="flex-none rotate-[225deg] scale-y-[-100%]">
|
||||||
|
<div className="h-[30px] relative w-[25.075px]" data-name="Vector">
|
||||||
|
<svg
|
||||||
|
className="block size-full"
|
||||||
|
fill="none"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
viewBox="0 0 26 30"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d={svgPaths.p177af00}
|
||||||
|
fill="var(--fill-0, white)"
|
||||||
|
id="Vector"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BuildYourLeadershipPipelineButton() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="relative size-full"
|
||||||
|
data-name="Build Your Leadership Pipeline (Button)"
|
||||||
|
>
|
||||||
|
<Frame1597884903 />
|
||||||
|
<Frame18 />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import svgPaths from "./svg-sphymsws2o";
|
||||||
|
|
||||||
|
function Frame1597884903() {
|
||||||
|
return (
|
||||||
|
<div className="absolute bottom-[22%] font-['Inter:Regular',_sans-serif] font-normal leading-[0] left-[19.66%] not-italic overflow-clip right-0 text-[#ffffff] text-[20px] text-left text-nowrap top-[22%]">
|
||||||
|
<div className="absolute left-0 top-0">
|
||||||
|
<p className="block leading-[28px] text-nowrap whitespace-pre">
|
||||||
|
Build Your Leadership Pipeline
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="absolute left-0 top-[22px]">
|
||||||
|
<p className="block leading-[28px] text-nowrap whitespace-pre">
|
||||||
|
Build Your Leadership Pipeline
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Frame18() {
|
||||||
|
return (
|
||||||
|
<div className="absolute bg-[#04045b] bottom-0 left-0 overflow-clip right-[85.95%] top-0">
|
||||||
|
<div
|
||||||
|
className="absolute flex h-[25.953px] items-center justify-center translate-x-[-50%] translate-y-[-50%] w-[25.953px]"
|
||||||
|
style={{ top: "calc(50% - 0.226px)", left: "calc(50% - 0.645px)" }}
|
||||||
|
>
|
||||||
|
<div className="flex-none rotate-[225deg] scale-y-[-100%]">
|
||||||
|
<div className="h-5 relative w-[16.717px]" data-name="Vector">
|
||||||
|
<svg
|
||||||
|
className="block size-full"
|
||||||
|
fill="none"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
viewBox="0 0 17 20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d={svgPaths.pac4fa00}
|
||||||
|
fill="var(--fill-0, white)"
|
||||||
|
id="Vector"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="absolute flex h-[38.935px] items-center justify-center translate-x-[-50%] translate-y-[-50%] w-[38.935px]"
|
||||||
|
style={{ top: "calc(50% + 35.775px)", left: "calc(50% - 35.646px)" }}
|
||||||
|
>
|
||||||
|
<div className="flex-none rotate-[225deg] scale-y-[-100%]">
|
||||||
|
<div className="h-[30px] relative w-[25.075px]" data-name="Vector">
|
||||||
|
<svg
|
||||||
|
className="block size-full"
|
||||||
|
fill="none"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
viewBox="0 0 26 30"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d={svgPaths.p177af00}
|
||||||
|
fill="var(--fill-0, white)"
|
||||||
|
id="Vector"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BuildYourLeadershipPipelineButton() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="relative size-full"
|
||||||
|
data-name="Build Your Leadership Pipeline (Button)"
|
||||||
|
>
|
||||||
|
<Frame1597884903 />
|
||||||
|
<Frame18 />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import svgPaths from "./svg-k5kyhbfrwc";
|
||||||
|
|
||||||
|
function Frame1597884903() {
|
||||||
|
return (
|
||||||
|
<div className="absolute bottom-[22%] font-['Source_Sans_Pro:Regular',_sans-serif] leading-[0] left-[21.61%] not-italic overflow-clip right-0 text-[#ffffff] text-[20px] text-left text-nowrap top-[22%]">
|
||||||
|
<div className="absolute left-0 top-0">
|
||||||
|
<p className="block leading-[28px] text-nowrap whitespace-pre">
|
||||||
|
Build Your Leadership Pipeline
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="absolute left-0 top-[22px]">
|
||||||
|
<p className="block leading-[28px] text-nowrap whitespace-pre">
|
||||||
|
Build Your Leadership Pipeline
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Frame18() {
|
||||||
|
return (
|
||||||
|
<div className="absolute bg-[#04045b] bottom-0 left-0 overflow-clip right-[84.57%] top-0">
|
||||||
|
<div
|
||||||
|
className="absolute flex h-[25.953px] items-center justify-center translate-x-[-50%] translate-y-[-50%] w-[25.953px]"
|
||||||
|
style={{ top: "calc(50% - 0.226px)", left: "calc(50% - 0.645px)" }}
|
||||||
|
>
|
||||||
|
<div className="flex-none rotate-[225deg] scale-y-[-100%]">
|
||||||
|
<div className="h-5 relative w-[16.717px]" data-name="Vector">
|
||||||
|
<svg
|
||||||
|
className="block size-full"
|
||||||
|
fill="none"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
viewBox="0 0 17 20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d={svgPaths.pac4fa00}
|
||||||
|
fill="var(--fill-0, white)"
|
||||||
|
id="Vector"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="absolute flex h-[38.935px] items-center justify-center translate-x-[-50%] translate-y-[-50%] w-[38.935px]"
|
||||||
|
style={{ top: "calc(50% + 35.775px)", left: "calc(50% - 35.645px)" }}
|
||||||
|
>
|
||||||
|
<div className="flex-none rotate-[225deg] scale-y-[-100%]">
|
||||||
|
<div className="h-[30px] relative w-[25.075px]" data-name="Vector">
|
||||||
|
<svg
|
||||||
|
className="block size-full"
|
||||||
|
fill="none"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
viewBox="0 0 26 30"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d={svgPaths.p177af00}
|
||||||
|
fill="var(--fill-0, white)"
|
||||||
|
id="Vector"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BuildYourLeadershipPipelineButton() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="relative size-full"
|
||||||
|
data-name="Build Your Leadership Pipeline (Button)"
|
||||||
|
>
|
||||||
|
<Frame1597884903 />
|
||||||
|
<Frame18 />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
178
.gitea/workflows/src/.figma_internal/OurApproachSection.tsx
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
function Frame1597884955() {
|
||||||
|
return (
|
||||||
|
<div className="box-border content-stretch flex flex-col gap-7 items-center justify-start leading-[0] not-italic p-0 relative shrink-0 text-center w-[652px]">
|
||||||
|
<div className="font-['Inter:Semi_Bold',_sans-serif] font-semibold relative shrink-0 text-[#26231a] text-[48px] tracking-[-0.96px] w-full">
|
||||||
|
<p className="block leading-[53px]">Our Approach</p>
|
||||||
|
</div>
|
||||||
|
<div className="font-['Inter:Regular',_sans-serif] font-normal relative shrink-0 text-[18px] text-[rgba(38,35,26,0.8)] w-full">
|
||||||
|
<p className="block leading-[24px]">
|
||||||
|
Programmatic journey over 6–12 weeks combining practical application with peer learning for maximum management
|
||||||
|
effectiveness.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Component01() {
|
||||||
|
return (
|
||||||
|
<div className="absolute bg-[#ffffff] h-[234px] left-0 top-0 w-[1230px]" data-name="01">
|
||||||
|
<div
|
||||||
|
className="absolute font-['Inter:Regular',_sans-serif] font-normal leading-[0] not-italic text-[18px] text-[rgba(38,35,26,0.8)] text-left top-[186px] w-[652px]"
|
||||||
|
style={{ left: "calc(50% - 400px)" }}
|
||||||
|
>
|
||||||
|
<p className="block leading-[24px]">
|
||||||
|
Comprehensive assessment of current management skills and development needs
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="absolute font-['Inter:Semi_Bold',_sans-serif] font-semibold leading-[0] not-italic text-[#26231a] text-[40px] text-center text-nowrap top-10 tracking-[-0.8px] translate-x-[-50%]"
|
||||||
|
style={{ left: "calc(50% - 577.5px)" }}
|
||||||
|
>
|
||||||
|
<p className="adjustLetterSpacing block leading-[53px] whitespace-pre">(01)</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="absolute font-['Inter:Semi_Bold',_sans-serif] font-semibold leading-[53px] not-italic text-[#26231a] text-[40px] text-left text-nowrap top-10 tracking-[-0.8px] whitespace-pre"
|
||||||
|
style={{ left: "calc(50% - 400px)" }}
|
||||||
|
>
|
||||||
|
<p className="adjustLetterSpacing block mb-0">{`Skills `}</p>
|
||||||
|
<p className="adjustLetterSpacing block">Assessment</p>
|
||||||
|
</div>
|
||||||
|
<div className="absolute h-0 left-0 top-0 w-[1230px]">
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 top-[-1px]">
|
||||||
|
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 1230 1">
|
||||||
|
<line id="Line 62" stroke="var(--stroke-0, black)" x2="1230" y1="0.5" y2="0.5" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Component02() {
|
||||||
|
return (
|
||||||
|
<div className="absolute bg-[#ffffff] h-[234px] left-0 top-[274px] w-[1230px]" data-name="02">
|
||||||
|
<div
|
||||||
|
className="absolute font-['Inter:Regular',_sans-serif] font-normal leading-[0] not-italic text-[#26231a] text-[18px] text-left top-[186px] w-[652px]"
|
||||||
|
style={{ left: "calc(50% - 399px)" }}
|
||||||
|
>
|
||||||
|
<p className="block leading-[24px]">
|
||||||
|
Customized curriculum design based on assessment results and operational context
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="absolute font-['Inter:Semi_Bold',_sans-serif] font-semibold leading-[0] not-italic text-[#26231a] text-[40px] text-center text-nowrap top-10 tracking-[-0.8px] translate-x-[-50%]"
|
||||||
|
style={{ left: "calc(50% - 576.5px)" }}
|
||||||
|
>
|
||||||
|
<p className="adjustLetterSpacing block leading-[53px] whitespace-pre">(02)</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="absolute font-['Inter:Semi_Bold',_sans-serif] font-semibold leading-[53px] not-italic text-[#26231a] text-[40px] text-left text-nowrap top-10 tracking-[-0.8px] whitespace-pre"
|
||||||
|
style={{ left: "calc(50% - 399px)" }}
|
||||||
|
>
|
||||||
|
<p className="adjustLetterSpacing block mb-0">{`Program `}</p>
|
||||||
|
<p className="adjustLetterSpacing block">Design</p>
|
||||||
|
</div>
|
||||||
|
<div className="absolute h-0 left-[3px] top-0 w-[1230px]">
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 top-[-1px]">
|
||||||
|
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 1230 1">
|
||||||
|
<line id="Line 62" stroke="var(--stroke-0, black)" x2="1230" y1="0.5" y2="0.5" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Component03() {
|
||||||
|
return (
|
||||||
|
<div className="absolute bg-[#ffffff] h-[234px] left-0 top-[548px] w-[1230px]" data-name="03">
|
||||||
|
<div
|
||||||
|
className="absolute font-['Inter:Regular',_sans-serif] font-normal leading-[0] not-italic text-[18px] text-[rgba(38,35,26,0.8)] text-left top-[186px] w-[601px]"
|
||||||
|
style={{ left: "calc(50% - 398px)" }}
|
||||||
|
>
|
||||||
|
<p className="block leading-[24px]">
|
||||||
|
Hands-on workshops and peer learning sessions with real-world application
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="absolute font-['Inter:Semi_Bold',_sans-serif] font-semibold leading-[0] not-italic text-[#26231a] text-[40px] text-center text-nowrap top-10 tracking-[-0.8px] translate-x-[-50%]"
|
||||||
|
style={{ left: "calc(50% - 576px)" }}
|
||||||
|
>
|
||||||
|
<p className="adjustLetterSpacing block leading-[53px] whitespace-pre">(03)</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="absolute font-['Inter:Semi_Bold',_sans-serif] font-semibold leading-[53px] not-italic text-[#26231a] text-[40px] text-left text-nowrap top-10 tracking-[-0.8px] whitespace-pre"
|
||||||
|
style={{ left: "calc(50% - 398px)" }}
|
||||||
|
>
|
||||||
|
<p className="adjustLetterSpacing block mb-0">{`Interactive `}</p>
|
||||||
|
<p className="adjustLetterSpacing block">Learning</p>
|
||||||
|
</div>
|
||||||
|
<div className="absolute h-0 left-1 top-0 w-[1230px]">
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 top-[-1px]">
|
||||||
|
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 1230 1">
|
||||||
|
<line id="Line 62" stroke="var(--stroke-0, black)" x2="1230" y1="0.5" y2="0.5" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Component04() {
|
||||||
|
return (
|
||||||
|
<div className="absolute bg-[#ffffff] h-[234px] left-0 top-[822px] w-[1230px]" data-name="04">
|
||||||
|
<div
|
||||||
|
className="absolute font-['Inter:Regular',_sans-serif] font-normal leading-[0] not-italic text-[18px] text-[rgba(38,35,26,0.8)] text-left top-[186px] w-[601px]"
|
||||||
|
style={{ left: "calc(50% - 398px)" }}
|
||||||
|
>
|
||||||
|
<p className="block leading-[24px]">
|
||||||
|
Ongoing support and progress tracking to ensure skills transfer and improvement
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="absolute font-['Inter:Semi_Bold',_sans-serif] font-semibold leading-[0] not-italic text-[#26231a] text-[40px] text-center text-nowrap top-10 tracking-[-0.8px] translate-x-[-50%]"
|
||||||
|
style={{ left: "calc(50% - 575.5px)" }}
|
||||||
|
>
|
||||||
|
<p className="adjustLetterSpacing block leading-[53px] whitespace-pre">(04)</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="absolute font-['Inter:Semi_Bold',_sans-serif] font-semibold leading-[53px] not-italic text-[#26231a] text-[40px] text-left text-nowrap top-10 tracking-[-0.8px] whitespace-pre"
|
||||||
|
style={{ left: "calc(50% - 398px)" }}
|
||||||
|
>
|
||||||
|
<p className="adjustLetterSpacing block mb-0">{`Application & `}</p>
|
||||||
|
<p className="adjustLetterSpacing block">Review</p>
|
||||||
|
</div>
|
||||||
|
<div className="absolute h-0 left-1 top-0 w-[1230px]">
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 top-[-1px]">
|
||||||
|
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 1230 1">
|
||||||
|
<line id="Line 62" stroke="var(--stroke-0, black)" x2="1230" y1="0.5" y2="0.5" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Frame1597884956() {
|
||||||
|
return (
|
||||||
|
<div className="h-[1056px] relative shrink-0 w-full">
|
||||||
|
<Component01 />
|
||||||
|
<Component02 />
|
||||||
|
<Component03 />
|
||||||
|
<Component04 />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function OurApproachSection() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="box-border content-stretch flex flex-col gap-20 items-center justify-start p-0 relative size-full"
|
||||||
|
data-name="Our approach section"
|
||||||
|
>
|
||||||
|
<Frame1597884955 />
|
||||||
|
<Frame1597884956 />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
4
.gitea/workflows/src/.figma_internal/svg-9nb29n5e63.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export default {
|
||||||
|
p177af00: "M24.7045 12.0426L12.5992 0L0.493934 12.0426C0.35111 12.1514 0.233321 12.2895 0.148542 12.4478C0.0637634 12.606 0.0139736 12.7806 0.00254296 12.9598C-0.00888768 13.1389 0.0183085 13.3185 0.0822895 13.4862C0.14627 13.6539 0.245543 13.806 0.373386 13.932C0.501229 14.058 0.654659 14.1551 0.823287 14.2167C0.991916 14.2783 1.17181 14.3029 1.35078 14.2889C1.52975 14.2749 1.70363 14.2226 1.86065 14.1356C2.01766 14.0486 2.15414 13.9288 2.26085 13.7845L11.3461 4.77444V28.7469C11.3461 29.0792 11.4781 29.398 11.7131 29.633C11.9481 29.868 12.2668 30 12.5992 30C12.9315 30 13.2503 29.868 13.4853 29.633C13.7203 29.398 13.8523 29.0792 13.8523 28.7469V4.77444L22.9375 13.7845C23.1735 14.0188 23.4929 14.1497 23.8254 14.1486C24.158 14.1474 24.4764 14.0142 24.7107 13.7782C24.945 13.5422 25.076 13.2228 25.0748 12.8903C25.0737 12.5578 24.9404 12.2393 24.7045 12.005V12.0426Z",
|
||||||
|
pac4fa00: "M16.4696 8.0284L8.39946 0L0.329289 8.0284C0.234073 8.10091 0.155547 8.19303 0.0990282 8.29852C0.042509 8.40402 0.00931573 8.52042 0.00169531 8.63986C-0.00592512 8.7593 0.0122057 8.87898 0.0548596 8.9908C0.0975136 9.10262 0.163695 9.20397 0.248924 9.28799C0.334153 9.37201 0.436439 9.43674 0.548858 9.47779C0.661277 9.51884 0.781204 9.53526 0.90052 9.52594C1.01984 9.51661 1.13576 9.48176 1.24043 9.42374C1.34511 9.36572 1.43609 9.28588 1.50723 9.18964L7.56404 3.18296V19.1646C7.56404 19.3861 7.65206 19.5986 7.80873 19.7553C7.9654 19.912 8.1779 20 8.39946 20C8.62103 20 8.83352 19.912 8.9902 19.7553C9.14687 19.5986 9.23488 19.3861 9.23488 19.1646V3.18296L15.2917 9.18964C15.449 9.34585 15.6619 9.43316 15.8836 9.43238C16.1053 9.43159 16.3176 9.34278 16.4738 9.18546C16.63 9.02815 16.7173 8.81523 16.7166 8.59354C16.7158 8.37185 16.627 8.15955 16.4696 8.00334V8.0284Z",
|
||||||
|
}
|
||||||
4
.gitea/workflows/src/.figma_internal/svg-k5kyhbfrwc.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export default {
|
||||||
|
p177af00: "M24.7045 12.0426L12.5992 0L0.493934 12.0426C0.35111 12.1514 0.233321 12.2895 0.148542 12.4478C0.0637634 12.606 0.0139736 12.7806 0.00254296 12.9598C-0.00888768 13.1389 0.0183085 13.3185 0.0822895 13.4862C0.14627 13.6539 0.245543 13.806 0.373386 13.932C0.501229 14.058 0.654659 14.1551 0.823287 14.2167C0.991916 14.2783 1.17181 14.3029 1.35078 14.2889C1.52975 14.2749 1.70363 14.2226 1.86065 14.1356C2.01766 14.0486 2.15414 13.9288 2.26085 13.7845L11.3461 4.77444V28.7469C11.3461 29.0792 11.4781 29.398 11.7131 29.633C11.9481 29.868 12.2668 30 12.5992 30C12.9315 30 13.2503 29.868 13.4853 29.633C13.7203 29.398 13.8523 29.0792 13.8523 28.7469V4.77444L22.9375 13.7845C23.1735 14.0188 23.4929 14.1497 23.8254 14.1486C24.158 14.1474 24.4764 14.0142 24.7107 13.7782C24.945 13.5422 25.076 13.2228 25.0748 12.8903C25.0737 12.5578 24.9404 12.2393 24.7045 12.005V12.0426Z",
|
||||||
|
pac4fa00: "M16.4696 8.0284L8.39946 0L0.329289 8.0284C0.234073 8.10091 0.155547 8.19303 0.0990282 8.29852C0.042509 8.40402 0.00931573 8.52042 0.00169531 8.63986C-0.00592512 8.7593 0.0122057 8.87898 0.0548596 8.9908C0.0975136 9.10262 0.163695 9.20397 0.248924 9.28799C0.334153 9.37201 0.436439 9.43674 0.548858 9.47779C0.661277 9.51884 0.781204 9.53526 0.90052 9.52594C1.01984 9.51661 1.13576 9.48176 1.24043 9.42374C1.34511 9.36572 1.43609 9.28588 1.50723 9.18964L7.56404 3.18296V19.1646C7.56404 19.3861 7.65206 19.5986 7.80873 19.7553C7.9654 19.912 8.1779 20 8.39946 20C8.62103 20 8.83352 19.912 8.9902 19.7553C9.14687 19.5986 9.23488 19.3861 9.23488 19.1646V3.18296L15.2917 9.18964C15.449 9.34585 15.6619 9.43316 15.8836 9.43238C16.1053 9.43159 16.3176 9.34278 16.4738 9.18546C16.63 9.02815 16.7173 8.81523 16.7166 8.59354C16.7158 8.37185 16.627 8.15955 16.4696 8.00334V8.0284Z",
|
||||||
|
}
|
||||||
4
.gitea/workflows/src/.figma_internal/svg-sphymsws2o.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export default {
|
||||||
|
p177af00: "M24.7045 12.0426L12.5992 0L0.493934 12.0426C0.35111 12.1514 0.233321 12.2895 0.148542 12.4478C0.0637634 12.606 0.0139736 12.7806 0.00254296 12.9598C-0.00888768 13.1389 0.0183085 13.3185 0.0822895 13.4862C0.14627 13.6539 0.245543 13.806 0.373386 13.932C0.501229 14.058 0.654659 14.1551 0.823287 14.2167C0.991916 14.2783 1.17181 14.3029 1.35078 14.2889C1.52975 14.2749 1.70363 14.2226 1.86065 14.1356C2.01766 14.0486 2.15414 13.9288 2.26085 13.7845L11.3461 4.77444V28.7469C11.3461 29.0792 11.4781 29.398 11.7131 29.633C11.9481 29.868 12.2668 30 12.5992 30C12.9315 30 13.2503 29.868 13.4853 29.633C13.7203 29.398 13.8523 29.0792 13.8523 28.7469V4.77444L22.9375 13.7845C23.1735 14.0188 23.4929 14.1497 23.8254 14.1486C24.158 14.1474 24.4764 14.0142 24.7107 13.7782C24.945 13.5422 25.076 13.2228 25.0748 12.8903C25.0737 12.5578 24.9404 12.2393 24.7045 12.005V12.0426Z",
|
||||||
|
pac4fa00: "M16.4696 8.0284L8.39946 0L0.329289 8.0284C0.234073 8.10091 0.155547 8.19303 0.0990282 8.29852C0.042509 8.40402 0.00931573 8.52042 0.00169531 8.63986C-0.00592512 8.7593 0.0122057 8.87898 0.0548596 8.9908C0.0975136 9.10262 0.163695 9.20397 0.248924 9.28799C0.334153 9.37201 0.436439 9.43674 0.548858 9.47779C0.661277 9.51884 0.781204 9.53526 0.90052 9.52594C1.01984 9.51661 1.13576 9.48176 1.24043 9.42374C1.34511 9.36572 1.43609 9.28588 1.50723 9.18964L7.56404 3.18296V19.1646C7.56404 19.3861 7.65206 19.5986 7.80873 19.7553C7.9654 19.912 8.1779 20 8.39946 20C8.62103 20 8.83352 19.912 8.9902 19.7553C9.14687 19.5986 9.23488 19.3861 9.23488 19.1646V3.18296L15.2917 9.18964C15.449 9.34585 15.6619 9.43316 15.8836 9.43238C16.1053 9.43159 16.3176 9.34278 16.4738 9.18546C16.63 9.02815 16.7173 8.81523 16.7166 8.59354C16.7158 8.37185 16.627 8.15955 16.4696 8.00334V8.0284Z",
|
||||||
|
}
|
||||||
165
.gitea/workflows/src/App.tsx
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import { Route, Routes, useNavigate } from "react-router-dom";
|
||||||
|
import { OurExpertise } from "./components/about/OurExpertise";
|
||||||
|
import { OurImpact } from "./components/about/OurImpact";
|
||||||
|
import { OurVision } from "./components/about/OurVision";
|
||||||
|
import { AIChatbot } from "./components/AIChatbot";
|
||||||
|
import { Articles } from "./components/Articles";
|
||||||
|
import { AuthProvider } from "./components/AuthContext";
|
||||||
|
import { BlogDetail } from "./components/BlogDetail";
|
||||||
|
import { Blogs } from "./components/Blogs";
|
||||||
|
import { Cart } from "./components/Cart";
|
||||||
|
import { CartProvider } from "./components/CartContext";
|
||||||
|
import { Contact } from "./components/Contact";
|
||||||
|
import { CorporateSignIn } from "./components/CorporateSignIn";
|
||||||
|
import { CorporateSignUp } from "./components/CorporateSignUp";
|
||||||
|
import { Footer } from "./components/Footer";
|
||||||
|
import { LeadershipJourneyPage } from "./components/LeadershipJourneyPage";
|
||||||
|
import { LearningOnline } from "./components/LearningOnline";
|
||||||
|
import { Navigation } from "./components/Navigation";
|
||||||
|
import { ProgrammeDetail } from "./components/ProgrammeDetail";
|
||||||
|
import { SelfLearnerSignIn } from "./components/SelfLearnerSignIn";
|
||||||
|
import { SelfLearnerSignUp } from "./components/SelfLearnerSignUp";
|
||||||
|
import { Consulting } from "./components/services/Consulting";
|
||||||
|
import { CultureCompetence } from "./components/services/CultureCompetence";
|
||||||
|
import { ExecutiveCoaching } from "./components/services/ExecutiveCoaching";
|
||||||
|
import { LearningFacility } from "./components/services/LearningFacility";
|
||||||
|
import { ManagementDevelopment } from "./components/services/ManagementDevelopment";
|
||||||
|
import { Terms } from "./components/Terms";
|
||||||
|
import { Webinars } from "./components/Webinars";
|
||||||
|
import WebinarsListing from "./components/WebinarsListing";
|
||||||
|
import { WebinarsPage } from "./components/WebinarsPage";
|
||||||
|
import HomePage from './pages/HomePage';
|
||||||
|
import { AboutUs } from './components/AboutUs';
|
||||||
|
import { Services } from './components/Services';
|
||||||
|
import { LearningFacilityNew } from './components/LearningFacilityNew';
|
||||||
|
import { FooterNew } from './components/FooterNew';
|
||||||
|
import { Privacy } from "./pages/Privacy";
|
||||||
|
import { TermsCondition } from "./pages/TermsCondition";
|
||||||
|
import { FAQ } from "./pages/FAQ";
|
||||||
|
import { LeadershipPipelineDevelopment } from "./components/services/LeadershipPipelineDevelopment";
|
||||||
|
import { LeadershipDevelopment } from "./components/services/LeadershipDevelopment";
|
||||||
|
import { KautilyaFacility } from "./components/KautilyaFacility";
|
||||||
|
// import EnrollPlaceholder from "./components/EnrollPlaceholder";
|
||||||
|
// import ForgotPasswordPlaceholder from "./components/ForgotPasswordPlaceholder";
|
||||||
|
// import DashboardPlaceholder from "./components/DashboardPlaceholder";
|
||||||
|
// import CheckoutPlaceholder from "./components/CheckoutPlaceholder";
|
||||||
|
// import HomePage from "./components/HomePage";
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return (
|
||||||
|
<AuthProvider>
|
||||||
|
<CartProvider>
|
||||||
|
<div
|
||||||
|
className="min-h-screen main-content"
|
||||||
|
style={{ backgroundColor: '#FFFFFF' }}
|
||||||
|
>
|
||||||
|
|
||||||
|
<Navigation />
|
||||||
|
<Routes>
|
||||||
|
{/* Home Page */}
|
||||||
|
<Route path="/" element={<HomePage />} />
|
||||||
|
{/* Leadership Journey */}
|
||||||
|
<Route path="/leadership-journey" element={<LeadershipJourneyPage />} />
|
||||||
|
|
||||||
|
{/* Services Pages */}
|
||||||
|
<Route path="/services/leadership-pipeline-development" element={<LeadershipPipelineDevelopment />} />
|
||||||
|
<Route path="/services/leadership-development" element={<LeadershipDevelopment />} />
|
||||||
|
<Route path="/services/consulting" element={<Consulting />} />
|
||||||
|
<Route path="/services/culture-competence" element={<CultureCompetence />} />
|
||||||
|
<Route path="/services/executive-coaching" element={<ExecutiveCoaching />} />
|
||||||
|
<Route path="/services/management-development" element={<ManagementDevelopment />} />
|
||||||
|
<Route path="/services/learning-facility" element={<LearningFacility />} />
|
||||||
|
<Route path="/services" element={<Services />} />
|
||||||
|
<Route path="/services/kautilya-facility" element={<KautilyaFacility />} />
|
||||||
|
|
||||||
|
{/* About Us Pages */}
|
||||||
|
<Route path="/about/our-vision" element={<OurVision />} />
|
||||||
|
<Route path="/about/our-expertise" element={<OurExpertise />} />
|
||||||
|
<Route path="/about/our-impact" element={<OurImpact />} />
|
||||||
|
<Route path="/about-us" element={<AboutUs />} />
|
||||||
|
|
||||||
|
{/* Learning Pages */}
|
||||||
|
<Route path="/learning/articles" element={<Articles />} />
|
||||||
|
<Route path="/learning/blogs" element={<Blogs />} />
|
||||||
|
<Route path="/learning/webcast" element={<Webinars />} />
|
||||||
|
|
||||||
|
{/* Webinars Pages */}
|
||||||
|
<Route path="/webinars" element={<WebinarsPage />} />
|
||||||
|
<Route path="/webinars-legacy" element={<WebinarsListing />} />
|
||||||
|
|
||||||
|
{/* Learning Online */}
|
||||||
|
<Route path="/learning-online" element={<LearningOnline />} />
|
||||||
|
|
||||||
|
{/* Terms & Conditions */}
|
||||||
|
<Route path="/terms" element={<Terms />} />
|
||||||
|
|
||||||
|
{/* Cart */}
|
||||||
|
<Route path="/cart" element={<Cart />} />
|
||||||
|
|
||||||
|
{/* Authentication Pages */}
|
||||||
|
<Route path="/corporate-login" element={<CorporateSignIn />} />
|
||||||
|
<Route path="/self-learner-signin" element={<SelfLearnerSignIn />} />
|
||||||
|
<Route path="/signin/self-learner" element={<SelfLearnerSignIn />} />
|
||||||
|
<Route path="/corporate-signup" element={<CorporateSignUp />} />
|
||||||
|
<Route path="/self-learner-signup" element={<SelfLearnerSignUp />} />
|
||||||
|
|
||||||
|
{/* Contact Page */}
|
||||||
|
<Route path="/contact" element={<Contact />} />
|
||||||
|
|
||||||
|
{/* Dynamic Routes */}
|
||||||
|
<Route path="/learning/articles/:slugAndId" element={<BlogDetail />} />
|
||||||
|
{/* <Route path="/learning/blogs/:slug" element={<BlogDetail />} /> */}
|
||||||
|
{/* <Route path="/learning/webcast/:slug" element={<WebinarDetail />} />
|
||||||
|
<Route path="/webinar/:slug" element={<WebinarDetail />} /> */}
|
||||||
|
<Route path="/course/:slug" element={<ProgrammeDetail />} />
|
||||||
|
<Route path="/programme/:slug" element={<ProgrammeDetail />} />
|
||||||
|
|
||||||
|
{/* Learning Facility */}
|
||||||
|
<Route path="/learning-facility" element={<LearningFacilityNew />} />
|
||||||
|
{/* Privacy policy */}
|
||||||
|
<Route path="/privacy-policy" element={<Privacy />} />
|
||||||
|
<Route path="/term-condition" element={<TermsCondition />} />
|
||||||
|
<Route path="/faq" element={<FAQ />} />
|
||||||
|
|
||||||
|
{/* Placeholder Pages */}
|
||||||
|
{/* <Route path="/enroll" element={<EnrollPlaceholder />} />
|
||||||
|
<Route path="/forgot-password" element={<ForgotPasswordPlaceholder />} />
|
||||||
|
<Route path="/dashboard" element={<DashboardPlaceholder />} />
|
||||||
|
<Route path="/checkout" element={<CheckoutPlaceholder />} /> */}
|
||||||
|
|
||||||
|
{/* 404 Page */}
|
||||||
|
<Route path="*" element={<NotFound />} />
|
||||||
|
</Routes>
|
||||||
|
|
||||||
|
<FooterNew />
|
||||||
|
|
||||||
|
{/* Add AIChatbot to all pages */}
|
||||||
|
<AIChatbot />
|
||||||
|
</div>
|
||||||
|
</CartProvider>
|
||||||
|
</AuthProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 404 Not Found Component
|
||||||
|
function NotFound() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center" style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
<div className="text-center">
|
||||||
|
<h1 className="text-h2 mb-4">Page Not Found</h1>
|
||||||
|
<p className="text-body-lg text-muted mb-8">
|
||||||
|
The page you're looking for doesn't exist.
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<button
|
||||||
|
onClick={() => navigate('/')}
|
||||||
|
className="brand-button-system"
|
||||||
|
>
|
||||||
|
Back to Home
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
.gitea/workflows/src/Attributions.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
This Figma Make file includes components from [shadcn/ui](https://ui.shadcn.com/) used under [MIT license](https://github.com/shadcn-ui/ui/blob/main/LICENSE.md).
|
||||||
|
|
||||||
|
This Figma Make file includes photos from [Unsplash](https://unsplash.com) used under [license](https://unsplash.com/license).
|
||||||
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 25 KiB |
BIN
.gitea/workflows/src/assets/Aparna-Nair.png
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
.gitea/workflows/src/assets/Balaji-Chandrakumar.jpeg
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
.gitea/workflows/src/assets/Campus1.jpg
Normal file
|
After Width: | Height: | Size: 3.7 MiB |
BIN
.gitea/workflows/src/assets/Campus2.jpg
Normal file
|
After Width: | Height: | Size: 3.7 MiB |
BIN
.gitea/workflows/src/assets/Campus3.jpg
Normal file
|
After Width: | Height: | Size: 6.4 MiB |
BIN
.gitea/workflows/src/assets/Campus4.jpg
Normal file
|
After Width: | Height: | Size: 4.2 MiB |
BIN
.gitea/workflows/src/assets/Campus5.jpg
Normal file
|
After Width: | Height: | Size: 4.3 MiB |
BIN
.gitea/workflows/src/assets/Campus6.jpg
Normal file
|
After Width: | Height: | Size: 3.6 MiB |
BIN
.gitea/workflows/src/assets/Classroom1.jpg
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
BIN
.gitea/workflows/src/assets/Classroom2.jpg
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
.gitea/workflows/src/assets/Classroom3.jpg
Normal file
|
After Width: | Height: | Size: 3.9 MiB |
BIN
.gitea/workflows/src/assets/Classroom4.jpg
Normal file
|
After Width: | Height: | Size: 3.4 MiB |
BIN
.gitea/workflows/src/assets/Classroom5.jpg
Normal file
|
After Width: | Height: | Size: 3.5 MiB |
BIN
.gitea/workflows/src/assets/Diju.jpeg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
.gitea/workflows/src/assets/Eco-Conscious Design.jpg
Normal file
|
After Width: | Height: | Size: 6.4 MiB |
BIN
.gitea/workflows/src/assets/Fitness&Recreation-centre.jpg
Normal file
|
After Width: | Height: | Size: 3.8 MiB |
BIN
.gitea/workflows/src/assets/K-Ramkumar.png
Normal file
|
After Width: | Height: | Size: 457 KiB |
BIN
.gitea/workflows/src/assets/Kautilya.png
Normal file
|
After Width: | Height: | Size: 363 KiB |
BIN
.gitea/workflows/src/assets/R-Muralidharan.png
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
.gitea/workflows/src/assets/Ramesh-Padmanabhan.jpeg
Normal file
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
1
.gitea/workflows/src/assets/accenture.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg height="658" viewBox="0 0 163.4 43" width="2500" xmlns="http://www.w3.org/2000/svg"><path d="m95.1 12 9.4-3.5-9.4-3.6v-4.9l16.1 6.5v4l-16.1 6.5z" fill="#a100ff"/><path d="m6.2 43c-3.4 0-6.2-1.7-6.2-5.5v-.2c0-4.6 4-6.2 8.9-6.2h2.3v-.9c0-1.9-.8-3-2.8-3-1.8 0-2.7 1-2.8 2.4h-5c.4-4.2 3.7-6.2 8.1-6.2 4.5 0 7.8 1.9 7.8 6.6v12.6h-5.1v-2.2c-1 1.4-2.7 2.6-5.2 2.6zm5-6.6v-1.8h-2.1c-2.6 0-3.9.7-3.9 2.4v.2c0 1.3.8 2.2 2.6 2.2 1.8-.1 3.4-1.1 3.4-3zm17.2 6.6c-5.2 0-9-3.2-9-9.6v-.3c0-6.4 4-9.8 9-9.8 4.3 0 7.8 2.2 8.2 7.1h-5c-.3-1.8-1.3-3-3.1-3-2.2 0-3.8 1.8-3.8 5.5v.6c0 3.8 1.4 5.5 3.8 5.5 1.8 0 3.1-1.3 3.4-3.4h4.8c-.3 4.4-3.2 7.4-8.3 7.4zm19.6 0c-5.2 0-9-3.2-9-9.6v-.3c0-6.4 4-9.8 9-9.8 4.3 0 7.8 2.2 8.2 7.1h-5c-.3-1.8-1.3-3-3.1-3-2.2 0-3.8 1.8-3.8 5.5v.6c0 3.8 1.4 5.5 3.8 5.5 1.8 0 3.1-1.3 3.4-3.4h4.8c-.3 4.4-3.2 7.4-8.3 7.4zm19.7 0c-5.4 0-9.1-3.2-9.1-9.5v-.4c0-6.3 3.9-9.8 9-9.8 4.7 0 8.6 2.6 8.6 8.9v2.3h-12.3c.2 3.4 1.7 4.7 3.9 4.7 2 0 3.1-1.1 3.5-2.4h4.9c-.6 3.5-3.6 6.2-8.5 6.2zm-3.7-12h7c-.1-2.8-1.4-4-3.5-4-1.6.1-3.1 1-3.5 4zm15.4-7.2h5.3v2.8c.9-1.8 2.8-3.2 5.7-3.2 3.4 0 5.7 2.1 5.7 6.6v12.6h-5.3v-11.8c0-2.2-.9-3.2-2.8-3.2-1.8 0-3.3 1.1-3.3 3.5v11.5h-5.3zm26.4-5.7v5.7h3.6v3.9h-3.6v8.9c0 1.4.6 2.1 1.9 2.1.8 0 1.3-.1 1.8-.3v4.1c-.6.2-1.7.4-3 .4-4.1 0-6-1.9-6-5.7v-9.5h-2.2v-3.9h2.2v-3.5zm23.4 24.5h-5.2v-2.8c-.9 1.8-2.7 3.2-5.5 3.2-3.4 0-5.9-2.1-5.9-6.5v-12.7h5.3v12c0 2.2.9 3.2 2.7 3.2s3.3-1.2 3.3-3.5v-11.7h5.3zm3.9-18.8h5.3v3.5c1.1-2.5 2.9-3.7 5.7-3.7v5.2c-3.6 0-5.7 1.1-5.7 4.2v9.7h-5.3zm21.7 19.2c-5.4 0-9.1-3.2-9.1-9.5v-.4c0-6.3 3.9-9.8 9-9.8 4.7 0 8.6 2.6 8.6 8.9v2.3h-12.2c.2 3.4 1.7 4.7 3.9 4.7 2 0 3.1-1.1 3.5-2.4h4.9c-.8 3.5-3.7 6.2-8.6 6.2zm-3.8-12h7.1c-.1-2.8-1.4-4-3.5-4-1.6.1-3.1 1-3.6 4z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
1
.gitea/workflows/src/assets/adani-logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg height="789" width="2500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496.68700000000007 156.71"><linearGradient id="a" x2="1" y1=".5" y2=".5"><stop offset="0" stop-color="#1094c4"/><stop offset=".447" stop-color="#473aff"/><stop offset=".725" stop-color="#7a00fd"/><stop offset="1" stop-color="#bf1969"/></linearGradient><path d="M433.387 12.698c0 7.068 5.4 13.114 12.9 13.114 7.127 0 12.713-6.046 12.713-13.114C459 5.576 453.412 0 446.288 0c-7.5 0-12.9 5.576-12.9 12.698zm-252.9-.03v32.235c-7.087-3.206-17.812-6.214-28.162-6.214-27.713 0-43.613 16.53-45.975 43.2-.638 7.755-.638 23.003 0 30.533 2.362 26.437 19.988 44.288 49.013 44.288 20.85 0 38.25-9.252 43.836-13.35 3.452-2.349 4.089-4.733 4.089-8.6V12.668c0-6.436-4.951-11.606-11.401-11.606-6.225 0-11.4 5.17-11.4 11.606zm-51.375 96.949c-.6-6.672-.412-19.34 0-25.144.863-15.69 8.625-23.861 24.938-23.861 11.212 0 21.937 3.866 26.438 6.667v59.741c-4.276 3.244-15.451 7.74-24.713 7.74-16.536 0-25.35-8.602-26.663-25.143zM328.125 50.49c-3.225 2.168-4.539 4.324-4.539 8.81l.001 84.483c0 6.445 5.4 11.633 11.437 11.633 6.413-.001 11.363-5.188 11.363-11.633l.001-77.145c4.913-3.026 15.036-6.026 25.574-6.026 13.35 0 24.975 7.308 24.975 24.506v58.665c0 6.445 5.325 11.633 11.362 11.633 6.676-.001 11.4-5.188 11.4-11.633V84.049c0-29.036-15.9-45.36-47.062-45.36-22.575 0-38.288 7.113-44.513 11.801h.001zm-97.124-5.38c-5.814 2.594-7.539 6.892-7.539 10.566 0 6.657 6.225 11.603 15.712 8.58 6.002-2.137 14.587-4.71 23.401-4.71 16.762 0 23.888 6.214 23.888 20.847v7.072c-9.9-2.347-17.25-3.618-27.339-3.618-30.074 0-43.612 16.988-43.612 35.666.002 21.063 14.587 37.197 44.063 37.197 22.35 0 38.7-7.973 44.737-12.054 3.225-2.16 4.05-4.496 4.05-8.366V80.393c0-29.047-16.95-41.704-44.474-41.704-13.124 0-26.663 3.428-32.888 6.42zm6.862 73.56c0-10.321 9.45-16.164 22.574-16.164 9.677 0 16.537 1.309 26.026 3.665v23.875c-2.174 1.71-10.125 6.669-24.975 6.669-15.226 0-23.627-6.45-23.627-18.046h.001zM15.413 45.11c-5.776 2.594-7.538 6.892-7.538 10.566 0 6.657 6.263 11.603 15.711 8.58 6.04-2.144 14.664-4.717 23.439-4.717 16.764 0 23.85 6.221 23.85 20.854v7.072c-9.9-2.347-17.176-3.618-27.3-3.618C14.55 83.847.899 99.687 0 117.589v4.016c.938 20.03 15.525 35.105 43.988 35.105 22.387 0 38.699-7.973 44.737-12.054 3.226-2.16 4.087-4.496 4.087-8.366l.002-55.897c0-29.047-16.99-41.704-44.514-41.704-13.087 0-26.626 3.428-32.888 6.42zm6.9 73.56c0-10.329 9.45-16.164 22.537-16.164 9.675 0 16.575 1.309 26.025 3.665v23.875c-2.138 1.71-10.088 6.669-24.938 6.669-15.261 0-23.625-6.45-23.625-18.046zm412.576-66.428v91.987c0 6.458 5.398 11.644 11.399 11.644 6.45 0 11.436-5.186 11.436-11.644l.001-91.987c0-6.44-5.175-11.6-11.438-11.6-6.45 0-11.4 5.16-11.4 11.6h.002z" fill="url(#a)"/><path d="M486.075 7.46l2.776 10.536 2.774-10.537c.225-.717.674-1.272 1.425-1.272.864 0 1.462.5 1.612 1.44l1.913 10.812c.112.498-.15 1.05-.675 1.17-.563.113-.938-.282-1.012-.776l-1.763-10.878-2.736 10.162c-.225.876-.751 1.44-1.54 1.44-.823 0-1.35-.503-1.612-1.44L484.5 8.014l-1.761 10.876c-.039.501-.452.832-1.052.72-.487-.121-.712-.617-.6-1.11l1.839-10.872c.188-.942.824-1.44 1.574-1.44s1.35.439 1.574 1.271h.001zm-9.711 11.207c0 .503-.452.89-.827.89-.523 0-.9-.45-.9-.889l.001-10.654h-3.824a.806.806 0 0 1-.827-.832c0-.44.375-.837.825-.837h9.264c.488 0 .899.397.899.837 0 .503-.413.832-.9.832h-3.711v10.653z"/></svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
1
.gitea/workflows/src/assets/axis-bank.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg height="-358" viewBox="-.04 195.13 511.99 121.87" width="2500" xmlns="http://www.w3.org/2000/svg"><path d="m70.91 195.13c7.29 12.48 14.58 24.82 21.6 37.3-11.5 20.33-23 40.81-34.92 61-4.63 7.71-8.98 15.57-13.6 23.28-14.72.14-29.45.14-44.03 0 .42-.98.84-1.96 1.4-2.94 23.27-39.55 46.55-78.95 69.55-118.64zm319.74 30.57c8.13 15.15 16.27 30.29 24.4 45.44-2.8 0-5.75-.14-8.55 0-1.96-3.37-3.65-6.87-5.47-10.24-7.15-.28-14.44 0-21.6-.14-1.68 3.51-3.65 6.87-5.47 10.24h-8.41c8.13-15.15 16.4-30.29 25.1-45.3m-7.72 28.75c4.91 0 9.82 0 14.72.14-2.52-4.63-4.63-9.26-7.29-13.74a316.29 316.29 0 0 0 -7.43 13.6zm36.74-28.89c12.2 10.66 24.54 21.32 36.6 32.11.28-9.82 0-19.49.28-29.31 2.66 0 5.19.14 7.85.14-.14 15.01 0 30.15 0 45.16-7.99-6.73-15.85-13.74-23.7-20.61-4.49-3.79-8.83-7.57-13.04-11.64-.14 9.96 0 19.77-.14 29.73h-7.85c0-15.29-.14-30.43 0-45.58zm-259.15 45.44c8.41-15.15 16.69-30.43 25.38-45.44 8.13 15.15 16.13 30.29 24.4 45.44h-8.69a233.5 233.5 0 0 1 -5.19-10.24c-7.29-.14-14.44 0-21.74 0-1.82 3.37-3.65 6.87-5.61 10.24-2.94.14-5.75.14-8.55 0m17.67-16.55h14.86c-2.52-4.63-4.63-9.26-7.29-13.74-2.66 4.62-5.05 9.11-7.57 13.74zm102.93-24.82c4.21-2.1 8.98-2.24 13.46-1.4 3.93.84 7.01 3.65 9.4 6.59-2.1 1.12-4.21 2.1-6.31 3.23-2.8-4.35-9.4-5.75-13.46-2.38-2.1 1.54-1.96 5.33.42 6.45 5.47 2.94 12.06 3.65 16.83 7.71 4.91 4.21 4.77 12.48.14 16.83-4.91 4.91-12.62 6.03-19.07 4.21-5.47-1.4-9.54-6.31-10.24-11.78 2.66-.56 5.33-.98 7.99-1.4.28 2.52 1.26 5.47 3.79 6.73 3.79 2.1 9.4 1.54 12.06-2.24 1.82-2.52 1.26-6.45-1.26-8.27-5.33-3.51-12.34-3.79-17.25-8.27-2.52-2.1-3.51-5.75-2.66-8.98.69-3.25 3.36-5.63 6.16-7.03zm-68.29-.98c2.94-.42 6.03-.28 8.98-.28 3.23 4.77 6.45 9.54 9.54 14.3 3.37-4.77 6.59-9.54 9.96-14.16 3.09-.14 6.17-.14 9.26 0-4.91 6.73-9.82 13.46-14.58 20.33 4.91 7.57 10.38 14.72 15.29 22.44h-8.83c-3.79-5.33-7.43-10.8-11.08-16.13-3.79 5.33-7.71 10.66-11.5 16.13h-8.83c5.19-7.57 10.52-14.86 15.99-22.3-4.81-6.87-9.57-13.6-14.2-20.33zm45.01-.15a73.4 73.4 0 0 1 7.85 0v42.63h-7.99c.14-14.3 0-28.46.14-42.63zm76.99-.14c6.73.28 14.02-.98 20.33 2.38 6.03 3.23 7.43 12.9 1.82 17.25 3.51 1.4 7.01 4.07 7.85 7.85 1.54 4.77-1.12 9.96-5.33 12.48-7.57 4.21-16.41 2.24-24.68 2.8-.13-14.15.01-28.45.01-42.76m7.86 6.18v12.2c2.94-.14 6.17 0 8.69-1.82 2.52-2.24 2.52-7.01-.42-8.98-2.38-1.55-5.47-1.4-8.27-1.4m0 17.8v12.48c3.93-.14 8.13.56 11.78-1.4 3.23-1.54 3.79-6.73.84-8.83-3.51-2.95-8.28-2.25-12.62-2.25zm130.98-23.98h7.85v16.83c6.45-5.75 13.04-11.22 19.63-16.97h10.8c-7.71 6.31-15.43 12.76-23.14 19.35 7.57 7.99 15.29 15.57 23 23.28-3.51 0-7.15.28-10.66-.14-6.17-6.17-12.2-12.48-18.23-18.79l-1.26 1.68c-.14 5.75 0 11.5-.14 17.25h-7.99c0-14.02 0-28.18.14-42.49zm-402.76 42.5c14.72 0 29.45-.14 44.17.14 8.83 15.29 17.81 30.43 26.5 45.86-14.72.14-29.45 0-44.17 0-8.69-15.43-17.81-30.58-26.5-46z" fill="#ae285d"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.7 KiB |
BIN
.gitea/workflows/src/assets/boardroom.jpg
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 14 KiB |
BIN
.gitea/workflows/src/assets/campus-arial-view.jpg
Normal file
|
After Width: | Height: | Size: 4.6 MiB |
1
.gitea/workflows/src/assets/ceat-logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg enable-background="new 0 0 438 151" viewBox="0 0 438 151" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h438v151h-438zm45.2 23.3c-14.2 6.9-25.7 19.5-30.2 34.8-4.4 15.5-3.4 32.7 3.6 47.2 7.5 15 21.9 26.3 38.3 30 12.2 2.7 25.1 2.5 37.1-1.1 12.4-3.7 22.4-12.2 31.4-21.1-6.2-6.5-12.5-13-18.8-19.5-7.3 7.5-15.8 15.1-26.7 16.6-11.6 2-24.5-1.8-32.3-10.8-11.4-13.4-10.5-36 3.1-47.6 8-7.5 19.7-9.9 30.2-7.7 10.2 1.9 18.6 8.7 25.4 16.1 6.5-6 12.8-12.2 19.3-18.2-8.5-10.7-20.2-18.9-33.4-22.6-15.3-4.7-32.5-3.4-47 3.9m91-4.8c-.4 8.8-.1 17.7-.2 26.5h83.7c-.1-8.8.1-17.7-.1-26.5-27.8 0-55.6 0-83.4 0m136.9.1c-.3 1.1-.7 2.3-1 3.4-15.1 37.7-30.3 75.4-45.1 113.2h30.3c8.4-21.2 17-42.3 25.5-63.4 1.6-3.7 2.8-7.6 4.8-11 9.9 24.8 20 49.6 29.9 74.4 10.1.1 20.2 0 30.3 0-15.2-39-31.1-77.7-46.4-116.6-9.6 0-18.9-.1-28.3 0m57.3 27.8c10.6-.1 21.1 0 31.7 0v88.2h27.9c.2-29.4-.1-58.8.1-88.2h31.6c0-9.3 0-18.6 0-27.9-30.5.1-60.9 0-91.3 0-.1 9.4-.1 18.7 0 27.9m-194.5 16.9c.3 8.8-.2 17.5.3 26.3 27.8-.2 55.6-.1 83.4 0 .1-8.8 0-17.6 0-26.3-27.8 0-55.8-.2-83.7 0m.1 45.5c0 8.8-.2 17.7.1 26.5 27.8-.1 55.6-.1 83.4 0 .4-8.9.1-17.7.2-26.6-27.9 0-55.8-.1-83.7.1z" fill="#203f99"/><path d="m45.2 23.3c14.5-7.3 31.7-8.6 47.1-3.9 13.2 3.8 24.9 12 33.4 22.7-6.5 6-12.8 12.2-19.3 18.2-6.8-7.5-15.1-14.3-25.3-16.2-10.6-2.2-22.3.2-30.2 7.7-13.7 11.5-14.6 34.2-3.2 47.6 7.9 9 20.7 12.8 32.3 10.8 10.9-1.4 19.3-9 26.7-16.6 6.2 6.5 12.5 13 18.8 19.5-9 8.8-19.1 17.4-31.4 21.1-12 3.5-24.9 3.8-37 1.1-16.4-3.6-30.8-15-38.3-30-7.1-14.5-8.1-31.8-3.6-47.2 4.2-15.3 15.8-27.9 30-34.8zm227.9-4.7c9.4-.1 18.7 0 28.1 0 15.2 39 31.1 77.7 46.4 116.6-10.1-.1-20.2.1-30.3 0-9.9-24.9-20-49.6-29.9-74.4-2 3.5-3.2 7.4-4.8 11-8.5 21.2-17 42.3-25.5 63.4-10.1 0-20.2 0-30.3 0 15-37.8 30.2-75.5 45.3-113.2.3-1.1.7-2.2 1-3.4zm57.3 27.9c-.1-9.3 0-18.6 0-27.9 30.4 0 60.9.1 91.3 0v27.9c-10.5-.1-21.1 0-31.6 0-.2 29.4 0 58.8-.1 88.2-9.3 0-18.6 0-27.9 0 0-29.4 0-58.8 0-88.2-10.6-.1-21.2-.1-31.7 0z" fill="#fff"/><path d="m136.2 18.5c27.8 0 55.6.1 83.4 0 .3 8.8.1 17.7.1 26.5-27.9 0-55.8 0-83.7 0 .1-8.9-.2-17.7.2-26.5zm-.3 44.8c27.9-.2 55.9 0 83.8-.1 0 8.8.1 17.5 0 26.3-27.8-.1-55.6-.1-83.4 0-.6-8.7-.1-17.4-.4-26.2zm.1 45.5c27.9-.3 55.8-.2 83.7-.1-.1 8.9.2 17.7-.2 26.6-27.8-.1-55.6-.1-83.4 0-.3-8.7 0-17.6-.1-26.5z" fill="#f04f23"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 14 KiB |
BIN
.gitea/workflows/src/assets/dining-experience.jpg
Normal file
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 23 KiB |
BIN
.gitea/workflows/src/assets/exe-boardroom.jpg
Normal file
|
After Width: | Height: | Size: 4.7 MiB |
BIN
.gitea/workflows/src/assets/expert-support.jpg
Normal file
|
After Width: | Height: | Size: 3.9 MiB |
1
.gitea/workflows/src/assets/flipkart.svg
Normal file
|
After Width: | Height: | Size: 21 KiB |
1
.gitea/workflows/src/assets/hsbc.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="0 0 315.9 85.1" xmlns="http://www.w3.org/2000/svg" width="2500" height="673"><path d="M42.6 0h85v85h-85z" fill="#fff"/><g fill="#db0011"><path d="M170.1 42.6L127.6 0v85.1zM85.1 42.6L127.6 0h-85z"/><path d="M0 42.6l42.6 42.5V0zM85.1 42.6L42.6 85.1h85z"/></g><path d="M207.4 45.1H192v15.2h-7.7V24.7h7.7v14.6h15.4V24.7h7.7v35.6h-7.7zM233.7 61c-7.7 0-14-3.1-14.1-11.6h7.7c.1 3.8 2.3 6.1 6.5 6.1 3.1 0 6.7-1.6 6.7-5.1 0-2.8-2.4-3.6-6.4-4.8l-2.6-.7c-5.6-1.6-11.2-3.8-11.2-10.2 0-7.9 7.4-10.6 14.1-10.6 6.9 0 12.9 2.4 13 10.3h-7.7c-.3-3.2-2.2-5.1-5.8-5.1-2.9 0-5.7 1.5-5.7 4.7 0 2.6 2.4 3.4 7.4 5l3 .9c6.1 1.9 10 4 10 10-.1 8-7.9 11.1-14.9 11.1zM252.9 24.8h12.4c2.3-.1 4.7 0 7 .4 4.3 1 7.6 3.8 7.6 8.6 0 4.6-2.9 6.9-7.1 8 4.8.9 8.4 3.3 8.4 8.6 0 8.1-8 9.9-14.2 9.9h-14zm12.4 14.8c3.4 0 6.9-.7 6.9-4.8 0-3.7-3.2-4.7-6.4-4.7h-5.4v9.5zM266 55c3.6 0 7.1-.8 7.1-5.2s-3-5.2-6.7-5.2h-6.1V55zM301.2 61c-11.5 0-16.6-7.3-16.6-18.2s5.7-18.8 17-18.8c7.1 0 14 3.2 14.2 11.2h-8c-.4-3.6-2.8-5.4-6.2-5.4-7 0-9.1 7.5-9.1 13.2s2.1 12.3 8.8 12.3c3.5 0 6.1-1.9 6.6-5.5h8c-.8 8.2-7.3 11.2-14.7 11.2z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
1
.gitea/workflows/src/assets/idbi-bank-logo.svg
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
.gitea/workflows/src/assets/kautilya-learning-story.jpg
Normal file
|
After Width: | Height: | Size: 4.1 MiB |
BIN
.gitea/workflows/src/assets/klc-logo-dark.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
.gitea/workflows/src/assets/klc-logo.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
1
.gitea/workflows/src/assets/larsen-toubro-logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg height="50.25mm" viewBox="0.15 0.15 50 50" width="50.25mm" xmlns="http://www.w3.org/2000/svg"><path d="M25.141.15c13.74 0 25.009 11.217 25.009 24.974 0 13.791-11.27 25.026-25.009 25.026C11.367 50.15.15 38.915.15 25.124.15 11.367 11.367.15 25.141.15m-3.457 46.596l10.67-18.5h-7.213l3.58-6.209 16.932-.018-3.58 6.244-6.138-.018L25.14 47.028c12.028 0 21.887-9.859 21.887-21.905 0-11.992-9.859-21.834-21.887-21.834-.882 0-1.763.053-2.575.159l-10.758 18.59H25.14l-3.615 6.207H4.612l13.792-23.88C9.639 7.205 3.272 15.46 3.272 25.124c0 10.881 7.99 19.947 18.412 21.622" fill="#004f8a" fill-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 610 B |
1
.gitea/workflows/src/assets/levis.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg height="1025" width="2500" xmlns="http://www.w3.org/2000/svg" viewBox="0 -1.4210854715202004e-14 283.464 116.18800000000002"><path d="M250.094 7.094c.71-.05 1.341-.196 1.896-.439.552-.24.997-.61 1.323-1.117.331-.502.496-1.206.496-2.104 0-.759-.151-1.366-.441-1.819a3.04 3.04 0 0 0-1.145-1.04 4.978 4.978 0 0 0-1.611-.489c-.261-.04-.52-.065-.777-.086h-5.688v7.17h3.688c.797 0 1.549-.024 2.259-.076zm-13.348 13.168c1.437 1.489 3.135 2.656 5.092 3.506 1.955.849 4.076 1.271 6.358 1.271 2.255 0 4.347-.423 6.287-1.271a15.75 15.75 0 0 0 5.066-3.506c1.435-1.487 2.562-3.246 3.377-5.271.812-2.028 1.222-4.235 1.222-6.626 0-2.317-.41-4.482-1.222-6.491A17.011 17.011 0 0 0 262.014 0h-5.527c.564.958.854 2.15.854 3.588 0 2.043-.571 3.543-1.716 4.491-1.142.953-2.559 1.517-4.259 1.691l6.495 10.024h-3.795l-6.178-9.765h-3.74v9.765h-3.529V0h-6.338a16.93 16.93 0 0 0-.91 1.875c-.812 2.008-1.221 4.173-1.221 6.491 0 2.392.408 4.599 1.221 6.626.812 2.024 1.939 3.783 3.375 5.27zM99.877 38.848c-4.923 0-7.531 3.845-7.586 7.781h15.825c0-4.64-2.936-7.781-8.239-7.781zM265.756 0c.119.245.251.479.362.731 1.037 2.356 1.559 4.901 1.559 7.634 0 2.811-.521 5.396-1.559 7.771-1.037 2.365-2.438 4.427-4.207 6.175a18.945 18.945 0 0 1-6.18 4.079c-2.354.969-4.865 1.455-7.536 1.455-2.665 0-5.185-.485-7.553-1.455a19.146 19.146 0 0 1-6.235-4.079c-1.783-1.749-3.193-3.811-4.23-6.175-1.039-2.375-1.561-4.96-1.561-7.771 0-2.733.521-5.278 1.561-7.634.111-.252.244-.486.363-.731H0l27.257 116.188c57.227-36.803 112.227-1.435 114.439-.086h.006a.095.095 0 0 1 .017-.01h.028c.004.003.012.006.016.01h.009c2.208-1.349 57.211-36.717 114.437.086L283.464 0zM74.901 70.539H37.644V31.733h16.75V59.98h20.507zm50.111-16.1H92.32c0 5.051 4.276 8.861 9.975 8.861 3.389 0 6.476-1.121 8.657-4.154h13.037c-4.261 9.791-12.526 12.575-23.794 12.575-14.654 0-25.015-7.935-25.015-20.504 0-13.424 10.206-20.506 25.015-20.506 16.568 0 24.875 10.736 24.875 21.07-.001.862.066 1.723-.058 2.658zm30.326 16.162h-14.116l-21.076-38.9h19.02l12.094 23.646.333.713.303-.713L163.207 31.7h11.421zm37.224-.076h-16.156V31.628h16.156zm12.137-34.022l-4.867 5.294h-2.024l2.231-5.772h-4.459v-7.697h9.119zm21.203 35.13c-17.264 0-20.406-9.974-20.406-12.445h12.501c0 1.452 1.967 3.193 3.627 3.901 1.366.585 2.888.7 4.312.7 3.52 0 6.408-.854 6.408-2.898 0-2.489-2.957-3.041-6.219-3.509-8.877-1.28-21.318-2.771-21.318-12.701 0-9.262 10.113-13.976 20.849-13.976 17.187 0 21.743 9.521 21.743 12.278h-12.705c0-.823-.627-2.403-2.359-3.307-1.285-.671-3.174-1.214-5-1.214-4.747 0-7.418.988-7.418 3.163 0 1.777 2.408 2.658 5.865 3.251 9.023 1.549 22.879 2.085 22.879 13.066-.001 5.578-4.831 13.691-22.759 13.691z" fill="#a8112e"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
BIN
.gitea/workflows/src/assets/luxury-accommodation.jpg
Normal file
|
After Width: | Height: | Size: 3.4 MiB |
BIN
.gitea/workflows/src/assets/morning.jpg
Normal file
|
After Width: | Height: | Size: 3.7 MiB |
BIN
.gitea/workflows/src/assets/outdoor-amphitheater.jpg
Normal file
|
After Width: | Height: | Size: 4.5 MiB |
BIN
.gitea/workflows/src/assets/panoramas/cayley_interior.jpg
Normal file
|
After Width: | Height: | Size: 778 KiB |
BIN
.gitea/workflows/src/assets/panoramas/cedar_bridge_sunset.jpg
Normal file
|
After Width: | Height: | Size: 387 KiB |
BIN
.gitea/workflows/src/assets/private-space.jpg
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
1
.gitea/workflows/src/assets/tata-motors.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg height="387" viewBox="17.717 17.72 566.913 57.891" width="2500" xmlns="http://www.w3.org/2000/svg"><path d="m515.958 17.72c-5.909 0-11.618.477-15.586.977v55.82h10.156v-23.123h5.196c1.245 0 2.488.019 3.476.508 3.43 1.025 5.173 3.499 6.407 9.296 1.488 7.032 3.028 11.807 3.75 13.32h10.39c-1.001-2.258-2.499-8.539-4.258-15.546-1.477-5.543-3.692-9.605-7.89-11.094v-.234c5.432-1.745 10.195-6.769 10.195-14.063 0-4.791-1.8-8.84-4.766-11.367-3.188-2.765-7.639-4.255-13.828-4.492h-3.242zm53.125 0c-12.121 0-19.805 7.02-19.805 16.016 0 8.234 5.947 13.21 15.079 16.446 7.19 2.484 9.921 4.982 9.921 9.219 0 4.492-3.698 7.734-10.156 7.734-5.187 0-10.109-1.746-13.32-3.711l-2.227 8.437c2.967 2.003 9.146 3.75 15.078 3.75 14.344 0 20.977-7.969 20.977-16.953 0-7.977-4.64-12.932-14.297-16.68-7.397-2.777-10.664-4.769-10.664-9.023 0-3.479 2.72-6.992 9.14-6.992 5.188 0 9.195 1.777 11.172 2.734l2.461-8.203c-2.99-1.458-7.414-2.773-13.359-2.773zm-203.71.079c-15.454.22-26.094 12.29-26.094 29.336 0 16.309 10.002 28.36 25.664 28.36h.429c15.235-.233 26.368-10.914 26.368-29.298 0-16.12-9.568-28.398-25.938-28.398h-.43zm99.726 0c-15.479.22-26.133 12.29-26.133 29.336 0 16.309 9.978 28.36 25.664 28.36h.47c15.185-.233 26.289-10.914 26.289-29.298 0-16.12-9.5-28.398-25.82-28.398zm-447.382.742v16.406h15.937v39.688h22.617v-39.688h15.469v-16.406zm69.765 0-21.406 56.094h21.64l11.407-33.32 11.64 33.32h21.875l-21.64-56.094zm40.156 0v16.406h15.977v39.688h22.578v-39.688h15.507v-16.406zm69.57 0-21.21 56.094h21.64l11.406-33.32 11.641 33.32h21.914l-21.679-56.095zm76.524 0-3.477 56.094h9.57l1.172-22.383c.288-7.886.585-16.896.86-24.14h.273c1.453 6.964 3.502 14.808 5.82 21.796l7.266 24.14h7.852l8.437-24.413c2.32-6.976 4.629-14.56 6.68-21.524 0 7.557.554 16.268.86 23.555l.898 22.969h10.195l-3.203-56.094h-13.399l-7.851 22.344c-2.026 6.695-4.043 13.65-5.508 19.766h-.312c-1.43-6.384-3.176-13.051-5.196-19.454l-7.304-22.656zm120.703.156v8.633h16.133v47.227h10.195v-47.227h15.898v-8.633zm122.539 6.836h2.226c5.188.72 8.399 3.484 8.399 9.063 0 4.736-3.21 8.272-8.399 9.023-.988 0-1.744.235-2.734.235h-5.938v-17.814c.977-.262 3.248-.508 6.446-.508zm-151.602.43c10.23 0 15.43 10.02 15.43 20.469 0 11.792-5.432 20.86-15.43 20.86-9.753 0-15.468-9.31-15.468-20.43 0-11.116 5.25-20.899 15.468-20.899zm99.727 0c10.192 0 15.39 10.02 15.39 20.469 0 11.792-5.429 20.86-15.39 20.86-9.803 0-15.469-9.31-15.469-20.43 0-11.116 5.226-20.899 15.469-20.899z" fill="#5982c4"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
BIN
.gitea/workflows/src/assets/v-Swaminathan.jpg
Normal file
|
After Width: | Height: | Size: 53 KiB |
485
.gitea/workflows/src/components/AIChatbot.tsx
Normal file
@@ -0,0 +1,485 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
|
||||||
|
import { Badge } from './ui/badge';
|
||||||
|
import {
|
||||||
|
MessageCircle,
|
||||||
|
X,
|
||||||
|
Send,
|
||||||
|
Bot,
|
||||||
|
User,
|
||||||
|
Minimize2,
|
||||||
|
Maximize2
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { Input } from './ui/input';
|
||||||
|
import { ScrollArea } from './ui/scroll-area';
|
||||||
|
|
||||||
|
interface Message {
|
||||||
|
id: string;
|
||||||
|
type: 'user' | 'bot';
|
||||||
|
content: string;
|
||||||
|
timestamp: Date;
|
||||||
|
suggestions?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialMessage: Message = {
|
||||||
|
id: '1',
|
||||||
|
type: 'bot',
|
||||||
|
content: "Hi! I'm here to help you explore KLC's leadership programs and facilities. What are you looking for today?",
|
||||||
|
timestamp: new Date(),
|
||||||
|
suggestions: [
|
||||||
|
"Show me leadership programs",
|
||||||
|
"Book a facility tour",
|
||||||
|
"Upcoming webinars",
|
||||||
|
"Contact information"
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const botResponses: Record<string, { content: string; suggestions?: string[] }> = {
|
||||||
|
"programs": {
|
||||||
|
content: "Great! We offer various leadership development programs including Executive Leadership, Strategic Management, and Team Building workshops. Would you like to explore specific programs or see our full catalog?",
|
||||||
|
suggestions: ["View all programs", "Executive programs", "Team building", "Custom corporate training"]
|
||||||
|
},
|
||||||
|
"leadership": {
|
||||||
|
content: "Our leadership programs are designed to transform leaders at every level. We offer Executive Leadership Development, Strategic Leadership, Team Leadership, and Management Excellence programs.",
|
||||||
|
suggestions: ["Executive Leadership", "Strategic Leadership", "Team Leadership", "Management Excellence"]
|
||||||
|
},
|
||||||
|
"facilities": {
|
||||||
|
content: "Our state-of-the-art facilities include modern conference rooms, training halls, and collaboration spaces. You can take a virtual tour or book a facility for your event.",
|
||||||
|
suggestions: ["Virtual tour", "Book conference room", "Training halls", "Facility pricing"]
|
||||||
|
},
|
||||||
|
"facility": {
|
||||||
|
content: "Our learning facility features cutting-edge technology, flexible meeting spaces, and comfortable learning environments. We can accommodate groups from 10 to 500 participants.",
|
||||||
|
suggestions: ["Book now", "View amenities", "Check availability", "Pricing information"]
|
||||||
|
},
|
||||||
|
"tour": {
|
||||||
|
content: "I'd be happy to arrange a facility tour for you! Our tours showcase our modern classrooms, breakout spaces, and technology capabilities. Would you like to schedule one?",
|
||||||
|
suggestions: ["Schedule tour", "Virtual tour", "Facility features", "Contact tour guide"]
|
||||||
|
},
|
||||||
|
"webinars": {
|
||||||
|
content: "We host regular webinars on leadership topics. You can view upcoming sessions, register for live events, or access our library of recorded sessions.",
|
||||||
|
suggestions: ["Upcoming webinars", "Recorded sessions", "Register for webinar", "Webinar schedule"]
|
||||||
|
},
|
||||||
|
"webinar": {
|
||||||
|
content: "Our webinars cover essential leadership topics like Strategic Thinking, Team Management, Change Leadership, and Executive Presence. Most are free to attend!",
|
||||||
|
suggestions: ["View schedule", "Register now", "Past recordings", "Webinar topics"]
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
content: "You can reach us at info@klc.edu.in or call +91 11 4567 8900. Our team is available Monday to Friday, 9 AM to 6 PM IST. You can also schedule a consultation.",
|
||||||
|
suggestions: ["Schedule consultation", "Email us", "Office locations", "Support hours"]
|
||||||
|
},
|
||||||
|
"executive": {
|
||||||
|
content: "Our Executive Leadership programs are designed for senior leaders and C-suite executives. These intensive programs focus on strategic thinking, organizational transformation, and executive presence.",
|
||||||
|
suggestions: ["Program details", "Enrollment", "Schedule", "Executive coaching"]
|
||||||
|
},
|
||||||
|
"strategic": {
|
||||||
|
content: "Strategic Leadership development helps leaders think systematically about complex challenges, make better decisions, and drive organizational change effectively.",
|
||||||
|
suggestions: ["Learn more", "Case studies", "Program schedule", "Apply now"]
|
||||||
|
},
|
||||||
|
"team": {
|
||||||
|
content: "Team Leadership programs focus on building high-performing teams, improving collaboration, and developing emotional intelligence for team leaders.",
|
||||||
|
suggestions: ["Team programs", "Workshop format", "Group discounts", "Custom training"]
|
||||||
|
},
|
||||||
|
"management": {
|
||||||
|
content: "Management Excellence programs cover essential management skills including performance management, delegation, communication, and conflict resolution.",
|
||||||
|
suggestions: ["Management training", "Skills assessment", "Corporate packages", "Schedule consultation"]
|
||||||
|
},
|
||||||
|
"pricing": {
|
||||||
|
content: "Our pricing varies by program type and group size. Individual programs start from ₹15,000, while corporate packages offer significant discounts for bulk enrollments.",
|
||||||
|
suggestions: ["Individual pricing", "Corporate packages", "Group discounts", "Payment options"]
|
||||||
|
},
|
||||||
|
"schedule": {
|
||||||
|
content: "We offer programs throughout the year with flexible scheduling options. Most programs are available in weekend, weekday, and intensive formats.",
|
||||||
|
suggestions: ["View calendar", "Weekend programs", "Weekday options", "Custom scheduling"]
|
||||||
|
},
|
||||||
|
"enrollment": {
|
||||||
|
content: "Enrollment is simple! You can register online, call us directly, or schedule a consultation to discuss the best program for your needs.",
|
||||||
|
suggestions: ["Register online", "Call us", "Schedule consultation", "Application process"]
|
||||||
|
},
|
||||||
|
"corporate": {
|
||||||
|
content: "Our corporate solutions include custom leadership development programs, executive coaching, and organizational development services tailored to your company's needs.",
|
||||||
|
suggestions: ["Custom programs", "Executive coaching", "Organizational development", "Corporate pricing"]
|
||||||
|
},
|
||||||
|
"coaching": {
|
||||||
|
content: "We offer one-on-one executive coaching with certified leadership coaches. Our coaching programs are customized to address specific leadership challenges and goals.",
|
||||||
|
suggestions: ["Coach profiles", "Coaching process", "Pricing", "Schedule session"]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
content: "I can help you with information about our programs, facilities, webinars, and more. What would you like to know?",
|
||||||
|
suggestions: ["Leadership programs", "Facility booking", "Webinars", "Contact us"]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function AIChatbot() {
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [isMinimized, setIsMinimized] = useState(false);
|
||||||
|
const [messages, setMessages] = useState<Message[]>([initialMessage]);
|
||||||
|
const [inputValue, setInputValue] = useState('');
|
||||||
|
const [isTyping, setIsTyping] = useState(false);
|
||||||
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
const inactivityTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
|
// Show chatbot after user activity
|
||||||
|
useEffect(() => {
|
||||||
|
const resetTimer = () => {
|
||||||
|
if (inactivityTimerRef.current) {
|
||||||
|
clearTimeout(inactivityTimerRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
inactivityTimerRef.current = setTimeout(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setIsVisible(true);
|
||||||
|
}
|
||||||
|
}, 8000); // Show after 8 seconds of activity
|
||||||
|
};
|
||||||
|
|
||||||
|
const events = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart'];
|
||||||
|
|
||||||
|
const addEventListeners = () => {
|
||||||
|
events.forEach(event => {
|
||||||
|
document.addEventListener(event, resetTimer, true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeEventListeners = () => {
|
||||||
|
events.forEach(event => {
|
||||||
|
document.removeEventListener(event, resetTimer, true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
addEventListeners();
|
||||||
|
resetTimer();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
removeEventListeners();
|
||||||
|
if (inactivityTimerRef.current) {
|
||||||
|
clearTimeout(inactivityTimerRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
// Auto-scroll to bottom of messages
|
||||||
|
useEffect(() => {
|
||||||
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}, [messages]);
|
||||||
|
|
||||||
|
const getBotResponse = (userMessage: string): { content: string; suggestions?: string[] } => {
|
||||||
|
const message = userMessage.toLowerCase();
|
||||||
|
|
||||||
|
// Enhanced keyword matching
|
||||||
|
if (message.includes('leadership') && (message.includes('program') || message.includes('course') || message.includes('training'))) {
|
||||||
|
return botResponses.leadership;
|
||||||
|
} else if (message.includes('executive') || message.includes('senior') || message.includes('c-suite')) {
|
||||||
|
return botResponses.executive;
|
||||||
|
} else if (message.includes('strategic') || message.includes('strategy')) {
|
||||||
|
return botResponses.strategic;
|
||||||
|
} else if (message.includes('team') || message.includes('collaboration')) {
|
||||||
|
return botResponses.team;
|
||||||
|
} else if (message.includes('management') || message.includes('manager')) {
|
||||||
|
return botResponses.management;
|
||||||
|
} else if (message.includes('program') || message.includes('course') || message.includes('training')) {
|
||||||
|
return botResponses.programs;
|
||||||
|
} else if (message.includes('facility') || message.includes('space') || message.includes('room')) {
|
||||||
|
return botResponses.facility;
|
||||||
|
} else if (message.includes('tour') || message.includes('visit')) {
|
||||||
|
return botResponses.tour;
|
||||||
|
} else if (message.includes('book') || message.includes('reserve') || message.includes('schedule')) {
|
||||||
|
return botResponses.facilities;
|
||||||
|
} else if (message.includes('webinar') || message.includes('session') || message.includes('online')) {
|
||||||
|
return botResponses.webinar;
|
||||||
|
} else if (message.includes('contact') || message.includes('phone') || message.includes('email') || message.includes('reach')) {
|
||||||
|
return botResponses.contact;
|
||||||
|
} else if (message.includes('price') || message.includes('cost') || message.includes('fee')) {
|
||||||
|
return botResponses.pricing;
|
||||||
|
} else if (message.includes('schedule') || message.includes('timing') || message.includes('when')) {
|
||||||
|
return botResponses.schedule;
|
||||||
|
} else if (message.includes('enroll') || message.includes('register') || message.includes('apply')) {
|
||||||
|
return botResponses.enrollment;
|
||||||
|
} else if (message.includes('corporate') || message.includes('company') || message.includes('organization')) {
|
||||||
|
return botResponses.corporate;
|
||||||
|
} else if (message.includes('coach') || message.includes('mentor') || message.includes('personal')) {
|
||||||
|
return botResponses.coaching;
|
||||||
|
} else {
|
||||||
|
return botResponses.default;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSendMessage = async (content: string) => {
|
||||||
|
if (!content.trim()) return;
|
||||||
|
|
||||||
|
// Add user message
|
||||||
|
const userMessage: Message = {
|
||||||
|
id: Date.now().toString(),
|
||||||
|
type: 'user',
|
||||||
|
content: content.trim(),
|
||||||
|
timestamp: new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
setMessages(prev => [...prev, userMessage]);
|
||||||
|
setInputValue('');
|
||||||
|
setIsTyping(true);
|
||||||
|
|
||||||
|
// Simulate typing delay with realistic timing
|
||||||
|
setTimeout(() => {
|
||||||
|
const response = getBotResponse(content);
|
||||||
|
const botMessage: Message = {
|
||||||
|
id: (Date.now() + 1).toString(),
|
||||||
|
type: 'bot',
|
||||||
|
content: response.content,
|
||||||
|
timestamp: new Date(),
|
||||||
|
suggestions: response.suggestions
|
||||||
|
};
|
||||||
|
|
||||||
|
setMessages(prev => [...prev, botMessage]);
|
||||||
|
setIsTyping(false);
|
||||||
|
}, 800 + Math.random() * 600); // 800ms to 1.4s delay
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSuggestionClick = (suggestion: string) => {
|
||||||
|
handleSendMessage(suggestion);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTime = (date: Date) => {
|
||||||
|
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSendMessage(inputValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isVisible) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Chatbot Toggle Button */}
|
||||||
|
{!isOpen && (
|
||||||
|
<Button
|
||||||
|
onClick={() => setIsOpen(true)}
|
||||||
|
className="fixed bottom-6 right-6 w-16 h-16 rounded-full shadow-xl z-50 transition-all duration-300 hover:scale-110 chatbot-button"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-primary)',
|
||||||
|
border: 'none'
|
||||||
|
}}
|
||||||
|
size="lg"
|
||||||
|
aria-label="Open KLC Assistant chat"
|
||||||
|
>
|
||||||
|
<MessageCircle className="w-6 h-6 text-white" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Chat Window */}
|
||||||
|
{isOpen && (
|
||||||
|
<Card className={`fixed bottom-6 right-6 w-80 md:w-96 shadow-2xl z-50 transition-all duration-300 border-0 ${
|
||||||
|
isMinimized ? 'h-16' : 'h-[500px]'
|
||||||
|
}`}>
|
||||||
|
{/* Header */}
|
||||||
|
<CardHeader
|
||||||
|
className="flex flex-row items-center justify-between space-y-0 pb-3 rounded-t-lg"
|
||||||
|
style={{ backgroundColor: 'var(--color-primary)' }}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div
|
||||||
|
className="w-10 h-10 rounded-full flex items-center justify-center"
|
||||||
|
style={{ backgroundColor: 'rgba(255, 255, 255, 0.2)' }}
|
||||||
|
>
|
||||||
|
<Bot className="w-5 h-5 text-white" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<CardTitle className="text-base font-medium text-white" style={{ fontFamily: 'var(--font-family-base)' }}>
|
||||||
|
KLC Assistant
|
||||||
|
</CardTitle>
|
||||||
|
<div className="flex items-center gap-2 text-sm text-white opacity-90">
|
||||||
|
<div className="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
|
||||||
|
<span style={{ fontFamily: 'var(--font-family-base)' }}>Online & Ready to Help</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setIsMinimized(!isMinimized)}
|
||||||
|
className="w-8 h-8 p-0 hover:bg-white/20 text-white"
|
||||||
|
aria-label={isMinimized ? "Maximize chat" : "Minimize chat"}
|
||||||
|
>
|
||||||
|
{isMinimized ? (
|
||||||
|
<Maximize2 className="w-4 h-4" />
|
||||||
|
) : (
|
||||||
|
<Minimize2 className="w-4 h-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setIsOpen(false)}
|
||||||
|
className="w-8 h-8 p-0 hover:bg-white/20 text-white"
|
||||||
|
aria-label="Close chat"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
{/* Chat Content */}
|
||||||
|
{!isMinimized && (
|
||||||
|
<CardContent className="p-0 flex flex-col h-[420px] overflow-y-auto">
|
||||||
|
{/* Messages */}
|
||||||
|
<ScrollArea className="flex-1 p-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
{messages.map((message) => (
|
||||||
|
<div key={message.id} className={`flex ${message.type === 'user' ? 'justify-end' : 'justify-start'}`}>
|
||||||
|
<div className={`flex items-start gap-3 max-w-[85%] ${message.type === 'user' ? 'flex-row-reverse' : ''}`}>
|
||||||
|
<div className={`w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 ${
|
||||||
|
message.type === 'user'
|
||||||
|
? 'text-white'
|
||||||
|
: 'bg-gray-100 text-gray-600'
|
||||||
|
}`}
|
||||||
|
style={message.type === 'user' ? { backgroundColor: 'var(--color-primary)' } : {}}
|
||||||
|
>
|
||||||
|
{message.type === 'user' ? (
|
||||||
|
<User className="w-4 h-4" />
|
||||||
|
) : (
|
||||||
|
<Bot className="w-4 h-4" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className={`p-3 rounded-lg ${
|
||||||
|
message.type === 'user'
|
||||||
|
? 'text-white'
|
||||||
|
: 'bg-gray-50 text-gray-800'
|
||||||
|
}`}
|
||||||
|
style={message.type === 'user' ? { backgroundColor: 'var(--color-primary)' } : {}}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
className="text-sm leading-relaxed"
|
||||||
|
style={{ fontFamily: 'var(--font-family-base)' }}
|
||||||
|
>
|
||||||
|
{message.content}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{message.suggestions && (
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{message.suggestions.map((suggestion, index) => (
|
||||||
|
<Badge
|
||||||
|
key={index}
|
||||||
|
variant="outline"
|
||||||
|
className="cursor-pointer transition-colors text-xs py-1 px-3 rounded-full font-normal border-gray-300 text-gray-700 hover:text-white"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
borderColor: 'var(--color-primary)'
|
||||||
|
}}
|
||||||
|
onClick={() => handleSuggestionClick(suggestion)}
|
||||||
|
onMouseEnter={(e:React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.currentTarget.style.backgroundColor = 'var(--color-primary)';
|
||||||
|
e.currentTarget.style.borderColor = 'var(--color-primary)';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e:React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.currentTarget.style.backgroundColor = 'transparent';
|
||||||
|
e.currentTarget.style.borderColor = 'var(--color-primary)';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{suggestion}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<p
|
||||||
|
className="text-xs text-gray-500 px-1"
|
||||||
|
style={{ fontFamily: 'var(--font-family-base)' }}
|
||||||
|
>
|
||||||
|
{formatTime(message.timestamp)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{isTyping && (
|
||||||
|
<div className="flex justify-start">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center">
|
||||||
|
<Bot className="w-4 h-4 text-gray-600" />
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-50 p-3 rounded-lg">
|
||||||
|
<div className="flex gap-1">
|
||||||
|
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
|
||||||
|
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }}></div>
|
||||||
|
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div ref={messagesEndRef} />
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
|
{/* Input */}
|
||||||
|
<div className="border-t p-4 bg-white">
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSendMessage(inputValue);
|
||||||
|
}}
|
||||||
|
className="flex gap-2"
|
||||||
|
role="form"
|
||||||
|
aria-label="Chat message form"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
value={inputValue}
|
||||||
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
|
onKeyPress={handleKeyPress}
|
||||||
|
placeholder="Type your message..."
|
||||||
|
className="flex-1 min-h-[44px] border-gray-200 focus:border-blue-500 focus:ring-blue-500"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
disabled={isTyping}
|
||||||
|
aria-label="Chat message input"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
size="sm"
|
||||||
|
disabled={!inputValue.trim() || isTyping}
|
||||||
|
className="min-h-[44px] min-w-[44px] text-white transition-all duration-200"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-primary)',
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e:React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
if (!e.currentTarget.disabled) {
|
||||||
|
e.currentTarget.style.backgroundColor = '#030359';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e:React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
if (!e.currentTarget.disabled) {
|
||||||
|
e.currentTarget.style.backgroundColor = 'var(--color-primary)';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Send className="w-4 h-4" />
|
||||||
|
<span className="sr-only">Send message</span>
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* Quick Help Text */}
|
||||||
|
<p
|
||||||
|
className="text-xs text-gray-500 mt-2 text-center"
|
||||||
|
style={{ fontFamily: 'var(--font-family-base)' }}
|
||||||
|
>
|
||||||
|
Ask me about programs, facilities, or upcoming events
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
244
.gitea/workflows/src/components/AboutSection.tsx
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
export function AboutSection() {
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
className="py-16 section-margin-x"
|
||||||
|
style={{ backgroundColor: 'var(--color-brand-bg-white)' }}
|
||||||
|
>
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
{/* Section Header with Branded Tag */}
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<div className="flex items-center justify-center gap-2 mb-6">
|
||||||
|
<div
|
||||||
|
className="w-2 h-2 rounded-full"
|
||||||
|
style={{ backgroundColor: 'var(--color-brand-accent)' }}
|
||||||
|
></div>
|
||||||
|
<span
|
||||||
|
className="text-sm font-semibold tracking-wider uppercase"
|
||||||
|
style={{
|
||||||
|
color: 'var(--color-brand-black)',
|
||||||
|
fontFamily: 'var(--font-family-brand)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
WHO WE ARE
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h2
|
||||||
|
className="text-5xl font-semibold mb-6"
|
||||||
|
style={{
|
||||||
|
color: 'var(--color-brand-black)',
|
||||||
|
fontFamily: 'var(--font-family-brand)',
|
||||||
|
lineHeight: '1.1'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
About KLC
|
||||||
|
</h2>
|
||||||
|
<p
|
||||||
|
className="text-xl max-w-3xl mx-auto leading-relaxed"
|
||||||
|
style={{
|
||||||
|
color: 'var(--color-brand-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-brand)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Transforming businesses through strategic expertise, innovative solutions, and lasting partnerships that drive sustainable growth.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Three Column Layout */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-12 lg:gap-8">
|
||||||
|
{/* Our Vision */}
|
||||||
|
<div className="text-center lg:text-left">
|
||||||
|
<div
|
||||||
|
className="w-16 h-16 mx-auto lg:mx-0 mb-6 rounded-lg flex items-center justify-center"
|
||||||
|
style={{ backgroundColor: 'var(--color-brand-primary)' }}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
className="text-white"
|
||||||
|
>
|
||||||
|
<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/>
|
||||||
|
<circle cx="12" cy="12" r="3"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3
|
||||||
|
className="text-2xl font-bold mb-4"
|
||||||
|
style={{
|
||||||
|
color: 'var(--color-brand-black)',
|
||||||
|
fontFamily: 'var(--font-family-brand)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Our Vision
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
className="text-base leading-relaxed mb-4"
|
||||||
|
style={{
|
||||||
|
color: 'var(--color-brand-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-brand)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
To be the leading catalyst for business transformation, empowering organizations to unlock their full potential through innovative strategies and cutting-edge solutions.
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="text-base leading-relaxed"
|
||||||
|
style={{
|
||||||
|
color: 'var(--color-brand-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-brand)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
We envision a future where every business can thrive in an ever-evolving marketplace through strategic foresight and adaptive excellence.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Our Impact */}
|
||||||
|
<div className="text-center lg:text-left">
|
||||||
|
<div
|
||||||
|
className="w-16 h-16 mx-auto lg:mx-0 mb-6 rounded-lg flex items-center justify-center"
|
||||||
|
style={{ backgroundColor: 'var(--color-brand-primary)' }}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
className="text-white"
|
||||||
|
>
|
||||||
|
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
|
||||||
|
<path d="M3 3v5h5"/>
|
||||||
|
<path d="M12 7v5l4 2"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3
|
||||||
|
className="text-2xl font-bold mb-4"
|
||||||
|
style={{
|
||||||
|
color: 'var(--color-brand-black)',
|
||||||
|
fontFamily: 'var(--font-family-brand)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Our Impact
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
className="text-base leading-relaxed mb-4"
|
||||||
|
style={{
|
||||||
|
color: 'var(--color-brand-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-brand)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Over two decades of delivering measurable results for Fortune 500 companies and emerging businesses alike, driving over $2.5 billion in combined client value creation.
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="text-base leading-relaxed"
|
||||||
|
style={{
|
||||||
|
color: 'var(--color-brand-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-brand)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Our strategic interventions have transformed operational efficiency by an average of 40% and accelerated growth trajectories across diverse industries.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Our Expertise */}
|
||||||
|
<div className="text-center lg:text-left">
|
||||||
|
<div
|
||||||
|
className="w-16 h-16 mx-auto lg:mx-0 mb-6 rounded-lg flex items-center justify-center"
|
||||||
|
style={{ backgroundColor: 'var(--color-brand-primary)' }}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
className="text-white"
|
||||||
|
>
|
||||||
|
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
||||||
|
<path d="M2 17l10 5 10-5"/>
|
||||||
|
<path d="M2 12l10 5 10-5"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3
|
||||||
|
className="text-2xl font-bold mb-4"
|
||||||
|
style={{
|
||||||
|
color: 'var(--color-brand-black)',
|
||||||
|
fontFamily: 'var(--font-family-brand)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Our Expertise
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
className="text-base leading-relaxed mb-4"
|
||||||
|
style={{
|
||||||
|
color: 'var(--color-brand-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-brand)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Deep domain knowledge spanning strategic consulting, digital transformation, operational excellence, and change management across multiple industry verticals.
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="text-base leading-relaxed"
|
||||||
|
style={{
|
||||||
|
color: 'var(--color-brand-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-brand)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Our multidisciplinary team combines analytical rigor with creative problem-solving to deliver solutions that are both innovative and pragmatic.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CTA Section */}
|
||||||
|
<div className="text-center mt-16">
|
||||||
|
<button
|
||||||
|
className="brand-button"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-brand-accent)',
|
||||||
|
color: 'var(--color-brand-black)',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '10px',
|
||||||
|
padding: '1rem 2rem',
|
||||||
|
fontSize: '1.125rem',
|
||||||
|
fontWeight: '600',
|
||||||
|
fontFamily: 'var(--font-family-brand)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '0.5rem'
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = 'var(--color-brand-primary)';
|
||||||
|
e.currentTarget.style.color = 'white';
|
||||||
|
e.currentTarget.style.transform = 'translateY(-2px)';
|
||||||
|
e.currentTarget.style.boxShadow = '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = 'var(--color-brand-accent)';
|
||||||
|
e.currentTarget.style.color = 'var(--color-brand-black)';
|
||||||
|
e.currentTarget.style.transform = 'translateY(0)';
|
||||||
|
e.currentTarget.style.boxShadow = '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Learn More About Our Story
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
>
|
||||||
|
<path d="M5 12h14"/>
|
||||||
|
<path d="M12 5l7 7-7 7"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
996
.gitea/workflows/src/components/AboutUs.tsx
Normal file
@@ -0,0 +1,996 @@
|
|||||||
|
import {
|
||||||
|
BookOpen,
|
||||||
|
Brain,
|
||||||
|
CheckCircle,
|
||||||
|
Heart,
|
||||||
|
Puzzle,
|
||||||
|
Shield,
|
||||||
|
Target,
|
||||||
|
TrendingUp,
|
||||||
|
Users,
|
||||||
|
Zap
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { motion } from 'motion/react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import Aparna from '../assets/Aparna-Nair.png';
|
||||||
|
import Balaji from '../assets/Balaji-Chandrakumar.jpeg';
|
||||||
|
import Diju from '../assets/Diju.jpeg';
|
||||||
|
import Ramkumar from '../assets/K-Ramkumar.png';
|
||||||
|
import Muralidharan from '../assets/R-Muralidharan.png';
|
||||||
|
import Ramesh from '../assets/Ramesh-Padmanabhan.jpeg';
|
||||||
|
import Swaminathan from '../assets/v-Swaminathan.jpg';
|
||||||
|
import svgPaths from '../imports/svg-kw7r0ellyk';
|
||||||
|
import { useGetAboutUsQuery } from '../redux/services/aboutUsApi';
|
||||||
|
import { BrandedTag } from './about/BrandedTag';
|
||||||
|
import { CTABannerSection } from './CTABannerSection';
|
||||||
|
import { PrimaryCTAButton } from './PrimaryCTAButton';
|
||||||
|
import { navigateTo } from './Router';
|
||||||
|
import { TeamMemberModal } from './TeamMemberModal';
|
||||||
|
import { TestimonialsSection } from './TestimonialsSection';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { FullScreenLoader } from './FullScreenLoader';
|
||||||
|
|
||||||
|
|
||||||
|
// Static detailed team member data for modal
|
||||||
|
const staticTeamMembersDetails = {
|
||||||
|
'Mr. K Ramkumar': {
|
||||||
|
name: 'Mr. K Ramkumar',
|
||||||
|
role: 'Managing Director',
|
||||||
|
image: Ramkumar,
|
||||||
|
experience: 'Over 40 years of experience in HR, Business, and Leadership Development.',
|
||||||
|
fullBio: `Mr. K. Ramkumar is the founder of Kautilya Leadership Centre, an organisation dedicated to building world-class thought and practice in leadership consulting, research and development. He retired as Executive Director on the Board of ICICI Bank and was also on the Boards of ICICI Prudential Life Insurance and ICICI Ventures. Prior to his retirement, he was President of ICICI Foundation, the CSR arm of ICICI Group.
|
||||||
|
|
||||||
|
Over a 40-year career, Ram worked with HAL, Hindustan Unilever, ICI and ICICI Bank. His passion has always been leadership development, with a focus on enabling individuals to discover and expand their leadership resources. He played a key role in making ICICI Group a recognized Leadership Factory.
|
||||||
|
|
||||||
|
He co-created the ICICI Manipal Academy for Banking and Insurance, which inducted over 12,000 young leaders, and envisioned the ICICI Academy for Skills, now skilling 35,000 youth annually across 23 centers.`,
|
||||||
|
expertise: [
|
||||||
|
'Leadership Development',
|
||||||
|
'HR & Business Strategy',
|
||||||
|
'Organizational Capability Building',
|
||||||
|
'CSR Initiatives',
|
||||||
|
'Talent Pipeline Development'
|
||||||
|
],
|
||||||
|
education: 'Graduate in Science; further leadership and management exposure through senior executive roles.',
|
||||||
|
achievements: [
|
||||||
|
'Founded Kautilya Leadership Centre',
|
||||||
|
'Executive Director on ICICI Bank Board',
|
||||||
|
'Created ICICI Manipal Academy (12,000 leaders trained)',
|
||||||
|
'Founded ICICI Academy for Skills (35,000+ youth skilled)',
|
||||||
|
'Author of "Leveraging Human Capital" (McGraw Hill)'
|
||||||
|
],
|
||||||
|
clientWork: 'Guided leadership development across ICICI Group and worked with Manipal Global Education to groom future banking leaders.',
|
||||||
|
boardRoles: 'Former Board Member of ICICI Prudential Life, ICICI Ventures; served on CSR and leadership committees.'
|
||||||
|
},
|
||||||
|
'Mr. R. Muralidharan': {
|
||||||
|
name: 'Mr. R. Muralidharan',
|
||||||
|
role: 'Practice Head – Leadership Development',
|
||||||
|
image: Muralidharan,
|
||||||
|
experience: 'Over 33 years in Banking, Financial Services, and Leadership roles.',
|
||||||
|
fullBio: `Muralidharan (Murali) has 33+ years of experience in BFSI. He has held top leadership roles including CEO/COO positions at ICICI Bank, Dhanlaxmi Bank, and L&T Financial Services. His experience spans corporate and retail banking, strategy, technology, operations, and transformational projects.
|
||||||
|
|
||||||
|
At ICICI Bank, he was part of the founding team in 1994 and rose to become GM – Global Operations Group. His work at Dhanlaxmi Bank as COO won The Asian Banker Award (2012). At L&T Financial Services, he led operations, technology, credit, and CSR across retail, rural, and infrastructure finance.`,
|
||||||
|
expertise: [
|
||||||
|
'Banking & Financial Services',
|
||||||
|
'Transformational Projects',
|
||||||
|
'Customer Experience',
|
||||||
|
'Leadership Development',
|
||||||
|
'Strategy & Operations'
|
||||||
|
],
|
||||||
|
education: 'B.Sc. (Mathematics, Statistics, Physics), M.A. in Economics (Gold Medallist), Executive Education at Wharton & Stanford GSB.',
|
||||||
|
achievements: [
|
||||||
|
'Helped set up ICICI Bank in 1994',
|
||||||
|
'Asian Banker Award (2012) for transformation',
|
||||||
|
'Group Head at L&T Financial Services',
|
||||||
|
'Held Board and leadership committee roles',
|
||||||
|
'Speaker at NIBM, TMTC, and industry fora'
|
||||||
|
],
|
||||||
|
clientWork: 'Worked with public and private banks, financial services firms, and non-profits on leadership and customer service transformation.',
|
||||||
|
boardRoles: 'Held board positions in business and non-profits; Vice-Chair, Customer Service Excellence Foundation.'
|
||||||
|
},
|
||||||
|
'Ms. Aparna Nair': {
|
||||||
|
name: 'Ms. Aparna Nair',
|
||||||
|
role: 'Practice Head – Leadership Development',
|
||||||
|
image: Aparna,
|
||||||
|
experience: 'Over 25 years in leadership consulting, HR, and organizational development.',
|
||||||
|
fullBio: `Aparna is a founding member of Kautilya Leadership Centre and has been instrumental in building it since 2016. She advises Boards and Management Committees on leadership bench strength, designs CXO-level interventions, and consults on culture, performance, and talent management.
|
||||||
|
|
||||||
|
She is certified in MBTI and OPQ, has applied Balanced Scorecard frameworks, and built proprietary psychometric tools for KLC. Her prior roles include ICICI Bank and Blue Dart-FedEx, and she has engaged with leading organizations across multiple industries.`,
|
||||||
|
expertise: [
|
||||||
|
'Leadership Consulting',
|
||||||
|
'Psychometric Tools (MBTI, OPQ)',
|
||||||
|
'Culture & Strategy Alignment',
|
||||||
|
'Performance Management',
|
||||||
|
'HR Mentorship'
|
||||||
|
],
|
||||||
|
education: 'MBA in Marketing, Bachelor’s in Commerce; Accredited facilitator from SMR, Malaysia.',
|
||||||
|
achievements: [
|
||||||
|
'Co-founded KLC',
|
||||||
|
'Developed proprietary psychometric tools',
|
||||||
|
'Advised Boards across industries',
|
||||||
|
'Mentored HR professionals',
|
||||||
|
'Independent Director on a startup board'
|
||||||
|
],
|
||||||
|
clientWork: 'Worked with Godrej & Boyce, ICICI Prudential, Citi WAI, WNS, ThyssenKrupp, and others across pharma, BFSI, retail, auto, and private equity.',
|
||||||
|
boardRoles: 'Independent Woman Director; held leadership positions at ICICI Bank and Blue Dart-FedEx.'
|
||||||
|
},
|
||||||
|
'Mr. V. Swaminathan': {
|
||||||
|
name: 'Mr. V. Swaminathan',
|
||||||
|
role: 'Practice Head – Leadership Development',
|
||||||
|
image: Swaminathan,
|
||||||
|
experience: 'Over 31 years in BFSI and office automation; 26 years in leadership at Kotak Group.',
|
||||||
|
fullBio: `Swami spent 26 years at Kotak Group in leadership roles across consumer banking, investment banking, asset management, and asset finance. He has built businesses from concept to scale and handled P&L responsibilities across divisions.
|
||||||
|
|
||||||
|
He stepped down as Joint President of Kotak Mahindra Bank in 2021 before joining KLC. He was on Kotak’s Management Committee and led strategic initiatives, talent development, and capability building across the group.`,
|
||||||
|
expertise: [
|
||||||
|
'Banking & Financial Services',
|
||||||
|
'P&L Management',
|
||||||
|
'Change Management',
|
||||||
|
'Leadership Pipeline Development',
|
||||||
|
'Business Strategy'
|
||||||
|
],
|
||||||
|
education: 'PGDBM (Finance & Marketing), Gujarat University; Bachelor of Commerce; Senior Management programs at IIM Ahmedabad, IIM Bangalore, ISB Hyderabad.',
|
||||||
|
achievements: [
|
||||||
|
'Joint President at Kotak Mahindra Bank',
|
||||||
|
'Built and scaled multiple Kotak business units',
|
||||||
|
'Led strategic and leadership initiatives',
|
||||||
|
'Mentored talent pipelines across Kotak',
|
||||||
|
'Joined KLC as Practice Head in 2021'
|
||||||
|
],
|
||||||
|
clientWork: 'Extensive work with BFSI organizations and leadership development initiatives at scale.',
|
||||||
|
boardRoles: 'Key member of Kotak’s Management Committee; contributor to strategic boards within Kotak divisions.'
|
||||||
|
},
|
||||||
|
'Mr. Balaji Chandrakumar': {
|
||||||
|
name: 'Mr. Balaji Chandrakumar',
|
||||||
|
role: 'Practice Head – Leadership Development',
|
||||||
|
image: Balaji,
|
||||||
|
experience: '25+ years across Sales, HR Consulting, and HR leadership in India and SE Asia.',
|
||||||
|
fullBio: `Balaji has led HR leadership across India and SE Asia, including roles at Etisalat (India) and Goodhope Asia Holdings (Singapore). He has expertise in organization design, succession management, change management, and policy development.
|
||||||
|
|
||||||
|
Earlier, he worked in consulting with top Indian firms and began his HR journey with NIS Sparta after regional sales roles at Sandvik Asia.`,
|
||||||
|
expertise: [
|
||||||
|
'HR Leadership & Consulting',
|
||||||
|
'Organization Design',
|
||||||
|
'Succession Management',
|
||||||
|
'Change Management',
|
||||||
|
'Leadership Hiring'
|
||||||
|
],
|
||||||
|
education: 'Bachelor’s in Engineering, Master’s in HR, Postgraduate in Management; Chartered Fellow of CIPD.',
|
||||||
|
achievements: [
|
||||||
|
'HR Leadership roles at Etisalat and Goodhope Asia',
|
||||||
|
'Consulted for top Indian companies',
|
||||||
|
'Built people and performance initiatives across industries',
|
||||||
|
'Certified in multiple HR programs',
|
||||||
|
'CIPD Chartered Fellow'
|
||||||
|
],
|
||||||
|
clientWork: 'Worked with leading companies in telecom, food, and HR consulting sectors across India and SE Asia.',
|
||||||
|
boardRoles: 'Advisor in HR capability building across organizations.'
|
||||||
|
},
|
||||||
|
'Mr. Ramesh Padmanabhan': {
|
||||||
|
name: 'Mr. Ramesh Padmanabhan',
|
||||||
|
role: 'Practice Head – Leadership Development',
|
||||||
|
image: Ramesh,
|
||||||
|
experience: '30+ years in BFSI, with leadership roles at ICICI Bank, Dhanlaxmi Bank, and ADCB India.',
|
||||||
|
fullBio: `Ramesh has worked with SBI, ICICI Bank, Dhanlaxmi Bank, and ADCB India across corporate credit, retail banking, operations, and training. He has built leadership pipelines among branch and middle managers, and at ADCB India created products for NRIs and centralized operations.
|
||||||
|
|
||||||
|
He has consistently worked on capability building and leadership development alongside business roles.`,
|
||||||
|
expertise: [
|
||||||
|
'Banking & Financial Services',
|
||||||
|
'Leadership Development',
|
||||||
|
'Capability Building',
|
||||||
|
'Operations & Process Design',
|
||||||
|
'Retail & Corporate Banking'
|
||||||
|
],
|
||||||
|
education: 'Bachelor’s in Commerce; senior leadership roles provided extensive global exposure.',
|
||||||
|
achievements: [
|
||||||
|
'Built leadership pipelines in ICICI and ADCB',
|
||||||
|
'Part of ICICI for 13 years',
|
||||||
|
'Created NRI-focused products at ADCB India',
|
||||||
|
'Set up centralized operations at ADCB',
|
||||||
|
'30 years in BFSI leadership'
|
||||||
|
],
|
||||||
|
clientWork: 'Significant work in BFSI sector; tailored leadership development for managers and executives.',
|
||||||
|
boardRoles: 'Served on MANCOM and strategic boards in ICICI, Dhanlaxmi, and ADCB India.'
|
||||||
|
},
|
||||||
|
'Ms. Diju S': {
|
||||||
|
name: 'Ms. Diju S',
|
||||||
|
role: 'Practice Head – Leadership Development',
|
||||||
|
image: Diju,
|
||||||
|
experience: '15+ years in leadership development, banking, and people-centric initiatives.',
|
||||||
|
fullBio: `Diju began at ICICI Bank, leading initiatives in customer service, Six Sigma, workplace induction (SWIFT), and branch operations. She drove business growth, mentored teams, and implemented technology projects.
|
||||||
|
|
||||||
|
After a career break, she joined KLC as Practice Head. She now co-creates leadership program designs with clients and manages knowledge business and learning facilities.`,
|
||||||
|
expertise: [
|
||||||
|
'Leadership Program Design',
|
||||||
|
'Customer Experience',
|
||||||
|
'Operational Excellence',
|
||||||
|
'Women Leadership & Diversity',
|
||||||
|
'Project Management'
|
||||||
|
],
|
||||||
|
education: 'B.Com (Honours), Delhi University; MBA (Finance & Marketing), Symbiosis Institute of Management Studies.',
|
||||||
|
achievements: [
|
||||||
|
'Led Six Sigma rollout at ICICI Bank branches',
|
||||||
|
'Part of SWIFT trainer team',
|
||||||
|
'Head of Customer Service at ICICI',
|
||||||
|
'Built leadership learning facility at KLC',
|
||||||
|
'Special focus on diversity & inclusion'
|
||||||
|
],
|
||||||
|
clientWork: 'Worked with BFSI clients and KLC partners to create custom leadership programs.',
|
||||||
|
boardRoles: 'Active in leadership forums and project management at KLC.'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to get member details by name
|
||||||
|
const getMemberDetails = (nameRole: string) => {
|
||||||
|
// Extract the name from "Name - Role" format
|
||||||
|
const name = nameRole.split(' - ')[0];
|
||||||
|
return staticTeamMembersDetails[name as keyof typeof staticTeamMembersDetails] || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function AboutUs() {
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
const [expandedValue, setExpandedValue] = useState<string | null>('context');
|
||||||
|
const [selectedMember, setSelectedMember] = useState<any>(null);
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
|
||||||
|
// Fetch About Us data from API
|
||||||
|
const { data: aboutUsData, isLoading, isError, error } = useGetAboutUsQuery();
|
||||||
|
|
||||||
|
const transformTestimonials = (apiTestimonials: any[]) => {
|
||||||
|
if (!apiTestimonials || apiTestimonials.length === 0) return [];
|
||||||
|
|
||||||
|
return apiTestimonials.map((testimonial, index) => ({
|
||||||
|
id: testimonial.id || index,
|
||||||
|
name: testimonial.name || "Anonymous",
|
||||||
|
role: testimonial.designation || "Client",
|
||||||
|
company: undefined,
|
||||||
|
avatar: testimonial.profile_photo_url || undefined,
|
||||||
|
image: testimonial.profile_photo_url || undefined,
|
||||||
|
quote: testimonial.content || "",
|
||||||
|
rating: 5,
|
||||||
|
isVideo: !!testimonial.video_url,
|
||||||
|
videoThumbnail: testimonial.video_thumbnail_url || testimonial.profile_photo_url,
|
||||||
|
videoUrl: testimonial.video_url || undefined
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Transform the testimonials
|
||||||
|
const testimonialsData = transformTestimonials(aboutUsData?.testimonials || []);
|
||||||
|
|
||||||
|
// Get team members from API
|
||||||
|
const apiTeamMembers = aboutUsData?.our_team || [];
|
||||||
|
|
||||||
|
const handleMemberClick = (member: any) => {
|
||||||
|
// Get detailed static data for the clicked member
|
||||||
|
const memberDetails = getMemberDetails(member.name_role);
|
||||||
|
if (memberDetails) {
|
||||||
|
setSelectedMember(memberDetails);
|
||||||
|
} else {
|
||||||
|
// Fallback to API data if no static details found
|
||||||
|
setSelectedMember({
|
||||||
|
name: member.name_role.split(' - ')[0],
|
||||||
|
role: member.name_role.split(' - ')[1] || 'Team Member',
|
||||||
|
image: member.photo_url,
|
||||||
|
experience: member.bio,
|
||||||
|
fullBio: member.bio,
|
||||||
|
expertise: [],
|
||||||
|
education: '',
|
||||||
|
achievements: [],
|
||||||
|
clientWork: '',
|
||||||
|
boardRoles: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setIsModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseModal = () => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
setSelectedMember(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prevent background scroll when modal is open
|
||||||
|
useEffect(() => {
|
||||||
|
if (isModalOpen) {
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
} else {
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
};
|
||||||
|
}, [isModalOpen]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsVisible(true);
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
const timelineSection = document.querySelector('#timeline-fill-line') as HTMLElement | null;
|
||||||
|
const timelineContainer = timelineSection?.parentElement;
|
||||||
|
|
||||||
|
if (!timelineSection || !timelineContainer) return;
|
||||||
|
|
||||||
|
const rect = timelineContainer.getBoundingClientRect();
|
||||||
|
const windowHeight = window.innerHeight;
|
||||||
|
|
||||||
|
const sectionTop = rect.top;
|
||||||
|
const sectionHeight = rect.height;
|
||||||
|
const visibleTop = Math.max(0, windowHeight - sectionTop);
|
||||||
|
const visibleHeight = Math.min(visibleTop, sectionHeight);
|
||||||
|
const scrollProgress = Math.max(0, Math.min(1, visibleHeight / sectionHeight));
|
||||||
|
|
||||||
|
const maxFillHeight = 'calc(100% - 1rem)';
|
||||||
|
|
||||||
|
if (scrollProgress >= 0.9) {
|
||||||
|
timelineSection.style.height = maxFillHeight;
|
||||||
|
} else {
|
||||||
|
timelineSection.style.height = `${scrollProgress * 90}%`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
handleScroll();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('scroll', handleScroll);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-white">
|
||||||
|
<FullScreenLoader text="Loading articles..." />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
|
<div className="text-center">
|
||||||
|
<h2 className="text-2xl font-bold text-red-600 mb-4">Error Loading Page</h2>
|
||||||
|
<p className="text-gray-600 mb-4">Failed to load About Us content. Please try again later.</p>
|
||||||
|
<Button onClick={() => window.location.reload()}>Retry</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ backgroundColor: '#FFFFFF', fontFamily: 'var(--font-family-base)' }}>
|
||||||
|
{/* Hero Section - Dynamic from API */}
|
||||||
|
<section className="relative min-h-[85vh] flex flex-col">
|
||||||
|
<div className="absolute inset-0 z-0">
|
||||||
|
<div
|
||||||
|
className="w-full h-full bg-cover bg-center bg-no-repeat"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url('${aboutUsData?.hero_section?.background_image_url || 'https://images.unsplash.com/photo-1552664730-d307ca884978?w=1920&h=1080&fit=crop'}')`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-black/85 via-black/75 to-black/65"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative z-10 flex-1 flex items-center">
|
||||||
|
<div className="w-full section-margin-x">
|
||||||
|
<div className="max-w-6xl">
|
||||||
|
<div className="mb-8">
|
||||||
|
<h1 className="text-h1-white">
|
||||||
|
{aboutUsData?.hero_section?.headline || "Advancing Leadership Through Insight"}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-body-lg-white mb-8 max-w-3xl">
|
||||||
|
<strong>
|
||||||
|
{aboutUsData?.hero_section?.subtext || "Founded in 2016 with the vision of being a world class institution in the thought and practice of leadership. We facilitate institutions to build Leadership capacity and capability while helping individuals unleash their potential."}
|
||||||
|
</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex justify-start">
|
||||||
|
<PrimaryCTAButton
|
||||||
|
text={aboutUsData?.hero_section?.cta_text || "Talk to Us"}
|
||||||
|
onClick={() => navigateTo(aboutUsData?.hero_section?.cta_destination || '/contact?topic=management-development')}
|
||||||
|
ariaLabel="Talk to us about management development"
|
||||||
|
className="primary-cta-button-blue cta-text-white"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 1: Our Promise - Dynamic from API */}
|
||||||
|
<section className="py-24 lg:py-32" style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
<div className="section-margin-x">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="text-center"
|
||||||
|
>
|
||||||
|
<BrandedTag text={aboutUsData?.our_promise_title || "Our Promise"} />
|
||||||
|
<h2 className="text-h1 mb-8" style={{
|
||||||
|
fontSize: 'clamp(2.5rem, 5vw, 4rem)',
|
||||||
|
lineHeight: '1.1',
|
||||||
|
color: 'var(--color-black)'
|
||||||
|
}}>
|
||||||
|
We build leaders who deliver business results.
|
||||||
|
</h2>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 2: How We Work - Dynamic from API */}
|
||||||
|
<section className="py-24 lg:py-32" style={{ backgroundColor: '#F9F9F9' }}>
|
||||||
|
<div className="section-margin-x">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="text-center mb-16"
|
||||||
|
>
|
||||||
|
<BrandedTag text={aboutUsData?.how_we_work_title || "How We Work"} />
|
||||||
|
<h2 className="text-h2 mb-8">{aboutUsData?.how_we_work_title || "How We Work"}</h2>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 lg:gap-12">
|
||||||
|
{(aboutUsData?.how_we_work && aboutUsData.how_we_work.length > 0) ? (
|
||||||
|
aboutUsData.how_we_work.map((item, index) => (
|
||||||
|
<motion.div
|
||||||
|
key={item.id}
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.1 * (index + 1) }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="bg-white p-8 rounded-2xl shadow-lg border border-gray-100"
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
className="w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0"
|
||||||
|
style={{ backgroundColor: 'var(--color-primary)' }}
|
||||||
|
>
|
||||||
|
{index === 0 && <Puzzle className="w-6 h-6 text-white" />}
|
||||||
|
{index === 1 && <Target className="w-6 h-6 text-white" />}
|
||||||
|
{index === 2 && <BookOpen className="w-6 h-6 text-white" />}
|
||||||
|
{index === 3 && <Zap className="w-6 h-6 text-white" />}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-h4 mb-4">{item.title}</h3>
|
||||||
|
<p className="text-body text-muted leading-relaxed">
|
||||||
|
{item.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.1 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="bg-white p-8 rounded-2xl shadow-lg border border-gray-100"
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
className="w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0"
|
||||||
|
style={{ backgroundColor: 'var(--color-primary)' }}
|
||||||
|
>
|
||||||
|
<Puzzle className="w-6 h-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-h4 mb-4">Co-created interventions</h3>
|
||||||
|
<p className="text-body text-muted leading-relaxed">
|
||||||
|
We collaborate with you to design solutions that fit your unique organizational context and strategic objectives.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.2 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="bg-white p-8 rounded-2xl shadow-lg border border-gray-100"
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
className="w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0"
|
||||||
|
style={{ backgroundColor: 'var(--color-primary)' }}
|
||||||
|
>
|
||||||
|
<Target className="w-6 h-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-h4 mb-4">Grounded in business context</h3>
|
||||||
|
<p className="text-body text-muted leading-relaxed">
|
||||||
|
Every solution is tailored to your specific business environment, challenges, and growth objectives.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.3 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="bg-white p-8 rounded-2xl shadow-lg border border-gray-100"
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
className="w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0"
|
||||||
|
style={{ backgroundColor: 'var(--color-primary)' }}
|
||||||
|
>
|
||||||
|
<BookOpen className="w-6 h-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-h4 mb-4">Research-backed, behaviour-anchored</h3>
|
||||||
|
<p className="text-body text-muted leading-relaxed">
|
||||||
|
Our methodologies are rooted in rigorous research and focused on sustainable behavioral transformation.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.4 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="bg-white p-8 rounded-2xl shadow-lg border border-gray-100"
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
className="w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0"
|
||||||
|
style={{ backgroundColor: 'var(--color-primary)' }}
|
||||||
|
>
|
||||||
|
<Zap className="w-6 h-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-h4 mb-4">Delivered through immersive formats</h3>
|
||||||
|
<p className="text-body text-muted leading-relaxed">
|
||||||
|
Interactive, experiential learning approaches that engage participants and drive lasting impact.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 3: Who We Are - Updated Statistics - Dynamic from API */}
|
||||||
|
<section className="py-24 lg:py-32" style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
<div className="section-margin-x">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-5 gap-8 lg:gap-12 mb-16">
|
||||||
|
<div className="lg:col-span-1">
|
||||||
|
<div className="branded-tag-system">
|
||||||
|
<div className="dot"></div>
|
||||||
|
<span className="text">{aboutUsData?.who_we_are_title || "Who we are"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="lg:col-span-4">
|
||||||
|
<h2 className="text-h1 leading-tight" style={{
|
||||||
|
fontSize: 'clamp(2.5rem, 5vw, 4rem)',
|
||||||
|
lineHeight: '1.1',
|
||||||
|
color: 'var(--color-black)'
|
||||||
|
}}>
|
||||||
|
A premier Indian leadership development institution shaping India's most transformational leaders
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8 lg:gap-12 pt-12 border-t border-gray-200">
|
||||||
|
{(aboutUsData?.stat_section && aboutUsData.stat_section.length > 0) ? (
|
||||||
|
aboutUsData.stat_section.map((stat, index) => (
|
||||||
|
<motion.div
|
||||||
|
key={stat.id}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.1 * (index + 1) }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="text-center lg:text-left"
|
||||||
|
>
|
||||||
|
<div className="text-5xl lg:text-6xl font-medium mb-2" style={{
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: '1',
|
||||||
|
color: 'var(--color-primary)'
|
||||||
|
}}>
|
||||||
|
{stat.number}{stat.suffix}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-center lg:justify-start gap-2 text-body text-muted" style={{ fontFamily: 'var(--font-family-base)' }}>
|
||||||
|
<div className="w-2 h-2 rounded-sm" style={{ backgroundColor: 'var(--color-accent)' }}></div>
|
||||||
|
<span style={{ color: 'var(--color-black)', fontWeight: '500' }}>{stat.label.toUpperCase()}</span>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.1 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="text-center lg:text-left"
|
||||||
|
>
|
||||||
|
<div className="text-5xl lg:text-6xl font-medium mb-2" style={{
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: '1',
|
||||||
|
color: 'var(--color-primary)'
|
||||||
|
}}>
|
||||||
|
150+
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-center lg:justify-start gap-2 text-body text-muted" style={{ fontFamily: 'var(--font-family-base)' }}>
|
||||||
|
<div className="w-2 h-2 rounded-sm" style={{ backgroundColor: 'var(--color-accent)' }}></div>
|
||||||
|
<span style={{ color: 'var(--color-black)', fontWeight: '500' }}>CORPORATES</span>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.2 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="text-center lg:text-left"
|
||||||
|
>
|
||||||
|
<div className="text-5xl lg:text-6xl font-medium mb-2" style={{
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: '1',
|
||||||
|
color: 'var(--color-primary)'
|
||||||
|
}}>
|
||||||
|
27,000+
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-center lg:justify-start gap-2 text-body text-muted" style={{ fontFamily: 'var(--font-family-base)' }}>
|
||||||
|
<div className="w-2 h-2 rounded-sm" style={{ backgroundColor: 'var(--color-accent)' }}></div>
|
||||||
|
<span style={{ color: 'var(--color-black)', fontWeight: '500' }}>LEADERS</span>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.3 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="text-center lg:text-left"
|
||||||
|
>
|
||||||
|
<div className="text-5xl lg:text-6xl font-medium mb-2" style={{
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: '1',
|
||||||
|
color: 'var(--color-primary)'
|
||||||
|
}}>
|
||||||
|
5,000+
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-center lg:justify-start gap-2 text-body text-muted" style={{ fontFamily: 'var(--font-family-base)' }}>
|
||||||
|
<div className="w-2 h-2 rounded-sm" style={{ backgroundColor: 'var(--color-accent)' }}></div>
|
||||||
|
<span style={{ color: 'var(--color-black)', fontWeight: '500' }}>ROOM NIGHTS</span>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.4 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="text-center lg:text-left"
|
||||||
|
>
|
||||||
|
<div className="text-3xl lg:text-4xl font-medium mb-2" style={{
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: '1',
|
||||||
|
color: 'var(--color-primary)'
|
||||||
|
}}>
|
||||||
|
India & APAC
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-center lg:justify-start gap-2 text-body text-muted" style={{ fontFamily: 'var(--font-family-base)' }}>
|
||||||
|
<div className="w-2 h-2 rounded-sm" style={{ backgroundColor: 'var(--color-accent)' }}></div>
|
||||||
|
<span style={{ color: 'var(--color-black)', fontWeight: '500' }}>PRESENCE</span>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 4: Our Team - Dynamic from API (outer grid from API, modal from static) */}
|
||||||
|
<section className="py-24 lg:py-32" style={{ backgroundColor: '#F9F9F9' }}>
|
||||||
|
<div className="section-margin-x">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="text-center mb-16"
|
||||||
|
>
|
||||||
|
<BrandedTag text={aboutUsData?.our_team_title || "Our Team"} />
|
||||||
|
<h2 className="text-h2 mb-8">{aboutUsData?.our_team_title || "Our Team"}</h2>
|
||||||
|
<div className="max-w-4xl mx-auto text-center space-y-6">
|
||||||
|
<p className="text-body-lg text-muted leading-relaxed">
|
||||||
|
{aboutUsData?.our_team_description || "We have a team of 7 consultants and 4 young consultants. All our senior Consultants are ex-business professionals with experience ranging from 15-30 years in varied business functions and carry a deep understanding of the area they are engaging in. Two of them bring in Board room experience. – Meet them"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Team Members Grid - Using API data for outer display */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 lg:gap-12">
|
||||||
|
{apiTeamMembers.map((member, index) => {
|
||||||
|
const name = member.name_role.split(' - ')[0];
|
||||||
|
const role = member.name_role.split(' - ')[1] || 'Team Member';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
key={member.id || index}
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: index * 0.1 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="text-left cursor-pointer"
|
||||||
|
onClick={() => handleMemberClick(member)}
|
||||||
|
>
|
||||||
|
<div className="relative mb-6 group">
|
||||||
|
<div className="aspect-square rounded-2xl overflow-hidden bg-gray-100 shadow-lg group-hover:shadow-xl transition-all duration-300">
|
||||||
|
<img
|
||||||
|
src={member.photo_url}
|
||||||
|
alt={member.alt_text || name}
|
||||||
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||||
|
onError={(e) => {
|
||||||
|
(e.target as HTMLImageElement).src = 'https://ui-avatars.com/api/?name=' + encodeURIComponent(name) + '&background=04045B&color=fff&size=200';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-all duration-300 rounded-2xl flex items-center justify-center">
|
||||||
|
<div className="opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||||
|
<div
|
||||||
|
className="px-4 py-2 rounded-lg text-white text-small"
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#04045B',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontWeight: '500'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
View Profile
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="text-h4 text-black group-hover:text-primary transition-colors duration-300">{name}</h3>
|
||||||
|
<p className="text-body text-muted leading-relaxed font-medium">
|
||||||
|
{role}
|
||||||
|
</p>
|
||||||
|
{/* Bio Section */}
|
||||||
|
<p className="text-small text-muted leading-relaxed mt-2 line-clamp-3">
|
||||||
|
{member.bio || "No bio available"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 5: Our Methodology - Dynamic from API */}
|
||||||
|
{aboutUsData?.methodology && (
|
||||||
|
<section className="py-16 lg:py-20" style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
<div className="section-margin-x">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="text-center mb-16"
|
||||||
|
>
|
||||||
|
<BrandedTag text={aboutUsData.methodology.title || "Our Methodology"} />
|
||||||
|
{aboutUsData.methodology.subtitle && (
|
||||||
|
<h2 className="text-h2 mb-8">{aboutUsData.methodology.subtitle}</h2>
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<div className="relative max-w-6xl mx-auto">
|
||||||
|
<div
|
||||||
|
className="absolute left-4 top-0 w-0.5 bg-gray-300"
|
||||||
|
style={{
|
||||||
|
height: 'calc(100% - 1rem)',
|
||||||
|
zIndex: 1
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="timeline-fill-line"
|
||||||
|
className="absolute left-4 top-0 w-0.5 transition-all duration-1000 ease-out"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-primary)',
|
||||||
|
height: '0%',
|
||||||
|
maxHeight: 'calc(100% - 1rem)',
|
||||||
|
zIndex: 2
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
|
||||||
|
{[...(aboutUsData.methodology.phases || [])]
|
||||||
|
.sort((a, b) => (a.display_order || 0) - (b.display_order || 0))
|
||||||
|
.map((phase, phaseIndex) => (
|
||||||
|
<div key={phase.id || phaseIndex} className="relative pb-20">
|
||||||
|
<div className="grid lg:grid-cols-12 gap-8 lg:gap-12 pl-12">
|
||||||
|
<div
|
||||||
|
className="absolute left-3 top-1 w-2.5 h-2.5 rounded-full bg-white border-2 z-10"
|
||||||
|
style={{
|
||||||
|
borderColor: 'var(--color-primary)',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div className="lg:col-span-2">
|
||||||
|
<div className="branded-tag-system">
|
||||||
|
<div className="dot"></div>
|
||||||
|
<span className="text">{phase.phase_label || `Phase ${phase.phase_number}`}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="lg:col-span-3">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: phaseIndex * 0.2 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
>
|
||||||
|
<h3 className="text-h3">{phase.title}</h3>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="lg:col-span-7">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: phaseIndex * 0.2 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
>
|
||||||
|
<p className="text-body-lg text-muted leading-relaxed mb-6">
|
||||||
|
{phase.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{phase.bullet_title && phase.bullets && phase.bullets.length > 0 && (
|
||||||
|
<div className="mb-8">
|
||||||
|
<h4 className="text-h4 mb-4">{phase.bullet_title}</h4>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{phase.bullets.map((bullet, bulletIndex) => (
|
||||||
|
<div key={bulletIndex} className="flex items-start gap-3">
|
||||||
|
<div
|
||||||
|
className="w-1.5 h-1.5 rounded-full mt-2 flex-shrink-0"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-primary)',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<span className="text-body text-muted">
|
||||||
|
{bullet}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{aboutUsData?.philosophy && (
|
||||||
|
<div className="relative pb-20">
|
||||||
|
<div
|
||||||
|
className="absolute left-3 top-1 w-2.5 h-2.5 rounded-full bg-white border-2 z-10"
|
||||||
|
style={{
|
||||||
|
borderColor: 'var(--color-primary)',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div className="pl-12">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.6 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="rounded-2xl p-8 lg:p-12"
|
||||||
|
style={{
|
||||||
|
background: 'linear-gradient(135deg, var(--color-primary) 0%, #030359 100%)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col lg:flex-row gap-8 lg:gap-12">
|
||||||
|
<div className="lg:w-1/3">
|
||||||
|
<h3 className="text-h3 text-white mb-6 lg:mb-0">{aboutUsData.philosophy.title || "Our Philosophy"}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="lg:w-2/3">
|
||||||
|
<p className="text-body-white mb-8 opacity-90">
|
||||||
|
{aboutUsData.philosophy.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{aboutUsData.philosophy.points && aboutUsData.philosophy.points.length > 0 && (
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
{aboutUsData.philosophy.points.map((point, pointIndex) => (
|
||||||
|
<div key={pointIndex} className="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
className="bg-[#f8c301] content-stretch flex items-center justify-center rounded-full shrink-0 size-[32px]"
|
||||||
|
>
|
||||||
|
<svg className="block size-[16px]" fill="none" preserveAspectRatio="none" viewBox="0 0 16 16">
|
||||||
|
<g>
|
||||||
|
<path d="M3.33398 8H12.6673" stroke="#26231A" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.33333" />
|
||||||
|
<path d={svgPaths.p2c1c9a80} stroke="#26231A" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.33333" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<p className="font-['Inter',sans-serif] font-normal leading-[25.6px] text-[16px] text-white">
|
||||||
|
{point}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.3 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="flex justify-center items-center mt-16"
|
||||||
|
>
|
||||||
|
<PrimaryCTAButton
|
||||||
|
text="Design your leadership journey"
|
||||||
|
onClick={() => navigateTo('/contact')}
|
||||||
|
ariaLabel="Contact us to design your leadership journey"
|
||||||
|
className="cta-text-black"
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Testimonials Section */}
|
||||||
|
<TestimonialsSection
|
||||||
|
customTestimonials={testimonialsData}
|
||||||
|
title="What Our Clients Say About Us"
|
||||||
|
subtitle="Hear from leaders who have transformed their approach through our comprehensive leadership development programs."
|
||||||
|
tagText="Client Success Stories"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* CTA Banner Section - Dynamic from API */}
|
||||||
|
<CTABannerSection
|
||||||
|
ctaSection={aboutUsData?.cta_section}
|
||||||
|
isLoading={isLoading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
767
.gitea/workflows/src/components/Articles.tsx
Normal file
@@ -0,0 +1,767 @@
|
|||||||
|
import {
|
||||||
|
BookOpen,
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
Filter,
|
||||||
|
Grid,
|
||||||
|
List,
|
||||||
|
Search,
|
||||||
|
X
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { useRef, useState, useEffect } from 'react';
|
||||||
|
import { BlogItem, useGetBlogsQuery } from '../redux/services/blogApi';
|
||||||
|
import { useGetFaqCategoriesQuery } from '../redux/services/faqApi';
|
||||||
|
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||||
|
import { PrimaryCTAButton } from './PrimaryCTAButton';
|
||||||
|
import { navigateTo } from './Router';
|
||||||
|
import { Badge } from './ui/badge';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Card, CardContent } from './ui/card';
|
||||||
|
import { Input } from './ui/input';
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
|
||||||
|
import { FullScreenLoader } from './FullScreenLoader';
|
||||||
|
import { getSlugWithId } from '../utils/urlHelpers';
|
||||||
|
|
||||||
|
// Define category type with ID and name
|
||||||
|
interface CategoryOption {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type DateRange =
|
||||||
|
| 'all_time'
|
||||||
|
| 'last_7_days'
|
||||||
|
| 'last_30_days'
|
||||||
|
| 'last_3_months'
|
||||||
|
| 'last_6_months';
|
||||||
|
|
||||||
|
type SortBy =
|
||||||
|
| 'most_recent'
|
||||||
|
| 'oldest_first'
|
||||||
|
| 'title_az';
|
||||||
|
|
||||||
|
export function Articles() {
|
||||||
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState<CategoryOption>({ id: 'all', name: 'All Categories' });
|
||||||
|
const [selectedReadTime, setSelectedReadTime] = useState('All Read Times');
|
||||||
|
const [selectedDateRange, setSelectedDateRange] = useState<DateRange>('all_time');
|
||||||
|
const [selectedTopic, setSelectedTopic] = useState<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}>({
|
||||||
|
id: 'all',
|
||||||
|
name: 'All Topics'
|
||||||
|
});
|
||||||
|
const [sortBy, setSortBy] = useState<SortBy>('most_recent');
|
||||||
|
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [debouncedSearch, setDebouncedSearch] = useState('');
|
||||||
|
const articlesPerPage = 6;
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [allTags, setAllTags] = useState<{ id: string; name: string }[]>([]);
|
||||||
|
|
||||||
|
// Fetch categories for filter dropdown
|
||||||
|
const {
|
||||||
|
data: categoriesData,
|
||||||
|
isLoading: isLoadingCategories
|
||||||
|
} = useGetFaqCategoriesQuery({
|
||||||
|
limit: 100,
|
||||||
|
offset: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter categories to only those for blog and create options with id and name
|
||||||
|
const categories: CategoryOption[] = [
|
||||||
|
{ id: 'all', name: 'All Categories' },
|
||||||
|
...(categoriesData?.data?.items
|
||||||
|
?.filter((category: any) => category.for_blog)
|
||||||
|
.map((category: any) => ({
|
||||||
|
id: category.id,
|
||||||
|
name: category.category_name
|
||||||
|
})) || [])
|
||||||
|
];
|
||||||
|
|
||||||
|
// Debounce search term
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setDebouncedSearch(searchTerm);
|
||||||
|
}, 500);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [searchTerm]);
|
||||||
|
|
||||||
|
// Fetch blogs from API with all parameters
|
||||||
|
const {
|
||||||
|
data: blogsData,
|
||||||
|
isLoading: isLoadingBlogs,
|
||||||
|
isError: isBlogsError,
|
||||||
|
refetch: refetchBlogs
|
||||||
|
} = useGetBlogsQuery({
|
||||||
|
limit: articlesPerPage,
|
||||||
|
offset: (currentPage - 1) * articlesPerPage,
|
||||||
|
search: debouncedSearch || undefined,
|
||||||
|
content_status: 'publish',
|
||||||
|
content_type: 'BLOG',
|
||||||
|
date_range: selectedDateRange !== 'all_time' ? selectedDateRange : undefined,
|
||||||
|
sort_by: sortBy,
|
||||||
|
content_category_id: selectedCategory.id !== 'all' ? selectedCategory.id : undefined, // Send UUID
|
||||||
|
tag_id: selectedTopic.id !== 'all' ? selectedTopic.id : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const sortOptions = [
|
||||||
|
{ value: 'most_recent', label: 'Most Recent' },
|
||||||
|
{ value: 'oldest_first', label: 'Oldest First' },
|
||||||
|
{ value: 'title_az', label: 'Title A-Z' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const readTimes = ['All Read Times', 'Under 5 min', '5-10 min', 'Over 10 min'];
|
||||||
|
const dateRanges = [
|
||||||
|
{ value: 'all_time', label: 'All Time' },
|
||||||
|
{ value: 'last_7_days', label: 'Last 7 days' },
|
||||||
|
{ value: 'last_30_days', label: 'Last 30 days' },
|
||||||
|
{ value: 'last_3_months', label: 'Last 3 months' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Format date function
|
||||||
|
const formatDate = (dateString: string) => {
|
||||||
|
return new Date(dateString).toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate read time based on content length (approx)
|
||||||
|
const calculateReadTime = (content: string): string => {
|
||||||
|
const wordsPerMinute = 200;
|
||||||
|
const wordCount = content.split(/\s+/).length;
|
||||||
|
const minutes = Math.ceil(wordCount / wordsPerMinute);
|
||||||
|
return `${minutes} min read`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filter articles by read time (client-side only - API doesn't support this)
|
||||||
|
const getReadTimeFilteredArticles = () => {
|
||||||
|
if (selectedReadTime === 'All Read Times' || !blogsData?.data?.items) {
|
||||||
|
return blogsData?.data?.items || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (blogsData?.data?.items || []).filter((blog: BlogItem) => {
|
||||||
|
const readTimeMinutes = parseInt(calculateReadTime(blog.content).replace(' min read', '')) || 0;
|
||||||
|
return (selectedReadTime === 'Under 5 min' && readTimeMinutes < 5) ||
|
||||||
|
(selectedReadTime === '5-10 min' && readTimeMinutes >= 5 && readTimeMinutes <= 10) ||
|
||||||
|
(selectedReadTime === 'Over 10 min' && readTimeMinutes > 10);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const finalFilteredArticles = getReadTimeFilteredArticles();
|
||||||
|
|
||||||
|
const totalPages = Math.ceil((blogsData?.data?.pagination?.total || 0) / articlesPerPage);
|
||||||
|
|
||||||
|
const clearAllFilters = () => {
|
||||||
|
setSearchTerm('');
|
||||||
|
setDebouncedSearch('');
|
||||||
|
setSelectedCategory({ id: 'all', name: 'All Categories' });
|
||||||
|
setSelectedReadTime('All Read Times');
|
||||||
|
setSelectedDateRange('all_time');
|
||||||
|
setSelectedTopic({ id: 'all', name: 'All Topics' });
|
||||||
|
setSortBy('most_recent');
|
||||||
|
setCurrentPage(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasActiveFilters = Boolean(
|
||||||
|
searchTerm ||
|
||||||
|
selectedCategory.id !== 'all' ||
|
||||||
|
selectedReadTime !== 'All Read Times' ||
|
||||||
|
selectedDateRange !== 'all_time' ||
|
||||||
|
selectedTopic.id !== 'all'
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!blogsData?.data?.items || allTags.length > 0) return;
|
||||||
|
|
||||||
|
const tags = Array.from(
|
||||||
|
new Map(
|
||||||
|
blogsData.data.items
|
||||||
|
.flatMap((blog: BlogItem) => blog.blog_tags || [])
|
||||||
|
.map(tag => [tag.id, { id: tag.id, name: tag.tag_name }])
|
||||||
|
).values()
|
||||||
|
);
|
||||||
|
|
||||||
|
setAllTags(tags);
|
||||||
|
}, [blogsData]);
|
||||||
|
|
||||||
|
// Handle loading state
|
||||||
|
if (isLoadingBlogs || isLoadingCategories) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-white">
|
||||||
|
<FullScreenLoader text="Loading articles..." />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle error state
|
||||||
|
if (isBlogsError) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-white">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<X className="w-8 h-8 text-red-600" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-h3 mb-2">Failed to load articles</h2>
|
||||||
|
<p className="text-gray-600 mb-4">Please try again later</p>
|
||||||
|
<Button onClick={() => refetchBlogs()}>
|
||||||
|
Retry
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ backgroundColor: '#FFFFFF' }} ref={containerRef}>
|
||||||
|
{/* Hero Section */}
|
||||||
|
<section className="relative py-28 overflow-hidden">
|
||||||
|
<div className="absolute inset-0">
|
||||||
|
<ImageWithFallback
|
||||||
|
src="https://images.unsplash.com/photo-1481627834876-b7833e8f5570?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhcnRpY2xlJTIwYmxvZyUyMGNvbnRlbnQlMjBrbm93bGVkZ2V8ZW58MXx8fHwxNzU1ODU0Mjg2fDA&ixlib=rb-4.1.0&q=80&w=1080"
|
||||||
|
alt="Knowledge and insights through articles and research"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-black/60" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative section-margin-x">
|
||||||
|
<div className="max-w-4xl mx-auto text-center">
|
||||||
|
<div className="branded-tag-system-white mb-6 justify-center">
|
||||||
|
<div className="dot"></div>
|
||||||
|
<span className="text">INSIGHTS & KNOWLEDGE</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="text-h1-white mb-6">
|
||||||
|
Articles & Research
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="text-body-lg-white mb-8 max-w-2xl mx-auto">
|
||||||
|
Discover cutting-edge insights, research findings, and expert perspectives on leadership development, management strategies, and organizational excellence.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Statistics Strip */}
|
||||||
|
<div className="absolute bottom-0 left-0 right-0">
|
||||||
|
<div className="bg-black/80 backdrop-blur-sm px-8 py-6">
|
||||||
|
<div className="section-margin-x">
|
||||||
|
<div className="grid grid-cols-3 gap-8 text-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-h2-white mb-2">{blogsData?.data?.pagination?.total || 0}+</div>
|
||||||
|
<div className="text-small-white">Expert Articles</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-h2-white mb-2">{categories.length - 1}</div>
|
||||||
|
<div className="text-small-white">Categories</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-h2-white mb-2">{allTags.length}+</div>
|
||||||
|
<div className="text-small-white">Topics</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Search and Controls Section */}
|
||||||
|
<section className="py-8" style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
<div className="section-margin-x">
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6">
|
||||||
|
{/* Search Bar */}
|
||||||
|
<div className="relative max-w-md flex-1">
|
||||||
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search articles..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
className="pl-10 pr-4 py-3 text-body rounded-lg border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all duration-200 w-full bg-gray-50"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
height: '48px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* View Toggle and Sort */}
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex items-center border border-gray-300 rounded-lg overflow-hidden">
|
||||||
|
<button
|
||||||
|
onClick={() => setViewMode('grid')}
|
||||||
|
className={`p-2 transition-colors ${viewMode === 'grid'
|
||||||
|
? 'text-white'
|
||||||
|
: 'bg-white text-gray-600 hover:bg-gray-50'
|
||||||
|
}`}
|
||||||
|
style={{
|
||||||
|
backgroundColor: viewMode === 'grid' ? 'var(--color-primary)' : undefined
|
||||||
|
}}
|
||||||
|
aria-label="Grid view"
|
||||||
|
>
|
||||||
|
<Grid className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setViewMode('list')}
|
||||||
|
className={`p-2 transition-colors ${viewMode === 'list'
|
||||||
|
? 'text-white'
|
||||||
|
: 'bg-white text-gray-600 hover:bg-gray-50'
|
||||||
|
}`}
|
||||||
|
style={{
|
||||||
|
backgroundColor: viewMode === 'list' ? 'var(--color-primary)' : undefined
|
||||||
|
}}
|
||||||
|
aria-label="List view"
|
||||||
|
>
|
||||||
|
<List className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Select value={sortBy} onValueChange={setSortBy}>
|
||||||
|
<SelectTrigger className="w-40 text-body">
|
||||||
|
<SelectValue placeholder="Sort by" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{sortOptions.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Main Content Section with Sidebar */}
|
||||||
|
<section className="pb-16" style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
<div className="section-margin-x">
|
||||||
|
<div className="grid grid-cols-12 gap-8">
|
||||||
|
{/* Left Sidebar - Sticky Filters */}
|
||||||
|
<div className="col-span-12 lg:col-span-3">
|
||||||
|
<div className="sticky top-4">
|
||||||
|
<Card className="bg-white border border-gray-200 rounded-lg shadow-md overflow-hidden">
|
||||||
|
{/* Filter Header */}
|
||||||
|
<div className="bg-gray-50 px-4 py-3 border-b border-gray-200">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="p-1.5 rounded-md" style={{ backgroundColor: 'rgba(4, 4, 91, 0.1)' }}>
|
||||||
|
<Filter className="w-3.5 h-3.5" style={{ color: 'var(--color-primary)' }} />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-body font-semibold text-gray-800">
|
||||||
|
Filters
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
{hasActiveFilters && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={clearAllFilters}
|
||||||
|
className="text-xs px-2 py-1 rounded-md transition-colors filter-clear-btn"
|
||||||
|
>
|
||||||
|
<X className="w-3 h-3 mr-1" />
|
||||||
|
Clear
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filter Content */}
|
||||||
|
<div className="p-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Category Filter */}
|
||||||
|
<div className="filter-section">
|
||||||
|
<label className="block text-small mb-2 font-medium text-gray-700">
|
||||||
|
Category
|
||||||
|
</label>
|
||||||
|
<Select
|
||||||
|
value={selectedCategory.id}
|
||||||
|
onValueChange={(value: string) => {
|
||||||
|
const category = categories.find(c => c.id === value);
|
||||||
|
if (category) {
|
||||||
|
setSelectedCategory(category);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-full text-small h-9 border-gray-300 hover:border-gray-400 transition-colors">
|
||||||
|
<SelectValue placeholder="All Categories">
|
||||||
|
{selectedCategory.name}
|
||||||
|
</SelectValue>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{categories.map((category) => (
|
||||||
|
<SelectItem key={category.id} value={category.id} className="text-small">
|
||||||
|
{category.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Read Time Filter - Client-side only */}
|
||||||
|
<div className="filter-section">
|
||||||
|
<label className="block text-small mb-2 font-medium text-gray-700">
|
||||||
|
Read Time
|
||||||
|
</label>
|
||||||
|
<Select value={selectedReadTime} onValueChange={setSelectedReadTime}>
|
||||||
|
<SelectTrigger className="w-full text-small h-9 border-gray-300 hover:border-gray-400 transition-colors">
|
||||||
|
<SelectValue placeholder="All Read Times" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{readTimes.map((readTime) => (
|
||||||
|
<SelectItem key={readTime} value={readTime} className="text-small">
|
||||||
|
{readTime}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Date Range Filter */}
|
||||||
|
<div className="filter-section">
|
||||||
|
<label className="block text-small mb-2 font-medium text-gray-700">
|
||||||
|
Date Range
|
||||||
|
</label>
|
||||||
|
<Select value={selectedDateRange} onValueChange={setSelectedDateRange}>
|
||||||
|
<SelectTrigger className="w-full text-small h-9 border-gray-300 hover:border-gray-400 transition-colors">
|
||||||
|
<SelectValue placeholder="All Time" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{dateRanges.map((range) => (
|
||||||
|
<SelectItem key={range.value} value={range.value}>
|
||||||
|
{range.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Topics Filter */}
|
||||||
|
<div className="filter-section">
|
||||||
|
<label className="block text-small mb-2 font-medium text-gray-700">
|
||||||
|
Topic
|
||||||
|
</label>
|
||||||
|
<Select
|
||||||
|
value={selectedTopic.id}
|
||||||
|
onValueChange={(value: string) => {
|
||||||
|
if (value === 'all') {
|
||||||
|
setSelectedTopic({ id: 'all', name: 'All Topics' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const topic = allTags.find(t => t.id === value);
|
||||||
|
if (topic) setSelectedTopic(topic);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-full text-small h-9 border-gray-300 hover:border-gray-400 transition-colors">
|
||||||
|
<SelectValue placeholder="All Topics" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="all">
|
||||||
|
All Topics
|
||||||
|
</SelectItem>
|
||||||
|
|
||||||
|
{allTags.map((tag) => (
|
||||||
|
<SelectItem key={tag.id} value={tag.id}>
|
||||||
|
{tag.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Content Area - Scrollable Articles */}
|
||||||
|
<div className="col-span-12 lg:col-span-9">
|
||||||
|
<div className="mb-4 text-small text-muted">
|
||||||
|
Showing {finalFilteredArticles.length} of {blogsData?.data?.pagination?.total || 0} articles
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Articles Results */}
|
||||||
|
{finalFilteredArticles.length === 0 ? (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<BookOpen className="w-16 h-16 text-gray-400 mx-auto mb-4" />
|
||||||
|
<p className="text-body-lg text-muted">
|
||||||
|
No articles found matching your criteria.
|
||||||
|
</p>
|
||||||
|
{hasActiveFilters && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={clearAllFilters}
|
||||||
|
className="mt-4"
|
||||||
|
>
|
||||||
|
Clear Filters
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* Grid View */}
|
||||||
|
{viewMode === 'grid' && (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
{finalFilteredArticles.map((article: BlogItem) => (
|
||||||
|
<Card
|
||||||
|
key={article.id}
|
||||||
|
className="overflow-hidden hover:shadow-lg transition-all duration-300 cursor-pointer group"
|
||||||
|
onClick={() => {
|
||||||
|
// Use slug_name to create the URL with full UUID
|
||||||
|
const url = getSlugWithId(article.slug_name, article.id);
|
||||||
|
navigateTo(`/learning/articles/${url}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="aspect-video w-full bg-gray-100 overflow-hidden relative">
|
||||||
|
<ImageWithFallback
|
||||||
|
src={article.banner_img || 'https://images.unsplash.com/photo-1481627834876-b7833e8f5570?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhcnRpY2xlJTIwYmxvZyUyMGNvbnRlbnQlMjBrbm93bGVkZ2V8ZW58MXx8fHwxNzU1ODU0Mjg2fDA&ixlib=rb-4.1.0&q=80&w=1080'}
|
||||||
|
alt={article.title}
|
||||||
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="flex items-center gap-2 mb-3">
|
||||||
|
<Badge variant="outline" className="text-small">
|
||||||
|
{article.content_category}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-small text-muted">
|
||||||
|
{calculateReadTime(article.content)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 className="text-h4 mb-3 group-hover:text-[#04045B] transition-colors line-clamp-2">
|
||||||
|
{article.title}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p className="text-small text-muted mb-4 line-clamp-3">
|
||||||
|
{article.short_description || article.content.substring(0, 150) + '...'}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-end gap-2">
|
||||||
|
<div className="text-small text-muted">
|
||||||
|
{formatDate(article.updated_at)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Display tags if available */}
|
||||||
|
{article.blog_tags && article.blog_tags.length > 0 && (
|
||||||
|
<div className="flex flex-wrap gap-1 mt-3 pt-3 border-t border-gray-100">
|
||||||
|
{article.blog_tags.slice(0, 3).map((tag, idx) => (
|
||||||
|
<Badge key={idx} variant="secondary" className="text-xs">
|
||||||
|
{tag.tag_name}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
{article.blog_tags.length > 3 && (
|
||||||
|
<span className="text-xs text-gray-500">+{article.blog_tags.length - 3}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* List View */}
|
||||||
|
{viewMode === 'list' && (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{finalFilteredArticles.map((article: BlogItem) => (
|
||||||
|
<Card
|
||||||
|
key={article.id}
|
||||||
|
className="overflow-hidden hover:shadow-lg transition-all duration-300 cursor-pointer group"
|
||||||
|
onClick={() => {
|
||||||
|
// Use slug_name to create the URL with full UUID
|
||||||
|
const url = getSlugWithId(article.slug_name, article.id);
|
||||||
|
navigateTo(`/learning/articles/${url}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col md:flex-row">
|
||||||
|
<div className="md:w-80 h-48 md:h-auto bg-gray-100 overflow-hidden relative flex-shrink-0">
|
||||||
|
<ImageWithFallback
|
||||||
|
src={article.banner_img || 'https://images.unsplash.com/photo-1481627834876-b7833e8f5570?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhcnRpY2xlJTIwYmxvZyUyMGNvbnRlbnQlMjBrbm93bGVkZ2V8ZW58MXx8fHwxNzU1ODU0Mjg2fDA&ixlib=rb-4.1.0&q=80&w=1080'}
|
||||||
|
alt={article.title}
|
||||||
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 p-6">
|
||||||
|
<div className="flex justify-between items-start">
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<Badge variant="outline" className="text-small">
|
||||||
|
{article.content_category}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-small text-muted">
|
||||||
|
{calculateReadTime(article.content)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 className="text-h4 mb-2 group-hover:text-[#04045B] transition-colors">
|
||||||
|
{article.title}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p className="text-body text-muted mb-3">
|
||||||
|
{article.short_description || article.content.substring(0, 200) + '...'}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="text-small text-muted">
|
||||||
|
{formatDate(article.updated_at)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Display tags if available */}
|
||||||
|
{article.blog_tags && article.blog_tags.length > 0 && (
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{article.blog_tags.slice(0, 2).map((tag, idx) => (
|
||||||
|
<Badge key={idx} variant="secondary" className="text-xs">
|
||||||
|
{tag.tag_name}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
{article.blog_tags.length > 2 && (
|
||||||
|
<span className="text-xs text-gray-500">+{article.blog_tags.length - 2}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<div className="flex items-center justify-center gap-2 mt-8">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setCurrentPage(prev => Math.max(1, prev - 1));
|
||||||
|
containerRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
}}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
className="flex items-center gap-1 border-gray-300 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-4 h-4" />
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{Array.from({ length: totalPages }, (_, i) => {
|
||||||
|
const page = i + 1;
|
||||||
|
// Show limited pages for better UX
|
||||||
|
if (totalPages > 7) {
|
||||||
|
const showPage =
|
||||||
|
page === 1 ||
|
||||||
|
page === totalPages ||
|
||||||
|
(page >= currentPage - 1 && page <= currentPage + 1);
|
||||||
|
|
||||||
|
if (!showPage) {
|
||||||
|
if (page === currentPage - 2 || page === currentPage + 2) {
|
||||||
|
return <span key={page} className="px-2">...</span>;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={page}
|
||||||
|
variant={currentPage === page ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
containerRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
}}
|
||||||
|
className={`min-w-10 ${currentPage === page
|
||||||
|
? 'bg-[#04045B] text-white hover:bg-[#04045B]'
|
||||||
|
: 'border-gray-300 text-gray-700 hover:bg-gray-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{page}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setCurrentPage(prev => Math.min(totalPages, prev + 1));
|
||||||
|
containerRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
}}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
className="flex items-center gap-1 border-gray-300 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
<ChevronRight className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* CTA Banner Section */}
|
||||||
|
<section className="relative h-[700px] overflow-hidden">
|
||||||
|
<div className="absolute inset-0">
|
||||||
|
<ImageWithFallback
|
||||||
|
src="https://images.unsplash.com/photo-1753613648191-4771cf76f034?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxvbmxpbmUlMjBsZWFybmluZyUyMGRpZ2l0YWwlMjBlZHVjYXRpb258ZW58MXx8fHwxNzU1ODU0Mjc1fDA&ixlib=rb-4.1.0&q=80&w=1080"
|
||||||
|
alt="Online learning and digital education environment"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-black/30" />
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-black/20 via-transparent to-black/60" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative h-full flex items-center justify-end section-margin-x">
|
||||||
|
<div
|
||||||
|
className="bg-opacity-95 backdrop-blur-sm rounded-lg p-16 max-w-2xl"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-brand-primary)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="branded-tag-system-next-steps mb-6 justify-start">
|
||||||
|
<div className="dot"></div>
|
||||||
|
<span className="text">NEXT STEPS</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 className="text-h2-white mb-8">
|
||||||
|
Ready to explore more insights?
|
||||||
|
<span
|
||||||
|
className="italic"
|
||||||
|
style={{ color: 'var(--color-brand-accent)' }}
|
||||||
|
>
|
||||||
|
{" "}Discover{" "}
|
||||||
|
</span>
|
||||||
|
our complete library of leadership resources.
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<PrimaryCTAButton
|
||||||
|
text="Browse All Resources"
|
||||||
|
onClick={() => navigateTo('/learning/articles')}
|
||||||
|
ariaLabel="Browse all leadership articles and resources"
|
||||||
|
className="cta-banner-yellow mb-6"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p className="text-body-white opacity-90">
|
||||||
|
Access cutting-edge research, expert insights, and practical guidance to accelerate your leadership journey and organizational success.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
54
.gitea/workflows/src/components/AuthContext.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import React, { createContext, useContext, useState, ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
corporateName: string;
|
||||||
|
avatar?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AuthContextType {
|
||||||
|
user: User | null;
|
||||||
|
isAuthenticated: boolean;
|
||||||
|
signIn: (user: User) => void;
|
||||||
|
signOut: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
interface AuthProviderProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AuthProvider({ children }: AuthProviderProps) {
|
||||||
|
const [user, setUser] = useState<User | null>(null);
|
||||||
|
|
||||||
|
const signIn = (userData: User) => {
|
||||||
|
setUser(userData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const signOut = () => {
|
||||||
|
setUser(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const value: AuthContextType = {
|
||||||
|
user,
|
||||||
|
isAuthenticated: !!user,
|
||||||
|
signIn,
|
||||||
|
signOut,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuth() {
|
||||||
|
const context = useContext(AuthContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useAuth must be used within an AuthProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
546
.gitea/workflows/src/components/BlogDetail.tsx
Normal file
@@ -0,0 +1,546 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Card, CardContent } from './ui/card';
|
||||||
|
import { Badge } from './ui/badge';
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar';
|
||||||
|
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||||
|
import { CTABannerSection } from './CTABannerSection';
|
||||||
|
import { useCart } from './CartContext';
|
||||||
|
import {
|
||||||
|
Calendar,
|
||||||
|
Clock,
|
||||||
|
ChevronUp,
|
||||||
|
Bookmark,
|
||||||
|
Twitter,
|
||||||
|
Facebook,
|
||||||
|
Linkedin,
|
||||||
|
Link,
|
||||||
|
Heart,
|
||||||
|
Eye,
|
||||||
|
BookOpen,
|
||||||
|
ArrowLeft
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { useGetBlogByIDQuery, useGetBlogsQuery } from '../redux/services/blogApi';
|
||||||
|
import { FullScreenLoader } from './FullScreenLoader';
|
||||||
|
import { extractIdFromSlug, extractSlugFromSlugAndId, getSlugWithId } from '../utils/urlHelpers';
|
||||||
|
|
||||||
|
interface BlogDetailProps {
|
||||||
|
params?: {
|
||||||
|
slugAndId?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BlogDetail({ params }: BlogDetailProps) {
|
||||||
|
const { slugAndId } = useParams<{ slugAndId: string }>();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [scrollProgress, setScrollProgress] = useState(0);
|
||||||
|
const [showBackToTop, setShowBackToTop] = useState(false);
|
||||||
|
const [isLiked, setIsLiked] = useState(false);
|
||||||
|
const [isBookmarked, setIsBookmarked] = useState(false);
|
||||||
|
const { addToCart } = useCart();
|
||||||
|
|
||||||
|
// Extract full ID from URL using the new function
|
||||||
|
const fullId = slugAndId ? extractIdFromSlug(slugAndId) : null;
|
||||||
|
const urlSlug = slugAndId ? extractSlugFromSlugAndId(slugAndId) : '';
|
||||||
|
|
||||||
|
// Fetch blog details by ID directly - no dependency on list API
|
||||||
|
const {
|
||||||
|
data: blogPost,
|
||||||
|
isLoading: isLoadingBlog,
|
||||||
|
isError: isBlogError,
|
||||||
|
refetch: refetchBlog
|
||||||
|
} = useGetBlogByIDQuery(fullId as string, {
|
||||||
|
skip: !fullId,
|
||||||
|
refetchOnMountOrArgChange: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch related blogs (excluding current blog)
|
||||||
|
const {
|
||||||
|
data: relatedBlogsData,
|
||||||
|
isLoading: isLoadingRelated
|
||||||
|
} = useGetBlogsQuery({
|
||||||
|
limit: 3,
|
||||||
|
offset: 0,
|
||||||
|
content_status: 'publish',
|
||||||
|
content_type: 'BLOG',
|
||||||
|
}, {
|
||||||
|
skip: !fullId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// SEO: Check if URL slug matches the actual slug_name and redirect if needed
|
||||||
|
useEffect(() => {
|
||||||
|
if (blogPost && fullId) {
|
||||||
|
// Get the expected slug from the blog post
|
||||||
|
const expectedSlug = blogPost.slug_name;
|
||||||
|
// Create the expected URL with proper formatting
|
||||||
|
const expectedUrl = getSlugWithId(expectedSlug, fullId);
|
||||||
|
// Get the current URL slug
|
||||||
|
const currentSlugAndId = slugAndId || '';
|
||||||
|
|
||||||
|
// Compare (case-insensitive)
|
||||||
|
if (currentSlugAndId.toLowerCase() !== expectedUrl.toLowerCase()) {
|
||||||
|
// Redirect to the correct URL
|
||||||
|
navigate(`/learning/articles/${expectedUrl}`, { replace: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [blogPost, fullId, slugAndId, navigate]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (blogPost?.title) {
|
||||||
|
document.title = `${blogPost.title} | KLC Blog`;
|
||||||
|
}
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
const winScroll = document.body.scrollTop || document.documentElement.scrollTop;
|
||||||
|
const height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
|
||||||
|
const scrolled = (winScroll / height) * 100;
|
||||||
|
|
||||||
|
setScrollProgress(scrolled);
|
||||||
|
setShowBackToTop(winScroll > 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
return () => window.removeEventListener('scroll', handleScroll);
|
||||||
|
}, [blogPost?.title]);
|
||||||
|
|
||||||
|
const formatDate = (dateString: string) => {
|
||||||
|
return new Date(dateString).toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShare = (platform: string) => {
|
||||||
|
const url = window.location.href;
|
||||||
|
const title = blogPost?.title || '';
|
||||||
|
|
||||||
|
const shareUrls = {
|
||||||
|
twitter: `https://twitter.com/intent/tweet?text=${encodeURIComponent(title)}&url=${encodeURIComponent(url)}`,
|
||||||
|
facebook: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(url)}`,
|
||||||
|
linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(url)}`,
|
||||||
|
copy: url
|
||||||
|
};
|
||||||
|
|
||||||
|
if (platform === 'copy') {
|
||||||
|
navigator.clipboard.writeText(url);
|
||||||
|
alert('Link copied to clipboard!');
|
||||||
|
} else {
|
||||||
|
window.open(shareUrls[platform as keyof typeof shareUrls], '_blank', 'width=600,height=400');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollToTop = () => {
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate read time based on content length (approx)
|
||||||
|
const calculateReadTime = (content: string): string => {
|
||||||
|
const wordsPerMinute = 200;
|
||||||
|
const wordCount = content.split(/\s+/).length;
|
||||||
|
const minutes = Math.ceil(wordCount / wordsPerMinute);
|
||||||
|
return `${minutes} min read`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filter related blogs (exclude current blog)
|
||||||
|
const relatedPosts = relatedBlogsData?.data?.items
|
||||||
|
?.filter((blog: any) => blog.id !== fullId)
|
||||||
|
.map((blog: any) => ({
|
||||||
|
id: blog.id,
|
||||||
|
title: blog.title,
|
||||||
|
slug: blog.slug_name,
|
||||||
|
excerpt: blog.short_description || blog.content.substring(0, 150) + '...',
|
||||||
|
author: blog.author_name || 'KLC Team',
|
||||||
|
publishedDate: blog.updated_at,
|
||||||
|
readTime: calculateReadTime(blog.content),
|
||||||
|
category: blog.content_category,
|
||||||
|
image: blog.banner_img || 'https://images.unsplash.com/photo-1552664730-d307ca884978?w=400&h=300&fit=crop'
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
// Handle related post click - use full UUID
|
||||||
|
const handleRelatedPostClick = (postSlug: string, postId: string) => {
|
||||||
|
const url = getSlugWithId(postSlug, postId);
|
||||||
|
navigate(`/learning/articles/${url}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle loading state
|
||||||
|
if (isLoadingBlog) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-white">
|
||||||
|
<FullScreenLoader text="Loading article..." />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle error state
|
||||||
|
if (isBlogError || !blogPost) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-white">
|
||||||
|
<div className="text-center max-w-md px-4">
|
||||||
|
<div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<BookOpen className="w-8 h-8 text-red-600" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-h3 mb-2">Article Not Found</h2>
|
||||||
|
<p className="text-gray-600 mb-6">
|
||||||
|
The article you're looking for could not be found. It may have been moved or removed.
|
||||||
|
</p>
|
||||||
|
<Button onClick={() => navigate('/learning/articles')}>
|
||||||
|
Back to Articles
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen" style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
{/* Scroll Progress Bar */}
|
||||||
|
<div className="fixed top-0 left-0 w-full h-1 z-50" style={{ backgroundColor: 'rgba(0, 0, 0, 0.1)' }}>
|
||||||
|
<div
|
||||||
|
className="h-full transition-all duration-150"
|
||||||
|
style={{
|
||||||
|
width: `${scrollProgress}%`,
|
||||||
|
backgroundColor: '#04045B'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Back to Top Button */}
|
||||||
|
{showBackToTop && (
|
||||||
|
<Button
|
||||||
|
onClick={scrollToTop}
|
||||||
|
size="icon"
|
||||||
|
className="fixed bottom-8 right-8 z-40 rounded-full shadow-lg"
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#04045B',
|
||||||
|
color: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ChevronUp className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<main className="pt-20">
|
||||||
|
{/* Consistent Side Gutters - Breadcrumb Navigation */}
|
||||||
|
<div className="section-margin-x mb-8">
|
||||||
|
<div className="flex items-center gap-3 text-small" style={{ color: '#6F6F6F' }}>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => navigate('/learning/articles')}
|
||||||
|
className="p-0 h-auto font-medium hover:bg-transparent transition-colors"
|
||||||
|
style={{ color: '#6F6F6F' }}
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||||
|
Back to Articles
|
||||||
|
</Button>
|
||||||
|
<span className="text-[#E5E7EB]">•</span>
|
||||||
|
<span>{blogPost.content_category || 'Article'}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Content Container with Consistent Gutters */}
|
||||||
|
<div className="section-margin-x">
|
||||||
|
{/* Reading Width Constraint for Better Readability */}
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
{/* Hero Header - Improved Spacing */}
|
||||||
|
<header className="mb-16">
|
||||||
|
{/* Category Badge */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<Badge
|
||||||
|
className="mb-6 text-small px-4 py-2 font-medium border-none"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'rgba(4, 4, 91, 0.1)',
|
||||||
|
color: '#04045B'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{blogPost.content_category || 'Article'}
|
||||||
|
</Badge>
|
||||||
|
|
||||||
|
{/* Improved Typography Hierarchy */}
|
||||||
|
<h1 className="text-h1 mb-6 leading-tight" style={{ color: '#26231A' }}>
|
||||||
|
{blogPost.title}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{/* Constrained Width Excerpt for Better Readability */}
|
||||||
|
<div className="max-w-3xl">
|
||||||
|
<p className="text-body-lg leading-relaxed" style={{ color: '#6F6F6F' }}>
|
||||||
|
{blogPost.short_description || blogPost.content.substring(0, 200) + '...'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Enhanced Meta Bar with Cleaner Spacing */}
|
||||||
|
<div
|
||||||
|
className="flex flex-col lg:flex-row items-start lg:items-center justify-between gap-6 p-6 rounded-xl border"
|
||||||
|
style={{ backgroundColor: 'rgba(0, 0, 0, 0.02)', borderColor: 'rgba(0, 0, 0, 0.08)' }}
|
||||||
|
>
|
||||||
|
{/* Author Info with Improved Layout */}
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Avatar className="w-14 h-14 ring-2 ring-white shadow-md">
|
||||||
|
<AvatarImage src="https://images.unsplash.com/photo-1494790108755-2616b612b47c?w=150&h=150&fit=crop" alt="Author" />
|
||||||
|
<AvatarFallback className="text-subhead font-medium">KLC</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div>
|
||||||
|
<div className="text-subhead font-medium mb-1" style={{ color: '#26231A' }}>
|
||||||
|
KLC Team
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Cleaner Meta Information with Subtle Dividers */}
|
||||||
|
<div className="flex items-center gap-4 text-small" style={{ color: '#6F6F6F' }}>
|
||||||
|
<span className="flex items-center gap-1.5">
|
||||||
|
<Calendar className="w-4 h-4" />
|
||||||
|
{formatDate(blogPost.updated_at || new Date().toISOString())}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div className="w-1 h-1 rounded-full" style={{ backgroundColor: '#E5E7EB' }}></div>
|
||||||
|
|
||||||
|
<span className="flex items-center gap-1.5">
|
||||||
|
<Clock className="w-4 h-4" />
|
||||||
|
{calculateReadTime(blogPost.content)}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div className="w-1 h-1 rounded-full" style={{ backgroundColor: '#E5E7EB' }}></div>
|
||||||
|
|
||||||
|
<span className="flex items-center gap-1.5">
|
||||||
|
<Eye className="w-4 h-4" />
|
||||||
|
0
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action Buttons with Better Spacing */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setIsLiked(!isLiked)}
|
||||||
|
className={`transition-colors ${isLiked ? 'text-red-500' : 'text-[#6F6F6F]'}`}
|
||||||
|
>
|
||||||
|
<Heart className={`w-4 h-4 mr-2 ${isLiked ? 'fill-current' : ''}`} />
|
||||||
|
0
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setIsBookmarked(!isBookmarked)}
|
||||||
|
className={`transition-colors ${isBookmarked ? 'text-[#04045B]' : 'text-[#6F6F6F]'}`}
|
||||||
|
>
|
||||||
|
<Bookmark className={`w-4 h-4 ${isBookmarked ? 'fill-current' : ''}`} />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Share Options */}
|
||||||
|
<div className="flex items-center gap-1 ml-2 pl-2 border-l border-[#E5E7EB]">
|
||||||
|
<Button variant="ghost" size="sm" onClick={() => handleShare('twitter')} className="text-[#6F6F6F] hover:text-[#04045B]">
|
||||||
|
<Twitter className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" size="sm" onClick={() => handleShare('linkedin')} className="text-[#6F6F6F] hover:text-[#04045B]">
|
||||||
|
<Linkedin className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" size="sm" onClick={() => handleShare('copy')} className="text-[#6F6F6F] hover:text-[#04045B]">
|
||||||
|
<Link className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Featured Image with Better Aspect Ratio */}
|
||||||
|
<div className="aspect-[16/9] rounded-xl overflow-hidden mt-8 shadow-lg">
|
||||||
|
<ImageWithFallback
|
||||||
|
src={blogPost.banner_img || 'https://images.unsplash.com/photo-1552664730-d307ca884978?w=1200&h=600&fit=crop'}
|
||||||
|
alt={blogPost.title}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Article Body with Enhanced Typography - Full Width */}
|
||||||
|
<article className="mb-20">
|
||||||
|
{/* Full Width Container - Uses complete available width */}
|
||||||
|
<div className="w-full">
|
||||||
|
<div
|
||||||
|
className="prose prose-xl max-w-none blog-article-content w-full"
|
||||||
|
style={{
|
||||||
|
/* Enhanced Typography Hierarchy using Design System */
|
||||||
|
'--tw-prose-body': '#26231A',
|
||||||
|
'--tw-prose-headings': '#26231A',
|
||||||
|
'--tw-prose-lead': '#26231A',
|
||||||
|
'--tw-prose-links': '#04045B',
|
||||||
|
'--tw-prose-bold': '#26231A',
|
||||||
|
'--tw-prose-counters': '#6F6F6F',
|
||||||
|
'--tw-prose-bullets': '#6F6F6F',
|
||||||
|
'--tw-prose-hr': 'rgba(0, 0, 0, 0.1)',
|
||||||
|
'--tw-prose-quotes': '#04045B',
|
||||||
|
'--tw-prose-quote-borders': '#04045B',
|
||||||
|
'--tw-prose-captions': '#6F6F6F',
|
||||||
|
'--tw-prose-code': '#04045B',
|
||||||
|
'--tw-prose-pre-code': '#26231A',
|
||||||
|
'--tw-prose-pre-bg': 'rgba(0, 0, 0, 0.05)',
|
||||||
|
'--tw-prose-th-borders': 'rgba(0, 0, 0, 0.15)',
|
||||||
|
'--tw-prose-td-borders': 'rgba(0, 0, 0, 0.1)',
|
||||||
|
|
||||||
|
/* Typography Scale using Design System Tokens */
|
||||||
|
fontSize: 'var(--font-body-lg)',
|
||||||
|
lineHeight: '1.75',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
color: '#26231A',
|
||||||
|
width: '100%'
|
||||||
|
} as React.CSSProperties}
|
||||||
|
dangerouslySetInnerHTML={{ __html: blogPost.content }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
{/* Enhanced Tag Pills with Hover States */}
|
||||||
|
{blogPost.blog_tags && blogPost.blog_tags.length > 0 && (
|
||||||
|
<div className="mb-16">
|
||||||
|
<h3 className="text-subhead mb-6 font-medium" style={{ color: '#26231A' }}>
|
||||||
|
Topics covered in this article
|
||||||
|
</h3>
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{blogPost.blog_tags.map((tag: any) => (
|
||||||
|
<Badge
|
||||||
|
key={tag.tag_name}
|
||||||
|
className="transition-all duration-200 text-body px-4 py-2 font-medium"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'rgba(4, 4, 91, 0.08)',
|
||||||
|
color: '#04045B',
|
||||||
|
border: '1px solid rgba(4, 4, 91, 0.15)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tag.tag_name}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Enhanced Author Bio Card */}
|
||||||
|
<Card className="mb-16 shadow-md border-0" style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
<CardContent className="p-8">
|
||||||
|
<div className="flex items-start gap-6">
|
||||||
|
<Avatar className="w-20 h-20 ring-4 ring-white shadow-lg">
|
||||||
|
<AvatarImage src="https://images.unsplash.com/photo-1494790108755-2616b612b47c?w=150&h=150&fit=crop" alt="Author" />
|
||||||
|
<AvatarFallback className="text-lg font-medium">KLC</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h4 className="text-h4 mb-3 font-semibold" style={{ color: '#26231A' }}>
|
||||||
|
About KLC Team
|
||||||
|
</h4>
|
||||||
|
<p className="text-body leading-relaxed mb-6" style={{ color: '#6F6F6F' }}>
|
||||||
|
The Kautilya Leadership Center team is dedicated to providing cutting-edge insights and research on leadership development, management strategies, and organizational excellence.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Related Articles Section with Balanced Grid Layout */}
|
||||||
|
{relatedPosts.length > 0 && (
|
||||||
|
<section className="py-20" style={{ backgroundColor: 'rgba(0, 0, 0, 0.02)' }}>
|
||||||
|
<div className="section-margin-x">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<div className="branded-tag-system mb-6">
|
||||||
|
<div className="dot"></div>
|
||||||
|
<span className="text">Continue Learning</span>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-h2 mb-6 font-bold" style={{ color: '#26231A' }}>
|
||||||
|
Explore More Leadership Insights
|
||||||
|
</h2>
|
||||||
|
<p className="text-body-lg max-w-2xl mx-auto" style={{ color: '#6F6F6F' }}>
|
||||||
|
Discover additional strategies and frameworks to enhance your leadership effectiveness
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Balanced Card Grid with Equal Spacing */}
|
||||||
|
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
|
{relatedPosts.map((post: any) => (
|
||||||
|
<Card
|
||||||
|
key={post.id}
|
||||||
|
className="overflow-hidden hover:shadow-xl transition-all duration-300 cursor-pointer group border-0"
|
||||||
|
onClick={() => {
|
||||||
|
// Use the same pattern as the main articles with full UUID
|
||||||
|
const url = getSlugWithId(post.slug, post.id);
|
||||||
|
navigate(`/learning/articles/${url}`);
|
||||||
|
}}
|
||||||
|
style={{ backgroundColor: '#FFFFFF' }}
|
||||||
|
>
|
||||||
|
<div className="aspect-[16/10] w-full bg-gray-100 overflow-hidden relative">
|
||||||
|
<ImageWithFallback
|
||||||
|
src={post.image}
|
||||||
|
alt={post.title}
|
||||||
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="text-small border-none"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'rgba(4, 4, 91, 0.1)',
|
||||||
|
color: '#04045B'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{post.category}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-small" style={{ color: '#6F6F6F' }}>
|
||||||
|
{post.readTime}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3
|
||||||
|
className="mb-3 group-hover:text-blue-600 transition-colors line-clamp-2"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h4)',
|
||||||
|
fontWeight: 'var(--font-weight-h4)',
|
||||||
|
lineHeight: '1.3',
|
||||||
|
color: '#26231A',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{post.title}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p
|
||||||
|
className="mb-4 line-clamp-3"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
lineHeight: '1.5',
|
||||||
|
color: '#6F6F6F',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{post.excerpt}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between pt-4 border-t" style={{ borderColor: 'rgba(0, 0, 0, 0.05)' }}>
|
||||||
|
<span className="text-small" style={{ color: '#6F6F6F' }}>
|
||||||
|
{post.author}
|
||||||
|
</span>
|
||||||
|
<span className="text-small" style={{ color: '#6F6F6F' }}>
|
||||||
|
{formatDate(post.publishedDate)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* CTA Section */}
|
||||||
|
<CTABannerSection />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
483
.gitea/workflows/src/components/Blogs.tsx
Normal file
@@ -0,0 +1,483 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Badge } from './ui/badge';
|
||||||
|
import { Calendar, Clock, User, ChevronLeft, ChevronRight, Search, Filter } from 'lucide-react';
|
||||||
|
import { Input } from './ui/input';
|
||||||
|
import { navigateTo } from './Router';
|
||||||
|
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||||
|
|
||||||
|
// Mock blog data
|
||||||
|
const blogPosts = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
slug: 'future-of-leadership-development',
|
||||||
|
title: 'The Future of Leadership Development: Trends and Innovations',
|
||||||
|
excerpt: 'Explore emerging trends in leadership development, from AI-powered coaching to virtual reality training experiences that are reshaping how leaders learn and grow.',
|
||||||
|
content: 'Full blog content would go here...',
|
||||||
|
author: 'Dr. Sarah Mitchell',
|
||||||
|
authorRole: 'Chief Learning Officer',
|
||||||
|
publishDate: '2024-02-15',
|
||||||
|
readTime: '8 min read',
|
||||||
|
category: 'Leadership',
|
||||||
|
tags: ['Leadership Development', 'Innovation', 'Future Trends', 'Technology'],
|
||||||
|
thumbnail: 'https://images.unsplash.com/photo-1552664730-d307ca884978?w=600&h=400&fit=crop',
|
||||||
|
featured: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
slug: 'building-emotional-intelligence',
|
||||||
|
title: 'Building Emotional Intelligence: A Leader\'s Guide to Self-Awareness',
|
||||||
|
excerpt: 'Discover practical strategies for developing emotional intelligence and how self-aware leaders create more engaged and productive teams.',
|
||||||
|
content: 'Full blog content would go here...',
|
||||||
|
author: 'Marcus Rodriguez',
|
||||||
|
authorRole: 'Leadership Coach',
|
||||||
|
publishDate: '2024-02-10',
|
||||||
|
readTime: '6 min read',
|
||||||
|
category: 'Personal Development',
|
||||||
|
tags: ['Emotional Intelligence', 'Self-Awareness', 'Team Building', 'Communication'],
|
||||||
|
thumbnail: 'https://images.unsplash.com/photo-1559027615-cd4628902d4a?w=600&h=400&fit=crop',
|
||||||
|
featured: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
slug: 'remote-leadership-strategies',
|
||||||
|
title: 'Remote Leadership Strategies: Managing Distributed Teams Effectively',
|
||||||
|
excerpt: 'Learn proven strategies for leading remote teams, maintaining culture, and driving performance in a distributed work environment.',
|
||||||
|
content: 'Full blog content would go here...',
|
||||||
|
author: 'Dr. Emily Chen',
|
||||||
|
authorRole: 'Remote Work Expert',
|
||||||
|
publishDate: '2024-02-05',
|
||||||
|
readTime: '10 min read',
|
||||||
|
category: 'Remote Work',
|
||||||
|
tags: ['Remote Leadership', 'Virtual Teams', 'Digital Communication', 'Performance Management'],
|
||||||
|
thumbnail: 'https://images.unsplash.com/photo-1600880292203-757bb62b4baf?w=600&h=400&fit=crop',
|
||||||
|
featured: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
slug: 'diversity-inclusion-leadership',
|
||||||
|
title: 'Diversity and Inclusion: Leading Change in Your Organization',
|
||||||
|
excerpt: 'A comprehensive guide to implementing meaningful diversity and inclusion initiatives that drive real organizational change.',
|
||||||
|
content: 'Full blog content would go here...',
|
||||||
|
author: 'Jennifer Adams',
|
||||||
|
authorRole: 'D&I Consultant',
|
||||||
|
publishDate: '2024-01-28',
|
||||||
|
readTime: '12 min read',
|
||||||
|
category: 'Diversity & Inclusion',
|
||||||
|
tags: ['Diversity', 'Inclusion', 'Organizational Change', 'Culture'],
|
||||||
|
thumbnail: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=600&h=400&fit=crop',
|
||||||
|
featured: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
slug: 'agile-leadership-mindset',
|
||||||
|
title: 'Developing an Agile Leadership Mindset for Rapid Change',
|
||||||
|
excerpt: 'Explore how agile principles can transform your leadership approach and help organizations navigate uncertainty and rapid change.',
|
||||||
|
content: 'Full blog content would go here...',
|
||||||
|
author: 'David Park',
|
||||||
|
authorRole: 'Agile Coach',
|
||||||
|
publishDate: '2024-01-20',
|
||||||
|
readTime: '7 min read',
|
||||||
|
category: 'Agile Leadership',
|
||||||
|
tags: ['Agile', 'Change Management', 'Adaptability', 'Innovation'],
|
||||||
|
thumbnail: 'https://images.unsplash.com/photo-1552664730-d307ca884978?w=600&h=400&fit=crop',
|
||||||
|
featured: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
slug: 'coaching-vs-mentoring',
|
||||||
|
title: 'Coaching vs. Mentoring: Understanding the Difference and When to Use Each',
|
||||||
|
excerpt: 'Learn the key differences between coaching and mentoring, and discover when each approach is most effective for leadership development.',
|
||||||
|
content: 'Full blog content would go here...',
|
||||||
|
author: 'Lisa Thompson',
|
||||||
|
authorRole: 'Executive Coach',
|
||||||
|
publishDate: '2024-01-15',
|
||||||
|
readTime: '9 min read',
|
||||||
|
category: 'Coaching',
|
||||||
|
tags: ['Coaching', 'Mentoring', 'Leadership Development', 'Professional Growth'],
|
||||||
|
thumbnail: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=600&h=400&fit=crop',
|
||||||
|
featured: false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export function Blogs() {
|
||||||
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState('All');
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const postsPerPage = 6;
|
||||||
|
|
||||||
|
// Get unique categories
|
||||||
|
const categories = ['All', ...Array.from(new Set(blogPosts.map(post => post.category)))];
|
||||||
|
|
||||||
|
// Filter posts based on search and category
|
||||||
|
const filteredPosts = blogPosts.filter(post => {
|
||||||
|
const matchesSearch = post.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
|
post.excerpt.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
|
post.author.toLowerCase().includes(searchTerm.toLowerCase());
|
||||||
|
const matchesCategory = selectedCategory === 'All' || post.category === selectedCategory;
|
||||||
|
return matchesSearch && matchesCategory;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Paginate results
|
||||||
|
const totalPages = Math.ceil(filteredPosts.length / postsPerPage);
|
||||||
|
const currentPosts = filteredPosts.slice((currentPage - 1) * postsPerPage, currentPage * postsPerPage);
|
||||||
|
|
||||||
|
const formatDate = (dateString: string) => {
|
||||||
|
return new Date(dateString).toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ backgroundColor: 'var(--color-bg-white)' }}>
|
||||||
|
{/* Header Section */}
|
||||||
|
<div className="py-12">
|
||||||
|
<div className="hero-margin-x">
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<div className="branded-tag-system mb-6">
|
||||||
|
<div className="dot"></div>
|
||||||
|
<span className="text">INSIGHTS & EXPERTISE</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1
|
||||||
|
className="mb-6"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h1)',
|
||||||
|
fontWeight: 'var(--font-weight-h1)',
|
||||||
|
lineHeight: 'var(--line-height-h1)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Leadership Insights Blog
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p
|
||||||
|
className="max-w-3xl mx-auto"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body-lg)',
|
||||||
|
lineHeight: 'var(--line-height-body-lg)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Discover thought-provoking articles, expert insights, and practical strategies from leading voices in organizational development and leadership excellence.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Search and Filter Section */}
|
||||||
|
<div className="mb-12">
|
||||||
|
<div className="flex flex-col lg:flex-row gap-6 items-center justify-between">
|
||||||
|
{/* Search Bar */}
|
||||||
|
<div className="relative flex-1 max-w-md">
|
||||||
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search articles..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
className="pl-10"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Category Filter */}
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{categories.map((category) => (
|
||||||
|
<Button
|
||||||
|
key={category}
|
||||||
|
variant={selectedCategory === category ? "default" : "outline"}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedCategory(category);
|
||||||
|
setCurrentPage(1);
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
backgroundColor: selectedCategory === category ? 'var(--color-primary)' : 'transparent',
|
||||||
|
borderColor: 'var(--color-primary)',
|
||||||
|
color: selectedCategory === category ? 'white' : 'var(--color-primary)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{category}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Featured Posts Section */}
|
||||||
|
{searchTerm === '' && selectedCategory === 'All' && (
|
||||||
|
<div className="mb-16">
|
||||||
|
<h2
|
||||||
|
className="mb-8"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h2)',
|
||||||
|
fontWeight: 'var(--font-weight-h2)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Featured Articles
|
||||||
|
</h2>
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||||
|
{blogPosts.filter(post => post.featured).slice(0, 2).map((post) => (
|
||||||
|
<article
|
||||||
|
key={post.id}
|
||||||
|
className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden hover:shadow-lg transition-all duration-300 cursor-pointer"
|
||||||
|
onClick={() => navigateTo(`/learning/blogs/${post.slug}`)}
|
||||||
|
>
|
||||||
|
<div className="aspect-video w-full bg-gray-100">
|
||||||
|
<ImageWithFallback
|
||||||
|
src={post.thumbnail}
|
||||||
|
alt={post.title}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="p-6">
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'rgba(248, 195, 1, 0.1)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{post.category}
|
||||||
|
</Badge>
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
borderColor: 'var(--color-primary)',
|
||||||
|
color: 'var(--color-primary)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Featured
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3
|
||||||
|
className="mb-3 hover:text-blue-600 transition-colors"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h3)',
|
||||||
|
fontWeight: 'var(--font-weight-h3)',
|
||||||
|
lineHeight: 'var(--line-height-h3)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{post.title}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p
|
||||||
|
className="mb-4"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
lineHeight: 'var(--line-height-body)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{post.excerpt}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between text-sm text-gray-500">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<User className="w-4 h-4" />
|
||||||
|
<span style={{ fontSize: 'var(--font-small)', fontFamily: 'var(--font-family-base)' }}>
|
||||||
|
{post.author}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Calendar className="w-4 h-4" />
|
||||||
|
<span style={{ fontSize: 'var(--font-small)', fontFamily: 'var(--font-family-base)' }}>
|
||||||
|
{formatDate(post.publishDate)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Clock className="w-4 h-4" />
|
||||||
|
<span style={{ fontSize: 'var(--font-small)', fontFamily: 'var(--font-family-base)' }}>
|
||||||
|
{post.readTime}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* All Posts Grid */}
|
||||||
|
<div className="mb-12">
|
||||||
|
<h2
|
||||||
|
className="mb-8"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h2)',
|
||||||
|
fontWeight: 'var(--font-weight-h2)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{searchTerm || selectedCategory !== 'All' ? 'Search Results' : 'All Articles'}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{currentPosts.length === 0 ? (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body-lg)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
No articles found matching your criteria.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
|
{currentPosts.map((post) => (
|
||||||
|
<article
|
||||||
|
key={post.id}
|
||||||
|
className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden hover:shadow-lg transition-all duration-300 cursor-pointer group"
|
||||||
|
onClick={() => navigateTo(`/learning/blogs/${post.slug}`)}
|
||||||
|
>
|
||||||
|
<div className="aspect-video w-full bg-gray-100 overflow-hidden">
|
||||||
|
<ImageWithFallback
|
||||||
|
src={post.thumbnail}
|
||||||
|
alt={post.title}
|
||||||
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="p-6">
|
||||||
|
<div className="flex items-center gap-3 mb-3">
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'rgba(248, 195, 1, 0.1)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{post.category}
|
||||||
|
</Badge>
|
||||||
|
{post.featured && (
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
borderColor: 'var(--color-primary)',
|
||||||
|
color: 'var(--color-primary)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Featured
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3
|
||||||
|
className="mb-3 group-hover:text-blue-600 transition-colors"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h4)',
|
||||||
|
fontWeight: 'var(--font-weight-h4)',
|
||||||
|
lineHeight: 'var(--line-height-h4)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{post.title}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p
|
||||||
|
className="mb-4"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
lineHeight: 'var(--line-height-small)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{post.excerpt}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between text-sm text-gray-500">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<User className="w-4 h-4" />
|
||||||
|
<span style={{ fontSize: 'var(--font-small)', fontFamily: 'var(--font-family-base)' }}>
|
||||||
|
{post.author}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Clock className="w-4 h-4" />
|
||||||
|
<span style={{ fontSize: 'var(--font-small)', fontFamily: 'var(--font-family-base)' }}>
|
||||||
|
{post.readTime}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<div className="flex items-center justify-center gap-4">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
borderColor: 'var(--color-primary)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-4 h-4 mr-2" />
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
color: 'var(--color-black)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Page {currentPage} of {totalPages}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setCurrentPage(prev => Math.min(prev + 1, totalPages))}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
borderColor: 'var(--color-primary)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
<ChevronRight className="w-4 h-4 ml-2" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
991
.gitea/workflows/src/components/BookFacility.tsx
Normal file
@@ -0,0 +1,991 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { navigateTo } from './Router';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
|
||||||
|
import { Badge } from './ui/badge';
|
||||||
|
import { Input } from './ui/input';
|
||||||
|
import { Label } from './ui/label';
|
||||||
|
import { Textarea } from './ui/textarea';
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
|
||||||
|
import { Calendar } from './ui/calendar';
|
||||||
|
import { Alert, AlertDescription } from './ui/alert';
|
||||||
|
import { Separator } from './ui/separator';
|
||||||
|
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Users,
|
||||||
|
MapPin,
|
||||||
|
Clock,
|
||||||
|
CheckCircle,
|
||||||
|
CreditCard,
|
||||||
|
Calendar as CalendarIcon,
|
||||||
|
Building,
|
||||||
|
Phone,
|
||||||
|
Mail,
|
||||||
|
User,
|
||||||
|
ArrowLeft,
|
||||||
|
ArrowRight,
|
||||||
|
Wifi,
|
||||||
|
Coffee,
|
||||||
|
Car,
|
||||||
|
Utensils,
|
||||||
|
Copy,
|
||||||
|
Download,
|
||||||
|
AlertCircle,
|
||||||
|
XCircle,
|
||||||
|
Zap,
|
||||||
|
Award,
|
||||||
|
Shield
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
interface BookingFormData {
|
||||||
|
companyName: string;
|
||||||
|
contactName: string;
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
role: string;
|
||||||
|
teamSize: string;
|
||||||
|
facilityZone: string;
|
||||||
|
additionalRequirements: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TimeSlot {
|
||||||
|
time: string;
|
||||||
|
available: boolean;
|
||||||
|
price: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BookingConfirmation {
|
||||||
|
referenceId: string;
|
||||||
|
facilityName: string;
|
||||||
|
date: string;
|
||||||
|
timeSlot: string;
|
||||||
|
totalAmount: number;
|
||||||
|
companyName: string;
|
||||||
|
contactName: string;
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const facilityData = {
|
||||||
|
id: 'executive-boardroom',
|
||||||
|
name: 'Executive Boardroom',
|
||||||
|
description: 'Premium boardroom designed for high-level meetings and strategic discussions with state-of-the-art technology.',
|
||||||
|
capacity: 20,
|
||||||
|
pricePerHour: 2500,
|
||||||
|
image: 'https://images.unsplash.com/photo-1560472354-b33ff0c44a43?w=800&h=600&fit=crop',
|
||||||
|
features: [
|
||||||
|
'4K video conferencing system',
|
||||||
|
'Premium leather seating',
|
||||||
|
'Climate controlled environment',
|
||||||
|
'Wireless presentation system',
|
||||||
|
'Premium catering options',
|
||||||
|
'Dedicated technical support'
|
||||||
|
],
|
||||||
|
amenities: [
|
||||||
|
{ icon: Wifi, name: 'High-Speed WiFi' },
|
||||||
|
{ icon: Coffee, name: 'Refreshment Station' },
|
||||||
|
{ icon: Car, name: 'Valet Parking' },
|
||||||
|
{ icon: Utensils, name: 'Premium Catering' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateTimeSlots = (date: Date): TimeSlot[] => {
|
||||||
|
const slots: TimeSlot[] = [];
|
||||||
|
const isWeekend = date.getDay() === 0 || date.getDay() === 6;
|
||||||
|
|
||||||
|
if (isWeekend) {
|
||||||
|
return []; // No slots available on weekends
|
||||||
|
}
|
||||||
|
|
||||||
|
const startHour = 9;
|
||||||
|
const endHour = 18;
|
||||||
|
|
||||||
|
for (let hour = startHour; hour < endHour; hour++) {
|
||||||
|
const timeString = `${hour.toString().padStart(2, '0')}:00`;
|
||||||
|
const available = Math.random() > 0.3; // Random availability for demo
|
||||||
|
slots.push({
|
||||||
|
time: timeString,
|
||||||
|
available,
|
||||||
|
price: facilityData.pricePerHour
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return slots;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateReferenceId = (): string => {
|
||||||
|
return 'KLC-' + Date.now().toString().slice(-8).toUpperCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
export function BookFacility() {
|
||||||
|
const [currentStep, setCurrentStep] = useState<'booking' | 'confirmation' | 'payment-success' | 'payment-failed'>('booking');
|
||||||
|
const [selectedDate, setSelectedDate] = useState<Date | undefined>(new Date());
|
||||||
|
const [selectedTimeSlot, setSelectedTimeSlot] = useState<string>('');
|
||||||
|
const [timeSlots, setTimeSlots] = useState<TimeSlot[]>([]);
|
||||||
|
const [bookingForm, setBookingForm] = useState<BookingFormData>({
|
||||||
|
companyName: '',
|
||||||
|
contactName: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
role: '',
|
||||||
|
teamSize: '',
|
||||||
|
facilityZone: 'executive-wing',
|
||||||
|
additionalRequirements: ''
|
||||||
|
});
|
||||||
|
const [bookingConfirmation, setBookingConfirmation] = useState<BookingConfirmation | null>(null);
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = 'Book Facility - Kautilya Leadership Centre';
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedDate) {
|
||||||
|
setTimeSlots(generateTimeSlots(selectedDate));
|
||||||
|
setSelectedTimeSlot(''); // Reset time slot when date changes
|
||||||
|
}
|
||||||
|
}, [selectedDate]);
|
||||||
|
|
||||||
|
const formatPrice = (price: number) => {
|
||||||
|
return new Intl.NumberFormat('en-IN', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'INR',
|
||||||
|
maximumFractionDigits: 0
|
||||||
|
}).format(price);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDate = (date: Date) => {
|
||||||
|
return date.toLocaleDateString('en-IN', {
|
||||||
|
weekday: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmitBooking = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!selectedDate || !selectedTimeSlot) return;
|
||||||
|
|
||||||
|
setIsSubmitting(true);
|
||||||
|
|
||||||
|
// Simulate API call
|
||||||
|
setTimeout(() => {
|
||||||
|
const selectedSlot = timeSlots.find(slot => slot.time === selectedTimeSlot);
|
||||||
|
const confirmation: BookingConfirmation = {
|
||||||
|
referenceId: generateReferenceId(),
|
||||||
|
facilityName: facilityData.name,
|
||||||
|
date: formatDate(selectedDate),
|
||||||
|
timeSlot: selectedTimeSlot,
|
||||||
|
totalAmount: selectedSlot?.price || facilityData.pricePerHour,
|
||||||
|
companyName: bookingForm.companyName,
|
||||||
|
contactName: bookingForm.contactName,
|
||||||
|
email: bookingForm.email,
|
||||||
|
phone: bookingForm.phone
|
||||||
|
};
|
||||||
|
|
||||||
|
setBookingConfirmation(confirmation);
|
||||||
|
setCurrentStep('confirmation');
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePayNow = () => {
|
||||||
|
// Simulate payment processing
|
||||||
|
setIsSubmitting(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
const success = Math.random() > 0.2; // 80% success rate for demo
|
||||||
|
setCurrentStep(success ? 'payment-success' : 'payment-failed');
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyReferenceId = () => {
|
||||||
|
if (bookingConfirmation) {
|
||||||
|
navigator.clipboard.writeText(bookingConfirmation.referenceId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFormValid = () => {
|
||||||
|
return (
|
||||||
|
bookingForm.companyName &&
|
||||||
|
bookingForm.contactName &&
|
||||||
|
bookingForm.email &&
|
||||||
|
bookingForm.phone &&
|
||||||
|
bookingForm.role &&
|
||||||
|
bookingForm.teamSize &&
|
||||||
|
selectedDate &&
|
||||||
|
selectedTimeSlot
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const availableTimeSlots = timeSlots.filter(slot => slot.available);
|
||||||
|
|
||||||
|
if (currentStep === 'payment-success') {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen" style={{ backgroundColor: '#F7F7FD' }}>
|
||||||
|
<div className="pt-40 pb-16">
|
||||||
|
<div className="mx-auto section-margin-x">
|
||||||
|
<div className="max-w-2xl mx-auto text-center">
|
||||||
|
<div
|
||||||
|
className="w-20 h-20 rounded-full flex items-center justify-center mx-auto mb-6"
|
||||||
|
style={{ backgroundColor: 'rgba(34, 197, 94, 0.1)' }}
|
||||||
|
>
|
||||||
|
<CheckCircle className="w-10 h-10 text-green-600" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="text-h2 mb-4">
|
||||||
|
Payment Successful!
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="text-body-lg text-muted mb-8">
|
||||||
|
Your facility booking has been confirmed and payment processed successfully.
|
||||||
|
You'll receive a confirmation email shortly.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{bookingConfirmation && (
|
||||||
|
<Card className="mb-8 text-left" style={{ backgroundColor: 'var(--color-bg-white)' }}>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-subhead">Booking Confirmed</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4 text-body">
|
||||||
|
<div>
|
||||||
|
<span className="text-muted">Reference ID:</span>
|
||||||
|
<div className="text-subhead">{bookingConfirmation.referenceId}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-muted">Amount Paid:</span>
|
||||||
|
<div className="text-subhead text-green-600">
|
||||||
|
{formatPrice(bookingConfirmation.totalAmount)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
|
<button
|
||||||
|
className="brand-button-system"
|
||||||
|
onClick={() => navigateTo('/')}
|
||||||
|
>
|
||||||
|
<Download className="w-5 h-5" />
|
||||||
|
Download Receipt
|
||||||
|
</button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
onClick={() => navigateTo('/')}
|
||||||
|
className="text-body"
|
||||||
|
>
|
||||||
|
Return to Homepage
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentStep === 'payment-failed') {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen" style={{ backgroundColor: '#F7F7FD' }}>
|
||||||
|
<div className="pt-40 pb-16">
|
||||||
|
<div className="mx-auto section-margin-x">
|
||||||
|
<div className="max-w-2xl mx-auto text-center">
|
||||||
|
<div
|
||||||
|
className="w-20 h-20 rounded-full flex items-center justify-center mx-auto mb-6"
|
||||||
|
style={{ backgroundColor: 'rgba(239, 68, 68, 0.1)' }}
|
||||||
|
>
|
||||||
|
<XCircle className="w-10 h-10 text-red-600" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="text-h2 mb-4">
|
||||||
|
Payment Failed
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="text-body-lg text-muted mb-8">
|
||||||
|
We encountered an issue processing your payment. Your booking is still reserved
|
||||||
|
for the next 30 minutes. Please try again or contact our support team.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Alert className="mb-8 text-left">
|
||||||
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
<AlertDescription className="text-body">
|
||||||
|
Your booking reference <strong>{bookingConfirmation?.referenceId}</strong> is
|
||||||
|
temporarily held. Please complete payment within 30 minutes to confirm your booking.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
|
<Button
|
||||||
|
size="lg"
|
||||||
|
onClick={handlePayNow}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
className="text-body"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-primary)',
|
||||||
|
color: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CreditCard className="w-5 h-5 mr-2" />
|
||||||
|
{isSubmitting ? 'Processing...' : 'Try Payment Again'}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
onClick={() => navigateTo('/contact')}
|
||||||
|
className="text-body"
|
||||||
|
>
|
||||||
|
Contact Support
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentStep === 'confirmation') {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen" style={{ backgroundColor: '#F7F7FD' }}>
|
||||||
|
<div className="pt-40 pb-16">
|
||||||
|
<div className="mx-auto section-margin-x">
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
{/* Confirmation Header */}
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div
|
||||||
|
className="w-20 h-20 rounded-full flex items-center justify-center mx-auto mb-6"
|
||||||
|
style={{ backgroundColor: 'rgba(34, 197, 94, 0.1)' }}
|
||||||
|
>
|
||||||
|
<CheckCircle className="w-10 h-10 text-green-600" />
|
||||||
|
</div>
|
||||||
|
<h1 className="text-h2 mb-4">
|
||||||
|
Booking Request Submitted
|
||||||
|
</h1>
|
||||||
|
<p className="text-body-lg text-muted">
|
||||||
|
Your facility booking request has been received and is being processed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Confirmation Panel */}
|
||||||
|
{bookingConfirmation && (
|
||||||
|
<Card className="mb-8" style={{ backgroundColor: 'var(--color-bg-white)' }}>
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<CardTitle className="text-subhead">Booking Confirmation</CardTitle>
|
||||||
|
<Badge variant="outline" className="text-body px-4 py-2">
|
||||||
|
Pending Confirmation
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
{/* Reference ID */}
|
||||||
|
<div
|
||||||
|
className="rounded-lg p-4"
|
||||||
|
style={{ backgroundColor: 'rgba(0, 0, 0, 0.03)' }}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<span className="text-body text-muted">Reference ID</span>
|
||||||
|
<div className="text-subhead text-primary">
|
||||||
|
{bookingConfirmation.referenceId}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={copyReferenceId}
|
||||||
|
className="text-body"
|
||||||
|
>
|
||||||
|
<Copy className="w-4 h-4 mr-2" />
|
||||||
|
Copy
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Booking Details */}
|
||||||
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-subhead">Facility Details</h3>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Building className="w-5 h-5 text-muted" />
|
||||||
|
<div>
|
||||||
|
<div className="text-body">{bookingConfirmation.facilityName}</div>
|
||||||
|
<div className="text-small text-muted">Executive Wing</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<CalendarIcon className="w-5 h-5 text-muted" />
|
||||||
|
<div>
|
||||||
|
<div className="text-body">{bookingConfirmation.date}</div>
|
||||||
|
<div className="text-small text-muted">{bookingConfirmation.timeSlot}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Users className="w-5 h-5 text-muted" />
|
||||||
|
<div>
|
||||||
|
<div className="text-body">Up to {facilityData.capacity} people</div>
|
||||||
|
<div className="text-small text-muted">Maximum capacity</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-subhead">Contact Information</h3>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Building className="w-5 h-5 text-muted" />
|
||||||
|
<div>
|
||||||
|
<div className="text-body">{bookingConfirmation.companyName}</div>
|
||||||
|
<div className="text-small text-muted">Company</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<User className="w-5 h-5 text-muted" />
|
||||||
|
<div>
|
||||||
|
<div className="text-body">{bookingConfirmation.contactName}</div>
|
||||||
|
<div className="text-small text-muted">Contact Person</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Mail className="w-5 h-5 text-muted" />
|
||||||
|
<div>
|
||||||
|
<div className="text-body">{bookingConfirmation.email}</div>
|
||||||
|
<div className="text-small text-muted">Email</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
{/* Pricing */}
|
||||||
|
<div className="flex items-center justify-between text-subhead">
|
||||||
|
<span>Total Amount</span>
|
||||||
|
<span className="text-primary">
|
||||||
|
{formatPrice(bookingConfirmation.totalAmount)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
|
<Button
|
||||||
|
size="lg"
|
||||||
|
onClick={handlePayNow}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
className="text-body-lg"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-primary)',
|
||||||
|
color: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CreditCard className="w-5 h-5 mr-2" />
|
||||||
|
{isSubmitting ? 'Processing Payment...' : 'Pay Now'}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
onClick={() => navigateTo('/')}
|
||||||
|
className="text-body-lg"
|
||||||
|
>
|
||||||
|
Continue Browsing
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen" style={{ backgroundColor: '#F7F7FD' }}>
|
||||||
|
{/* Hero Section */}
|
||||||
|
<section className="relative h-[80vh] lg:h-[90vh] overflow-hidden pt-20" style={{ backgroundColor: 'var(--color-bg-white)' }}>
|
||||||
|
<div className="h-full flex items-center">
|
||||||
|
{/* Left Side - Content (60%) */}
|
||||||
|
<div className="relative w-full lg:w-[60%] h-full flex items-center justify-center p-8 lg:p-16 py-12 lg:py-16">
|
||||||
|
<div className="max-w-2xl space-y-8">
|
||||||
|
{/* Price Badge */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Badge
|
||||||
|
className="text-body px-4 py-2"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-primary)',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
₹2,500/hour
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Headline */}
|
||||||
|
<div className="space-y-6">
|
||||||
|
<h1 className="text-h1 text-primary leading-tight">
|
||||||
|
Book Executive
|
||||||
|
<br />
|
||||||
|
<span className="text-primary">Boardroom</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<div className="space-y-6">
|
||||||
|
<p className="text-body-lg text-muted leading-relaxed max-w-xl">
|
||||||
|
Premium boardroom designed for high-level meetings and strategic discussions with
|
||||||
|
state-of-the-art technology.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Facility Details */}
|
||||||
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Users className="w-5 h-5 text-muted" />
|
||||||
|
<span className="text-body">20 people</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<MapPin className="w-5 h-5 text-muted" />
|
||||||
|
<span className="text-body">Executive Wing</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Clock className="w-5 h-5 text-muted" />
|
||||||
|
<span className="text-body">9 AM - 6 PM</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<CheckCircle className="w-5 h-5 text-green-600" />
|
||||||
|
<span className="text-body">Available</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Amenities */}
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Wifi className="w-5 h-5 text-primary flex-shrink-0" />
|
||||||
|
<span className="text-body">High-Speed WiFi</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Coffee className="w-5 h-5 text-primary flex-shrink-0" />
|
||||||
|
<span className="text-body">Refreshment Station</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Car className="w-5 h-5 text-primary flex-shrink-0" />
|
||||||
|
<span className="text-body">Valet Parking</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Utensils className="w-5 h-5 text-primary flex-shrink-0" />
|
||||||
|
<span className="text-body">Premium Catering</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Zap className="w-5 h-5 text-primary flex-shrink-0" />
|
||||||
|
<span className="text-body">4K Video System</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Award className="w-5 h-5 text-primary flex-shrink-0" />
|
||||||
|
<span className="text-body">Premium Seating</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CTA Button */}
|
||||||
|
<div className="pt-4">
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
const bookingSection = document.getElementById('booking-section');
|
||||||
|
if (bookingSection) {
|
||||||
|
bookingSection.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="group shadow-xl"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-primary)',
|
||||||
|
color: 'white',
|
||||||
|
borderRadius: '9999px',
|
||||||
|
padding: '1rem 2rem'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>Book This Boardroom</span>
|
||||||
|
<ArrowRight className="w-5 h-5 ml-2 group-hover:translate-x-1 transition-transform" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Side - Luxury Interior Image (40%) */}
|
||||||
|
<div className="hidden lg:block relative w-[40%] h-full">
|
||||||
|
<div className="absolute inset-0">
|
||||||
|
<ImageWithFallback
|
||||||
|
src="https://images.unsplash.com/photo-1577791464704-3bbd2da56803?w=1080&h=800&fit=crop"
|
||||||
|
alt="Luxury executive boardroom with modern design"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
{/* Subtle overlay for depth */}
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-l from-transparent via-transparent to-white/20"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div id="booking-section" className="py-8 lg:py-12">
|
||||||
|
<div className="mx-auto section-margin-x">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
{/* Back Button */}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => navigateTo('/learning-facility/virtual-tour')}
|
||||||
|
className="mb-6 text-body"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||||
|
Back to Virtual Tour
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Facility Header Card */}
|
||||||
|
<Card className="mb-8" style={{ backgroundColor: 'var(--color-bg-white)' }}>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="grid lg:grid-cols-3 gap-6">
|
||||||
|
<div className="lg:col-span-1">
|
||||||
|
<div className="aspect-video rounded-lg overflow-hidden">
|
||||||
|
<ImageWithFallback
|
||||||
|
src={facilityData.image}
|
||||||
|
alt={facilityData.name}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="lg:col-span-2">
|
||||||
|
<div className="flex items-start justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-h3 mb-2">
|
||||||
|
Book {facilityData.name}
|
||||||
|
</h1>
|
||||||
|
<p className="text-body text-muted mb-4">
|
||||||
|
{facilityData.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Badge variant="secondary" className="text-body px-4 py-2">
|
||||||
|
{formatPrice(facilityData.pricePerHour)}/hour
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Users className="w-5 h-5 text-muted" />
|
||||||
|
<span className="text-body">{facilityData.capacity} people</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<MapPin className="w-5 h-5 text-muted" />
|
||||||
|
<span className="text-body">Executive Wing</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Clock className="w-5 h-5 text-muted" />
|
||||||
|
<span className="text-body">9 AM - 6 PM</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<CheckCircle className="w-5 h-5 text-green-600" />
|
||||||
|
<span className="text-body">Available</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{facilityData.amenities.map((amenity, index) => (
|
||||||
|
<Badge key={index} variant="outline" className="text-small px-3 py-1">
|
||||||
|
<amenity.icon className="w-3 h-3 mr-1" />
|
||||||
|
{amenity.name}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<div className="grid lg:grid-cols-3 gap-8">
|
||||||
|
{/* Booking Form */}
|
||||||
|
<div className="lg:col-span-2">
|
||||||
|
<form onSubmit={handleSubmitBooking} className="space-y-8">
|
||||||
|
{/* Company Information */}
|
||||||
|
<Card style={{ backgroundColor: 'var(--color-bg-white)' }}>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-subhead">Company Information</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="companyName" className="text-body">Company Name *</Label>
|
||||||
|
<Input
|
||||||
|
id="companyName"
|
||||||
|
value={bookingForm.companyName}
|
||||||
|
onChange={(e) => setBookingForm({...bookingForm, companyName: e.target.value})}
|
||||||
|
required
|
||||||
|
className="text-body min-h-[44px]"
|
||||||
|
placeholder="Enter company name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="contactName" className="text-body">Contact Person *</Label>
|
||||||
|
<Input
|
||||||
|
id="contactName"
|
||||||
|
value={bookingForm.contactName}
|
||||||
|
onChange={(e) => setBookingForm({...bookingForm, contactName: e.target.value})}
|
||||||
|
required
|
||||||
|
className="text-body min-h-[44px]"
|
||||||
|
placeholder="Enter contact name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="email" className="text-body">Email Address *</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
value={bookingForm.email}
|
||||||
|
onChange={(e) => setBookingForm({...bookingForm, email: e.target.value})}
|
||||||
|
required
|
||||||
|
className="text-body min-h-[44px]"
|
||||||
|
placeholder="Enter email address"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="phone" className="text-body">Phone Number *</Label>
|
||||||
|
<Input
|
||||||
|
id="phone"
|
||||||
|
value={bookingForm.phone}
|
||||||
|
onChange={(e) => setBookingForm({...bookingForm, phone: e.target.value})}
|
||||||
|
required
|
||||||
|
className="text-body min-h-[44px]"
|
||||||
|
placeholder="Enter phone number"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="role" className="text-body">Your Role *</Label>
|
||||||
|
<Select value={bookingForm.role} onValueChange={(value) => setBookingForm({...bookingForm, role: value})}>
|
||||||
|
<SelectTrigger className="text-body min-h-[44px]">
|
||||||
|
<SelectValue placeholder="Select your role" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="hr-director">HR Director</SelectItem>
|
||||||
|
<SelectItem value="learning-development">L&D Manager</SelectItem>
|
||||||
|
<SelectItem value="executive-assistant">Executive Assistant</SelectItem>
|
||||||
|
<SelectItem value="ceo-founder">CEO/Founder</SelectItem>
|
||||||
|
<SelectItem value="other">Other</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="teamSize" className="text-body">Expected Team Size *</Label>
|
||||||
|
<Select value={bookingForm.teamSize} onValueChange={(value) => setBookingForm({...bookingForm, teamSize: value})}>
|
||||||
|
<SelectTrigger className="text-body min-h-[44px]">
|
||||||
|
<SelectValue placeholder="Select team size" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="1-5">1-5 people</SelectItem>
|
||||||
|
<SelectItem value="6-10">6-10 people</SelectItem>
|
||||||
|
<SelectItem value="11-15">11-15 people</SelectItem>
|
||||||
|
<SelectItem value="16-20">16-20 people</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="additionalRequirements" className="text-body">Additional Requirements</Label>
|
||||||
|
<Textarea
|
||||||
|
id="additionalRequirements"
|
||||||
|
value={bookingForm.additionalRequirements}
|
||||||
|
onChange={(e) => setBookingForm({...bookingForm, additionalRequirements: e.target.value})}
|
||||||
|
placeholder="Catering, special AV setup, accessibility needs..."
|
||||||
|
className="text-body min-h-[100px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Date and Time Selection */}
|
||||||
|
<Card style={{ backgroundColor: 'var(--color-bg-white)' }}>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-subhead">Select Date & Time</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
{/* Calendar Widget */}
|
||||||
|
<div>
|
||||||
|
<Label className="text-body mb-4 block">Select Date *</Label>
|
||||||
|
<div className="border rounded-lg p-4">
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={selectedDate}
|
||||||
|
onSelect={setSelectedDate}
|
||||||
|
disabled={(date) =>
|
||||||
|
date < new Date() ||
|
||||||
|
date.getDay() === 0 ||
|
||||||
|
date.getDay() === 6
|
||||||
|
}
|
||||||
|
className="rounded-md"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="text-small text-muted mt-2">
|
||||||
|
Bookings available Monday to Friday only
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Time Slots */}
|
||||||
|
{selectedDate && (
|
||||||
|
<div>
|
||||||
|
<Label className="text-body mb-4 block">Available Time Slots *</Label>
|
||||||
|
{availableTimeSlots.length > 0 ? (
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||||
|
{availableTimeSlots.map((slot) => (
|
||||||
|
<Button
|
||||||
|
key={slot.time}
|
||||||
|
type="button"
|
||||||
|
variant={selectedTimeSlot === slot.time ? "default" : "outline"}
|
||||||
|
className="text-body justify-between p-4 h-auto"
|
||||||
|
onClick={() => setSelectedTimeSlot(slot.time)}
|
||||||
|
style={{
|
||||||
|
backgroundColor: selectedTimeSlot === slot.time ? 'var(--color-primary)' : 'transparent',
|
||||||
|
color: selectedTimeSlot === slot.time ? 'white' : 'var(--color-black)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>{slot.time}</span>
|
||||||
|
<span className="text-small">{formatPrice(slot.price)}</span>
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Alert>
|
||||||
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
<AlertDescription className="text-body">
|
||||||
|
No time slots available for the selected date. Please choose a different date.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<Card style={{ backgroundColor: 'var(--color-bg-white)' }}>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
size="lg"
|
||||||
|
className="w-full text-body-lg"
|
||||||
|
disabled={!isFormValid() || isSubmitting}
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-primary)',
|
||||||
|
color: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isSubmitting ? (
|
||||||
|
'Processing Booking...'
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
Submit Booking Request
|
||||||
|
<ArrowRight className="w-5 h-5 ml-2" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<p className="text-small text-muted text-center mt-4">
|
||||||
|
By submitting, you agree to our booking terms and conditions
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Booking Summary Sidebar */}
|
||||||
|
<div className="lg:col-span-1">
|
||||||
|
<Card className="sticky top-24" style={{ backgroundColor: 'var(--color-bg-white)' }}>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-subhead">Booking Summary</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex justify-between text-body">
|
||||||
|
<span>Facility:</span>
|
||||||
|
<span className="text-subhead">{facilityData.name}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedDate && (
|
||||||
|
<div className="flex justify-between text-body">
|
||||||
|
<span>Date:</span>
|
||||||
|
<span className="text-subhead">
|
||||||
|
{selectedDate.toLocaleDateString('en-IN', {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric'
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedTimeSlot && (
|
||||||
|
<div className="flex justify-between text-body">
|
||||||
|
<span>Time:</span>
|
||||||
|
<span className="text-subhead">{selectedTimeSlot}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex justify-between text-body">
|
||||||
|
<span>Duration:</span>
|
||||||
|
<span className="text-subhead">1 hour</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{bookingForm.teamSize && (
|
||||||
|
<div className="flex justify-between text-body">
|
||||||
|
<span>Team Size:</span>
|
||||||
|
<span className="text-subhead">{bookingForm.teamSize}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex justify-between text-body">
|
||||||
|
<span>Base Rate:</span>
|
||||||
|
<span>{formatPrice(facilityData.pricePerHour)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-body">
|
||||||
|
<span>Service Charge:</span>
|
||||||
|
<span>Included</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="flex justify-between text-subhead">
|
||||||
|
<span>Total:</span>
|
||||||
|
<span className="text-primary">{formatPrice(facilityData.pricePerHour)}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-small text-muted">
|
||||||
|
<p>• Payment due upon confirmation</p>
|
||||||
|
<p>• Free cancellation up to 24 hours</p>
|
||||||
|
<p>• Technical support included</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
201
.gitea/workflows/src/components/BookingModal.tsx
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { Calendar } from "lucide-react";
|
||||||
|
import { Button } from "./ui/button";
|
||||||
|
import { Input } from "./ui/input";
|
||||||
|
import { Label } from "./ui/label";
|
||||||
|
import { Textarea } from "./ui/textarea";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
|
||||||
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "./ui/dialog";
|
||||||
|
|
||||||
|
interface BookingFormData {
|
||||||
|
companyName: string;
|
||||||
|
contactName: string;
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
role: string;
|
||||||
|
teamSize: string;
|
||||||
|
facilityZone: string;
|
||||||
|
additionalRequirements: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BookingModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
initialFacilityZone?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BookingModal({ isOpen, onClose, initialFacilityZone = "" }: BookingModalProps) {
|
||||||
|
const [bookingForm, setBookingForm] = useState<BookingFormData>({
|
||||||
|
companyName: '',
|
||||||
|
contactName: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
role: '',
|
||||||
|
teamSize: '',
|
||||||
|
facilityZone: initialFacilityZone,
|
||||||
|
additionalRequirements: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleBookingSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log('Booking form submitted:', bookingForm);
|
||||||
|
// Here you would typically send the form data to your backend
|
||||||
|
alert('Booking request submitted successfully! We will contact you within 24 hours.');
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateFormField = (field: keyof BookingFormData, value: string) => {
|
||||||
|
setBookingForm(prev => ({ ...prev, [field]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onOpenChange={(open: boolean) => {
|
||||||
|
if (!open) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="text-h3 mb-2">Book Our Learning Facility</DialogTitle>
|
||||||
|
<DialogDescription className="text-body text-muted">
|
||||||
|
Submit your booking request and we'll get back to you within 24 hours to confirm availability and discuss your requirements.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<form onSubmit={handleBookingSubmit} className="space-y-6 mt-6">
|
||||||
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="companyName" className="text-body font-medium">Company/Organization Name *</Label>
|
||||||
|
<Input
|
||||||
|
id="companyName"
|
||||||
|
value={bookingForm.companyName}
|
||||||
|
onChange={(e) => updateFormField('companyName', e.target.value)}
|
||||||
|
placeholder="Enter company name"
|
||||||
|
required
|
||||||
|
className="text-body"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="contactName" className="text-body font-medium">Contact Person *</Label>
|
||||||
|
<Input
|
||||||
|
id="contactName"
|
||||||
|
value={bookingForm.contactName}
|
||||||
|
onChange={(e) => updateFormField('contactName', e.target.value)}
|
||||||
|
placeholder="Enter your full name"
|
||||||
|
required
|
||||||
|
className="text-body"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email" className="text-body font-medium">Email Address *</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
value={bookingForm.email}
|
||||||
|
onChange={(e) => updateFormField('email', e.target.value)}
|
||||||
|
placeholder="your.email@company.com"
|
||||||
|
required
|
||||||
|
className="text-body"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="phone" className="text-body font-medium">Phone Number</Label>
|
||||||
|
<Input
|
||||||
|
id="phone"
|
||||||
|
value={bookingForm.phone}
|
||||||
|
onChange={(e) => updateFormField('phone', e.target.value)}
|
||||||
|
placeholder="+1 (555) 123-4567"
|
||||||
|
className="text-body"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="role" className="text-body font-medium">Your Role/Position</Label>
|
||||||
|
<Input
|
||||||
|
id="role"
|
||||||
|
value={bookingForm.role}
|
||||||
|
onChange={(e) => updateFormField('role', e.target.value)}
|
||||||
|
placeholder="e.g., Training Manager, HR Director"
|
||||||
|
className="text-body"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="teamSize" className="text-body font-medium">Expected Group Size *</Label>
|
||||||
|
<Select value={bookingForm.teamSize} onValueChange={(value:string) => updateFormField('teamSize', value)}>
|
||||||
|
<SelectTrigger className="text-body">
|
||||||
|
<SelectValue placeholder="Select group size" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="1-10">1-10 participants</SelectItem>
|
||||||
|
<SelectItem value="11-25">11-25 participants</SelectItem>
|
||||||
|
<SelectItem value="26-50">26-50 participants</SelectItem>
|
||||||
|
<SelectItem value="51-80">51-80 participants</SelectItem>
|
||||||
|
<SelectItem value="80+">80+ participants</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="facilityZone" className="text-body font-medium">Preferred Learning Zone</Label>
|
||||||
|
<Select value={bookingForm.facilityZone} onValueChange={(value:string) => updateFormField('facilityZone', value)}>
|
||||||
|
<SelectTrigger className="text-body">
|
||||||
|
<SelectValue placeholder="Select preferred zone (optional)" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="amphitheater">Training Amphitheater (80-120 people)</SelectItem>
|
||||||
|
<SelectItem value="collaboration">Collaboration Suites (4-8 people)</SelectItem>
|
||||||
|
<SelectItem value="outdoor">Outdoor Learning Pavilion (20-40 people)</SelectItem>
|
||||||
|
<SelectItem value="multiple">Multiple zones needed</SelectItem>
|
||||||
|
<SelectItem value="flexible">Flexible - advise best option</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="requirements" className="text-body font-medium">Additional Requirements</Label>
|
||||||
|
<Textarea
|
||||||
|
id="requirements"
|
||||||
|
value={bookingForm.additionalRequirements}
|
||||||
|
onChange={(e) => updateFormField('additionalRequirements', e.target.value)}
|
||||||
|
placeholder="Please share any specific requirements, preferred dates, catering needs, accommodation requirements, etc."
|
||||||
|
rows={4}
|
||||||
|
className="text-body resize-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col sm:flex-row gap-3 justify-end pt-4">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
onClick={onClose}
|
||||||
|
className="text-body"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="text-body px-8"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-primary)',
|
||||||
|
color: 'white',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Calendar className="w-4 h-4 mr-2" />
|
||||||
|
Submit Booking Request
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
101
.gitea/workflows/src/components/CTABannerSection.tsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import { ArrowRight } from "lucide-react";
|
||||||
|
import { ImageWithFallback } from "./figma/ImageWithFallback";
|
||||||
|
import { BrandedTag } from "./about/BrandedTag";
|
||||||
|
import { PrimaryCTAButton } from "./PrimaryCTAButton";
|
||||||
|
import { navigateTo } from "./Router";
|
||||||
|
|
||||||
|
interface CTABannerSectionProps {
|
||||||
|
ctaSection?: {
|
||||||
|
id: string;
|
||||||
|
background_image_url: string;
|
||||||
|
text: string;
|
||||||
|
cta_text: string;
|
||||||
|
cta_destination: string;
|
||||||
|
description: string;
|
||||||
|
landing_page_type: string;
|
||||||
|
service_type: string | null;
|
||||||
|
};
|
||||||
|
isLoading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CTABannerSection({ ctaSection, isLoading }: CTABannerSectionProps) {
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<section className="relative h-[700px] overflow-hidden bg-gray-100 animate-pulse">
|
||||||
|
<div className="absolute inset-0 bg-gray-200" />
|
||||||
|
<div className="relative h-full flex items-center justify-end section-margin-x">
|
||||||
|
<div className="bg-gray-300 rounded-lg p-16 max-w-2xl w-full h-96" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no CTA section data is available, don't render anything
|
||||||
|
if (!ctaSection) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="relative h-[700px] overflow-hidden">
|
||||||
|
{/* Background Image */}
|
||||||
|
<div className="absolute inset-0">
|
||||||
|
<ImageWithFallback
|
||||||
|
src={ctaSection.background_image_url || "https://images.unsplash.com/photo-1552664730-d307ca884978?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2940&q=80"}
|
||||||
|
alt="Background image for call to action section"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Subtle dark overlay for overall image */}
|
||||||
|
<div className="absolute inset-0 bg-black/30" />
|
||||||
|
|
||||||
|
{/* Gradient overlay for better text readability */}
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-black/20 via-transparent to-black/60" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content Container */}
|
||||||
|
<div className="relative h-full flex items-center justify-end section-margin-x">
|
||||||
|
{/* CTA Content Block */}
|
||||||
|
<div
|
||||||
|
className="bg-opacity-95 backdrop-blur-sm rounded-lg p-16 max-w-2xl"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-brand-primary)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Branded Tag */}
|
||||||
|
<BrandedTag text="Next Steps" variant="white" />
|
||||||
|
|
||||||
|
{/* Main Headline */}
|
||||||
|
<h2
|
||||||
|
className="text-h2-white mb-4"
|
||||||
|
>
|
||||||
|
{ctaSection.text || "Ready to transform your leadership?"}
|
||||||
|
<span
|
||||||
|
className="italic"
|
||||||
|
style={{ color: 'var(--color-brand-accent)' }}
|
||||||
|
>
|
||||||
|
{" "}Get in touch{" "}
|
||||||
|
</span>
|
||||||
|
to start your development journey now.
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
{ctaSection.description && (
|
||||||
|
<p
|
||||||
|
className="text-body-white mb-6 opacity-90"
|
||||||
|
>
|
||||||
|
{ctaSection.description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* CTA Button */}
|
||||||
|
<PrimaryCTAButton
|
||||||
|
text={ctaSection.cta_text || "Schedule a Consultation"}
|
||||||
|
onClick={() => navigateTo(ctaSection.cta_destination || '/contact?topic=consulting')}
|
||||||
|
ariaLabel="Schedule a consultation with our leadership experts"
|
||||||
|
className="cta-banner-yellow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
213
.gitea/workflows/src/components/CTAPopupModal.tsx
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Eye, Calendar, ArrowRight, Building } from 'lucide-react';
|
||||||
|
import { navigateTo } from './Router';
|
||||||
|
|
||||||
|
interface CTAPopupModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CTAPopupModal({ isOpen, onClose }: CTAPopupModalProps) {
|
||||||
|
console.log('CTAPopupModal render - isOpen:', isOpen); // Debug log
|
||||||
|
|
||||||
|
const handleVirtualTour = () => {
|
||||||
|
navigateTo('/services/learning-facility');
|
||||||
|
onClose();
|
||||||
|
// Add a small delay to ensure navigation completes before triggering tour
|
||||||
|
setTimeout(() => {
|
||||||
|
// Trigger virtual tour start if on facility page
|
||||||
|
const startTourBtn = document.querySelector('[data-tour-trigger]') as HTMLButtonElement;
|
||||||
|
if (startTourBtn) {
|
||||||
|
startTourBtn.click();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBooking = () => {
|
||||||
|
console.log('Booking button clicked in modal'); // Debug log
|
||||||
|
|
||||||
|
// Use URL parameter approach for more reliable booking modal triggering
|
||||||
|
navigateTo('/services/learning-facility?autoBooking=true');
|
||||||
|
onClose();
|
||||||
|
|
||||||
|
// Also try the element-based approach as backup
|
||||||
|
const attemptBookingTrigger = (attempt = 1, maxAttempts = 5) => {
|
||||||
|
console.log(`Booking trigger attempt #${attempt}`); // Debug log
|
||||||
|
|
||||||
|
// Look for the booking trigger element
|
||||||
|
const bookingBtn = document.querySelector('[data-booking-trigger]') as HTMLButtonElement;
|
||||||
|
console.log('Found booking trigger:', bookingBtn); // Debug log
|
||||||
|
|
||||||
|
if (bookingBtn) {
|
||||||
|
console.log('Clicking booking trigger...'); // Debug log
|
||||||
|
bookingBtn.click();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not found and we haven't reached max attempts, try again
|
||||||
|
if (attempt < maxAttempts) {
|
||||||
|
setTimeout(() => attemptBookingTrigger(attempt + 1, maxAttempts), 200);
|
||||||
|
} else {
|
||||||
|
console.log('Element-based approach failed, relying on URL parameter approach'); // Debug log
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start the booking trigger attempts after initial navigation delay
|
||||||
|
setTimeout(() => attemptBookingTrigger(), 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContactUs = () => {
|
||||||
|
navigateTo('/contact');
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simple modal implementation that bypasses Dialog component issues
|
||||||
|
if (!isOpen) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
background: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
zIndex: 9999
|
||||||
|
}}
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: 'white',
|
||||||
|
padding: '2rem',
|
||||||
|
borderRadius: '8px',
|
||||||
|
maxWidth: '500px',
|
||||||
|
width: '90%',
|
||||||
|
maxHeight: '80vh',
|
||||||
|
overflow: 'auto'
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="text-center space-y-4 mb-6">
|
||||||
|
<div
|
||||||
|
className="w-20 h-20 rounded-2xl flex items-center justify-center mx-auto"
|
||||||
|
style={{ backgroundColor: 'rgba(4, 4, 91, 0.1)' }}
|
||||||
|
>
|
||||||
|
<Building className="w-10 h-10 text-primary" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-h3">
|
||||||
|
Explore Our Learning Facility
|
||||||
|
</h2>
|
||||||
|
<p className="text-body text-muted">
|
||||||
|
Discover our world-class learning environment. Take a virtual tour to see our facilities or book your space for an upcoming event.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="py-6 space-y-4">
|
||||||
|
{/* Test Message */}
|
||||||
|
<div className="text-center p-4 bg-green-100 rounded-lg">
|
||||||
|
<p className="text-body text-green-800">
|
||||||
|
🎉 Simple Modal is working! This popup opened successfully.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Virtual Tour Option */}
|
||||||
|
<div className="rounded-lg border p-6 space-y-4 hover:shadow-md transition-shadow">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div
|
||||||
|
className="w-12 h-12 rounded-lg flex items-center justify-center"
|
||||||
|
style={{ backgroundColor: 'rgba(4, 4, 91, 0.1)' }}
|
||||||
|
>
|
||||||
|
<Eye className="w-6 h-6 text-primary" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-h4 mb-1">Virtual Tour</h4>
|
||||||
|
<p className="text-body text-muted">
|
||||||
|
Explore our four distinct learning environments
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
onClick={handleVirtualTour}
|
||||||
|
className="w-full"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-primary)',
|
||||||
|
color: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Eye className="w-5 h-5 mr-2" />
|
||||||
|
Start Virtual Tour
|
||||||
|
<ArrowRight className="w-5 h-5 ml-2" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Booking Option */}
|
||||||
|
<div className="rounded-lg border p-6 space-y-4 hover:shadow-md transition-shadow">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div
|
||||||
|
className="w-12 h-12 rounded-lg flex items-center justify-center"
|
||||||
|
style={{ backgroundColor: 'rgba(248, 195, 1, 0.1)' }}
|
||||||
|
>
|
||||||
|
<Calendar className="w-6 h-6" style={{ color: '#F8C301' }} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-h4 mb-1">Book Facility</h4>
|
||||||
|
<p className="text-body text-muted">
|
||||||
|
Reserve our learning spaces for your next event
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
onClick={handleBooking}
|
||||||
|
variant="outline"
|
||||||
|
className="w-full"
|
||||||
|
style={{
|
||||||
|
borderColor: 'var(--color-primary)',
|
||||||
|
color: 'var(--color-primary)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Calendar className="w-5 h-5 mr-2" />
|
||||||
|
Book Now
|
||||||
|
<ArrowRight className="w-5 h-5 ml-2" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contact Us Option */}
|
||||||
|
<div className="pt-4 border-t">
|
||||||
|
<p className="text-center text-small text-muted mb-3">
|
||||||
|
Need more information or have specific requirements?
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
onClick={handleContactUs}
|
||||||
|
variant="ghost"
|
||||||
|
className="w-full text-body"
|
||||||
|
style={{ color: 'var(--color-primary)' }}
|
||||||
|
>
|
||||||
|
Contact Us
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Close Button */}
|
||||||
|
<div className="pt-4">
|
||||||
|
<Button
|
||||||
|
onClick={onClose}
|
||||||
|
variant="outline"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
493
.gitea/workflows/src/components/Cart.tsx
Normal file
@@ -0,0 +1,493 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Card, CardContent } from './ui/card';
|
||||||
|
import { Badge } from './ui/badge';
|
||||||
|
import { Separator } from './ui/separator';
|
||||||
|
import {
|
||||||
|
ShoppingBag,
|
||||||
|
Trash2,
|
||||||
|
Plus,
|
||||||
|
ArrowRight,
|
||||||
|
Shield,
|
||||||
|
CheckCircle,
|
||||||
|
Home,
|
||||||
|
ChevronRight
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { motion } from 'motion/react';
|
||||||
|
import { navigateTo } from './Router';
|
||||||
|
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||||
|
import { useCart } from './CartContext';
|
||||||
|
|
||||||
|
export function Cart() {
|
||||||
|
const { cartItems, removeFromCart, getCartTotal } = useCart();
|
||||||
|
|
||||||
|
// Calculate pricing
|
||||||
|
const subtotal = getCartTotal();
|
||||||
|
const gstRate = 0.18; // 18% GST
|
||||||
|
const gstAmount = subtotal * gstRate;
|
||||||
|
const total = subtotal + gstAmount;
|
||||||
|
|
||||||
|
const handleProceedToPayment = () => {
|
||||||
|
// Navigate to checkout/payment page
|
||||||
|
navigateTo('/checkout');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddMoreProgrammes = () => {
|
||||||
|
navigateTo('/learning-online');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
{/* Breadcrumb Navigation */}
|
||||||
|
<div className="section-margin-x py-6 border-b border-gray-200">
|
||||||
|
<div className="flex items-center gap-2 text-sm">
|
||||||
|
<button
|
||||||
|
onClick={() => navigateTo('/')}
|
||||||
|
className="flex items-center gap-1 text-gray-600 hover:text-gray-900 transition-colors"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Home className="w-4 h-4" />
|
||||||
|
Home
|
||||||
|
</button>
|
||||||
|
<ChevronRight className="w-4 h-4 text-gray-400" />
|
||||||
|
<span
|
||||||
|
className="text-gray-900 font-medium"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cart
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Cart Content */}
|
||||||
|
<div className="section-margin-x py-8">
|
||||||
|
{/* Page Header */}
|
||||||
|
<div className="flex items-center gap-3 mb-8">
|
||||||
|
<div className="w-10 h-10 rounded-lg bg-gray-100 flex items-center justify-center">
|
||||||
|
<ShoppingBag className="w-5 h-5 text-gray-700" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h2)',
|
||||||
|
fontWeight: 'var(--font-weight-h2)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: 'var(--line-height-h2)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Shopping Cart
|
||||||
|
</h1>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
marginTop: '0.25rem'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Review your selected programmes before proceeding to payment
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{cartItems.length === 0 ? (
|
||||||
|
/* Empty Cart State */
|
||||||
|
<div className="text-center py-16">
|
||||||
|
<div className="w-24 h-24 mx-auto mb-6 rounded-full bg-gray-100 flex items-center justify-center">
|
||||||
|
<ShoppingBag className="w-10 h-10 text-gray-400" />
|
||||||
|
</div>
|
||||||
|
<h2
|
||||||
|
className="mb-4"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h3)',
|
||||||
|
fontWeight: 'var(--font-weight-h3)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Your cart is empty
|
||||||
|
</h2>
|
||||||
|
<p
|
||||||
|
className="mb-8 max-w-md mx-auto"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: 'var(--line-height-body)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Browse our comprehensive leadership development programmes and add them to your cart.
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
onClick={handleAddMoreProgrammes}
|
||||||
|
className="inline-flex items-center gap-2 h-12 px-6 rounded-lg font-medium"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-primary)',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontWeight: '500'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Browse Programmes
|
||||||
|
<ArrowRight className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
/* Cart with Items */
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
|
|
||||||
|
{/* Left Column - Cart Items */}
|
||||||
|
<div className="lg:col-span-2">
|
||||||
|
{/* Cart Items Header */}
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<h2
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h4)',
|
||||||
|
fontWeight: 'var(--font-weight-h4)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cart Items ({cartItems.length})
|
||||||
|
</h2>
|
||||||
|
<Badge
|
||||||
|
className="px-3 py-1 rounded-full"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'rgba(248, 195, 1, 0.15)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cartItems.length} programme{cartItems.length !== 1 ? 's' : ''}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Cart Items List */}
|
||||||
|
<div className="space-y-6">
|
||||||
|
{cartItems.map((item, index) => (
|
||||||
|
<motion.div
|
||||||
|
key={item.id}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: index * 0.1 }}
|
||||||
|
>
|
||||||
|
<Card className="overflow-hidden hover:shadow-md transition-all duration-300">
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="flex items-start gap-6">
|
||||||
|
|
||||||
|
{/* Programme Thumbnail */}
|
||||||
|
<div className="w-24 h-16 rounded-lg overflow-hidden flex-shrink-0 bg-gray-100">
|
||||||
|
<ImageWithFallback
|
||||||
|
src={item.thumbnail}
|
||||||
|
alt={item.title}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Programme Details */}
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
|
||||||
|
{/* Programme Title */}
|
||||||
|
<h3
|
||||||
|
className="mb-3 line-clamp-2"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h4)',
|
||||||
|
fontWeight: 'var(--font-weight-h4)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: 'var(--line-height-h4)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{/* Course Details Row */}
|
||||||
|
<div className="flex flex-wrap items-center gap-4 mb-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span
|
||||||
|
className="text-xs px-3 py-1 bg-gray-100 rounded-full"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Course ID: {item.category.toUpperCase().slice(0, 3)}-{item.id}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="text-xs px-3 py-1 rounded-full"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-primary)',
|
||||||
|
backgroundColor: 'rgba(4, 4, 91, 0.1)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.level}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="text-xs px-3 py-1 bg-blue-50 text-blue-700 rounded-full"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Advanced
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Pricing Row */}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<span
|
||||||
|
className="font-bold"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h4)',
|
||||||
|
color: 'var(--color-primary)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.price}
|
||||||
|
</span>
|
||||||
|
{item.originalPrice && (
|
||||||
|
<span
|
||||||
|
className="line-through"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.originalPrice}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Remove Button */}
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => removeFromCart(item.id)}
|
||||||
|
className="text-red-500 hover:text-red-700 hover:bg-red-50 p-2"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Add More Programmes Button */}
|
||||||
|
<div className="mt-8 pt-6 border-t border-gray-200">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleAddMoreProgrammes}
|
||||||
|
className="flex items-center gap-2 h-12 px-6 rounded-lg border-2 border-dashed border-gray-300 hover:border-gray-400 transition-all duration-300 w-full"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontWeight: '500'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Plus className="w-5 h-5" />
|
||||||
|
Add More Programmes
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Column - Order Summary */}
|
||||||
|
<div className="lg:col-span-1">
|
||||||
|
<Card className="sticky top-4">
|
||||||
|
<CardContent className="p-6">
|
||||||
|
|
||||||
|
{/* Order Summary Header */}
|
||||||
|
<h3
|
||||||
|
className="mb-6"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h4)',
|
||||||
|
fontWeight: 'var(--font-weight-h4)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Order Summary
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{/* Pricing Breakdown */}
|
||||||
|
<div className="space-y-4 mb-6">
|
||||||
|
|
||||||
|
{/* Subtotal */}
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Subtotal
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontWeight: '500'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
₹{subtotal.toLocaleString('en-IN')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* GST */}
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
GST (18%)
|
||||||
|
</span>
|
||||||
|
<div className="w-4 h-4 rounded-full bg-gray-200 flex items-center justify-center cursor-help">
|
||||||
|
<span className="text-xs text-gray-600">i</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontWeight: '500'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
₹{gstAmount.toLocaleString('en-IN')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
{/* Total */}
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h4)',
|
||||||
|
fontWeight: 'var(--font-weight-h4)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Total
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h4)',
|
||||||
|
fontWeight: 'var(--font-weight-h4)',
|
||||||
|
color: 'var(--color-primary)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
₹{total.toLocaleString('en-IN')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Proceed to Payment Button */}
|
||||||
|
<Button
|
||||||
|
onClick={handleProceedToPayment}
|
||||||
|
className="w-full flex items-center justify-center gap-2 h-14 rounded-lg font-semibold mb-6"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-primary)',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 'var(--font-body-lg)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Proceed to Payment
|
||||||
|
<ArrowRight className="w-5 h-5" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Security Features */}
|
||||||
|
<div className="space-y-3 pt-4 border-t border-gray-200">
|
||||||
|
<div className="flex items-center gap-3 text-sm">
|
||||||
|
<CheckCircle className="w-4 h-4 text-green-600 flex-shrink-0" />
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Secure payment processing
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 text-sm">
|
||||||
|
<CheckCircle className="w-4 h-4 text-green-600 flex-shrink-0" />
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Instant course access
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 text-sm">
|
||||||
|
<CheckCircle className="w-4 h-4 text-green-600 flex-shrink-0" />
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Certificate upon completion
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Security Note */}
|
||||||
|
<div className="flex items-start gap-2 mt-6 p-4 bg-gray-50 rounded-lg">
|
||||||
|
<Shield className="w-4 h-4 text-gray-600 mt-0.5 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: 'var(--line-height-small)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<strong>Need help with payment?</strong><br />
|
||||||
|
Our AI assistant can guide you through the checkout process and answer questions about course enrollment. Click the chat icon to get started.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
99
.gitea/workflows/src/components/CartContext.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||||
|
import { CartItem } from './CartPopup';
|
||||||
|
|
||||||
|
interface CartContextType {
|
||||||
|
cartItems: CartItem[];
|
||||||
|
cartCount: number;
|
||||||
|
addToCart: (item: CartItem) => void;
|
||||||
|
removeFromCart: (itemId: string) => void;
|
||||||
|
clearCart: () => void;
|
||||||
|
getCartTotal: () => number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CartContext = createContext<CartContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export function CartProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const [cartItems, setCartItems] = useState<CartItem[]>([]);
|
||||||
|
|
||||||
|
// Load cart from localStorage on mount
|
||||||
|
useEffect(() => {
|
||||||
|
const savedCart = localStorage.getItem('klc-cart');
|
||||||
|
if (savedCart) {
|
||||||
|
try {
|
||||||
|
const parsedCart = JSON.parse(savedCart);
|
||||||
|
if (Array.isArray(parsedCart)) {
|
||||||
|
setCartItems(parsedCart);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading cart from localStorage:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Save cart to localStorage whenever it changes
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem('klc-cart', JSON.stringify(cartItems));
|
||||||
|
}, [cartItems]);
|
||||||
|
|
||||||
|
// Parse price helper function
|
||||||
|
const parsePrice = (priceStr: string) => {
|
||||||
|
const cleanPrice = priceStr.replace(/[₹$,]/g, '');
|
||||||
|
const numValue = parseFloat(cleanPrice);
|
||||||
|
return isNaN(numValue) ? 0 : numValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addToCart = (item: CartItem) => {
|
||||||
|
setCartItems(prevItems => {
|
||||||
|
// Check if item already exists
|
||||||
|
const existingItem = prevItems.find(cartItem => cartItem.id === item.id);
|
||||||
|
|
||||||
|
if (existingItem) {
|
||||||
|
// Item already in cart, don't add duplicate
|
||||||
|
return prevItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new item
|
||||||
|
return [...prevItems, item];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeFromCart = (itemId: string) => {
|
||||||
|
setCartItems(prevItems => prevItems.filter(item => item.id !== itemId));
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearCart = () => {
|
||||||
|
setCartItems([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCartTotal = () => {
|
||||||
|
return cartItems.reduce((total, item) => {
|
||||||
|
const price = parsePrice(item.price);
|
||||||
|
return total + price;
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const cartCount = cartItems.length;
|
||||||
|
|
||||||
|
const value: CartContextType = {
|
||||||
|
cartItems,
|
||||||
|
cartCount,
|
||||||
|
addToCart,
|
||||||
|
removeFromCart,
|
||||||
|
clearCart,
|
||||||
|
getCartTotal
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CartContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</CartContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCart() {
|
||||||
|
const context = useContext(CartContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useCart must be used within a CartProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
575
.gitea/workflows/src/components/CartPopup.tsx
Normal file
@@ -0,0 +1,575 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { X, CheckCircle, ShoppingCart, Trash2, Shield, ArrowRight } from 'lucide-react';
|
||||||
|
import { motion, AnimatePresence } from 'motion/react';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||||
|
import { navigateTo } from './Router';
|
||||||
|
import { useCart } from './CartContext';
|
||||||
|
|
||||||
|
export interface CartItem {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
thumbnail: string;
|
||||||
|
price: string;
|
||||||
|
originalPrice?: string;
|
||||||
|
category: string;
|
||||||
|
level: string;
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CartPopupProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
// cartItems: CartItem[]; // Legacy prop - no longer used but kept for backward compatibility
|
||||||
|
// onRemoveItem: (itemId: string) => void; // Legacy prop - no longer used but kept for backward compatibility
|
||||||
|
recentlyAddedItem?: CartItem | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CartPopup({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
// cartItems: legacyCartItems, // Renamed to avoid confusion
|
||||||
|
// onRemoveItem: legacyOnRemoveItem, // Renamed to avoid confusion
|
||||||
|
recentlyAddedItem
|
||||||
|
}: CartPopupProps) {
|
||||||
|
const [showSuccess, setShowSuccess] = useState(false);
|
||||||
|
|
||||||
|
// Use global cart context instead of props
|
||||||
|
const { cartItems, removeFromCart, getCartTotal } = useCart();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (recentlyAddedItem && isOpen) {
|
||||||
|
setShowSuccess(true);
|
||||||
|
const timer = setTimeout(() => setShowSuccess(false), 3000);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}, [recentlyAddedItem, isOpen]);
|
||||||
|
|
||||||
|
// Prevent background scrolling when popup is open
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
// Save current scroll position
|
||||||
|
const scrollY = window.scrollY;
|
||||||
|
|
||||||
|
// Prevent scrolling
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
document.body.style.position = 'fixed';
|
||||||
|
document.body.style.top = `-${scrollY}px`;
|
||||||
|
document.body.style.width = '100%';
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// Restore scrolling
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
document.body.style.position = '';
|
||||||
|
document.body.style.top = '';
|
||||||
|
document.body.style.width = '';
|
||||||
|
|
||||||
|
// Restore scroll position
|
||||||
|
window.scrollTo(0, scrollY);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
// Fixed price parsing function - handles both ₹ and $ formats
|
||||||
|
const parsePrice = (priceStr: string) => {
|
||||||
|
// Remove currency symbols and commas, then parse
|
||||||
|
const cleanPrice = priceStr.replace(/[₹$,]/g, '');
|
||||||
|
const numValue = parseFloat(cleanPrice);
|
||||||
|
return isNaN(numValue) ? 0 : numValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert to rupees - improved handling
|
||||||
|
const convertToRupees = (priceStr: string) => {
|
||||||
|
const numValue = parsePrice(priceStr);
|
||||||
|
|
||||||
|
// If already in rupees, return as is
|
||||||
|
if (priceStr.includes('₹')) {
|
||||||
|
return `₹${numValue.toLocaleString('en-IN')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert from USD to INR
|
||||||
|
return `₹${(numValue * 83).toLocaleString('en-IN')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use global cart total instead of calculating locally
|
||||||
|
const subtotal = getCartTotal();
|
||||||
|
const estimatedTotal = subtotal; // No taxes for now
|
||||||
|
|
||||||
|
const handleProceedToCheckout = () => {
|
||||||
|
// Navigate to checkout page (placeholder)
|
||||||
|
navigateTo('/checkout');
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContinueShopping = () => {
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle backdrop click - close popup only if clicking on backdrop
|
||||||
|
const handleBackdropClick = (e: React.MouseEvent) => {
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence>
|
||||||
|
{isOpen && (
|
||||||
|
<>
|
||||||
|
{/* Backdrop with higher z-index than navbar */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
className="fixed inset-0 bg-black/50 backdrop-blur-sm"
|
||||||
|
style={{ zIndex: 10001 }} // Higher than navbar (navbar typically uses z-50 which is 50)
|
||||||
|
onClick={handleBackdropClick}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Cart Popup with highest z-index */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ x: '100%' }}
|
||||||
|
animate={{ x: 0 }}
|
||||||
|
exit={{ x: '100%' }}
|
||||||
|
transition={{ type: 'spring', damping: 30, stiffness: 300 }}
|
||||||
|
className="fixed top-0 right-0 h-full w-full max-w-lg bg-white shadow-2xl overflow-hidden flex flex-col"
|
||||||
|
style={{
|
||||||
|
zIndex: 10002, // Highest z-index to be above everything including navbar
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between p-6 border-b border-gray-200 bg-white">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center">
|
||||||
|
<CheckCircle className="w-5 h-5 text-green-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2
|
||||||
|
className="font-semibold"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h4)',
|
||||||
|
fontWeight: 'var(--font-weight-h4)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Added to Cart
|
||||||
|
</h2>
|
||||||
|
<p
|
||||||
|
className="text-muted"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cartItems.length} programme{cartItems.length !== 1 ? 's' : ''} in your cart
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={onClose}
|
||||||
|
className="p-2 hover:bg-gray-100 rounded-full"
|
||||||
|
>
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Success Message */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{showSuccess && recentlyAddedItem && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: -20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -20 }}
|
||||||
|
className="mx-6 mt-4 p-4 bg-green-50 border border-green-200 rounded-lg"
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<CheckCircle className="w-5 h-5 text-green-600 mt-0.5 flex-shrink-0" />
|
||||||
|
<div className="flex-1">
|
||||||
|
<p
|
||||||
|
className="font-medium text-green-800"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
"{recentlyAddedItem.title}" added successfully!
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="text-green-600 mt-1"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
You can continue browsing or proceed to checkout.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
{/* Cart Content - Scrollable */}
|
||||||
|
<div className="flex-1 overflow-y-auto">
|
||||||
|
{/* Cart Items */}
|
||||||
|
<div className="p-6">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<h3
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h4)',
|
||||||
|
fontWeight: 'var(--font-weight-h4)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Your Cart ({cartItems.length})
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontWeight: '500'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Total: ₹{subtotal.toLocaleString('en-IN')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Cart Items List */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
{cartItems.map((item, index) => (
|
||||||
|
<motion.div
|
||||||
|
key={item.id}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: index * 0.1 }}
|
||||||
|
className="flex items-start gap-4 p-4 border border-gray-200 rounded-lg hover:border-gray-300 transition-colors"
|
||||||
|
>
|
||||||
|
{/* Course Thumbnail */}
|
||||||
|
<div className="w-20 h-14 rounded-lg overflow-hidden flex-shrink-0">
|
||||||
|
<ImageWithFallback
|
||||||
|
src={item.thumbnail}
|
||||||
|
alt={item.title}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Course Details */}
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<h4
|
||||||
|
className="font-medium line-clamp-2 mb-1"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontWeight: '500',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</h4>
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<span
|
||||||
|
className="text-xs px-2 py-1 bg-gray-100 rounded-md"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Programme
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="text-xs px-2 py-1 rounded-md"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-primary)',
|
||||||
|
backgroundColor: 'rgba(4, 4, 91, 0.1)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.level}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span
|
||||||
|
className="font-semibold"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-primary)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{convertToRupees(item.price)}
|
||||||
|
</span>
|
||||||
|
{item.originalPrice && (
|
||||||
|
<span
|
||||||
|
className="line-through"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{convertToRupees(item.originalPrice)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => removeFromCart(item.id)}
|
||||||
|
className="p-2 text-red-500 hover:text-red-700 hover:bg-red-50"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Cart Summary */}
|
||||||
|
{cartItems.length > 0 && (
|
||||||
|
<div className="mt-6 p-4 bg-gray-50 rounded-lg border">
|
||||||
|
<div className="flex items-center gap-2 mb-3">
|
||||||
|
<ShoppingCart className="w-5 h-5 text-gray-600" />
|
||||||
|
<h4
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontWeight: '600',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cart Summary
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Total Items:
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontWeight: '500'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cartItems.length} programme{cartItems.length !== 1 ? 's' : ''}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Subtotal:
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontWeight: '600'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
₹{subtotal.toLocaleString('en-IN')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between font-semibold pt-2 border-t border-gray-200">
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontWeight: '600'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Estimated Total:
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-primary)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontWeight: '700'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
₹{estimatedTotal.toLocaleString('en-IN')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className="text-center mt-2"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Final total calculated at checkout
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Total Value Highlight Box */}
|
||||||
|
{cartItems.length > 0 && (
|
||||||
|
<div
|
||||||
|
className="mt-6 p-6 rounded-lg text-center"
|
||||||
|
style={{ backgroundColor: 'rgba(4, 4, 91, 0.05)' }}
|
||||||
|
>
|
||||||
|
<h4
|
||||||
|
className="mb-2"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontWeight: '600',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Total Value
|
||||||
|
</h4>
|
||||||
|
<div
|
||||||
|
className="mb-2"
|
||||||
|
style={{
|
||||||
|
fontSize: '2rem',
|
||||||
|
fontWeight: '700',
|
||||||
|
color: 'var(--color-primary)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
₹{estimatedTotal.toLocaleString('en-IN')}
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-primary)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontWeight: '500'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cartItems.length} programme{cartItems.length !== 1 ? 's' : ''} selected
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Empty Cart Message */}
|
||||||
|
{cartItems.length === 0 && (
|
||||||
|
<div className="text-center py-8">
|
||||||
|
<ShoppingCart className="w-16 h-16 mx-auto text-gray-300 mb-4" />
|
||||||
|
<h3
|
||||||
|
className="mb-2"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h4)',
|
||||||
|
fontWeight: 'var(--font-weight-h4)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Your cart is empty
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
className="mb-6"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Browse our courses and add programmes to get started.
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
onClick={handleContinueShopping}
|
||||||
|
className="bg-primary text-white hover:bg-primary/90"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-primary)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Browse Courses
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer Actions */}
|
||||||
|
{cartItems.length > 0 && (
|
||||||
|
<div className="border-t border-gray-200 p-6 bg-white">
|
||||||
|
{/* CTA Buttons */}
|
||||||
|
<div className="space-y-3 mb-4">
|
||||||
|
{/* Proceed to Checkout - Primary */}
|
||||||
|
<Button
|
||||||
|
onClick={handleProceedToCheckout}
|
||||||
|
className="w-full flex items-center justify-center gap-2 h-12 rounded-lg font-medium"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-primary)',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontWeight: '600'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ShoppingCart className="w-5 h-5" />
|
||||||
|
Proceed to Checkout
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Continue Shopping - Secondary */}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleContinueShopping}
|
||||||
|
className="w-full flex items-center justify-center gap-2 h-12 rounded-lg font-medium"
|
||||||
|
style={{
|
||||||
|
borderColor: 'var(--color-primary)',
|
||||||
|
color: 'var(--color-primary)',
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontWeight: '500',
|
||||||
|
borderWidth: '2px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Continue Shopping
|
||||||
|
<ArrowRight className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Security Footer */}
|
||||||
|
<div className="flex items-center justify-center gap-2 pt-3 border-t border-gray-100">
|
||||||
|
<Shield className="w-4 h-4 text-green-600" />
|
||||||
|
<p
|
||||||
|
className="text-center"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Secure checkout • 30-day money-back guarantee
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
}
|
||||||
250
.gitea/workflows/src/components/Chatbot.tsx
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
|
import { MessageCircle, X, Send, User, Bot, Minimize2 } from 'lucide-react';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import exampleImage from 'figma:asset/a28d79dd35b730f689b77dbb30452ca27bd25759.png';
|
||||||
|
|
||||||
|
interface Message {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
sender: 'user' | 'bot';
|
||||||
|
timestamp: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Chatbot() {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [messages, setMessages] = useState<Message[]>([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
text: 'Hello! I\'m here to help you learn more about Kautilya Leadership Centre. How can I assist you today?',
|
||||||
|
sender: 'bot',
|
||||||
|
timestamp: new Date()
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
const [inputValue, setInputValue] = useState('');
|
||||||
|
const [isTyping, setIsTyping] = useState(false);
|
||||||
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const scrollToBottom = () => {
|
||||||
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
scrollToBottom();
|
||||||
|
}, [messages]);
|
||||||
|
|
||||||
|
const predefinedResponses: { [key: string]: string } = {
|
||||||
|
'hello': 'Hello! Welcome to Kautilya Leadership Centre. I\'m here to help you with information about our programs, services, and how we can support your leadership development journey.',
|
||||||
|
'programs': 'We offer comprehensive leadership development programs including executive coaching, team development, strategic leadership training, and virtual learning experiences. Would you like to know more about any specific program?',
|
||||||
|
'services': 'Our services include Leadership Development, Executive Coaching, Team Building, Strategic Planning, Organizational Development, and Virtual Learning Platforms. Each is designed to enhance leadership capabilities.',
|
||||||
|
'contact': 'You can reach us through our contact form on the website, or feel free to schedule a consultation. We\'d be happy to discuss how we can support your leadership development goals.',
|
||||||
|
'virtual': 'Our virtual learning platform offers flexible, interactive leadership development experiences. You can access courses, participate in live sessions, and connect with other leaders from anywhere.',
|
||||||
|
'coaching': 'Our executive coaching program provides personalized one-on-one guidance to help leaders develop their skills, overcome challenges, and achieve their professional goals.',
|
||||||
|
'team': 'Our team development services focus on building high-performing teams through collaborative workshops, communication training, and strategic alignment exercises.',
|
||||||
|
'webinar': 'Join our upcoming leadership webinars! We regularly host sessions covering various leadership topics. Check our webinars section for schedules and registration.',
|
||||||
|
'default': 'That\'s a great question! For detailed information about our specific programs and services, I\'d recommend exploring our website or contacting our team directly. Is there anything specific about leadership development I can help you with?'
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBotResponse = (userMessage: string): string => {
|
||||||
|
const lowerMessage = userMessage.toLowerCase();
|
||||||
|
|
||||||
|
for (const [key, response] of Object.entries(predefinedResponses)) {
|
||||||
|
if (key !== 'default' && lowerMessage.includes(key)) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return predefinedResponses.default;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSendMessage = async () => {
|
||||||
|
if (!inputValue.trim()) return;
|
||||||
|
|
||||||
|
const userMessage: Message = {
|
||||||
|
id: Date.now().toString(),
|
||||||
|
text: inputValue,
|
||||||
|
sender: 'user',
|
||||||
|
timestamp: new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
setMessages(prev => [...prev, userMessage]);
|
||||||
|
setInputValue('');
|
||||||
|
setIsTyping(true);
|
||||||
|
|
||||||
|
// Simulate typing delay
|
||||||
|
setTimeout(() => {
|
||||||
|
const botMessage: Message = {
|
||||||
|
id: (Date.now() + 1).toString(),
|
||||||
|
text: getBotResponse(inputValue),
|
||||||
|
sender: 'bot',
|
||||||
|
timestamp: new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
setMessages(prev => [...prev, botMessage]);
|
||||||
|
setIsTyping(false);
|
||||||
|
}, 1500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSendMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTime = (date: Date) => {
|
||||||
|
return date.toLocaleTimeString('en-US', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Chatbot Toggle Button */}
|
||||||
|
<div className="fixed bottom-6 right-6 z-50">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
className="w-16 h-16 rounded-full shadow-xl transition-all duration-300 hover:scale-105 focus:outline-none focus:ring-4 focus:ring-blue-300 chatbot-button"
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#04045B',
|
||||||
|
boxShadow: '0 8px 32px rgba(4, 4, 91, 0.3)'
|
||||||
|
}}
|
||||||
|
aria-label="Open chat"
|
||||||
|
>
|
||||||
|
{isOpen ? (
|
||||||
|
<X className="w-7 h-7 text-white mx-auto" />
|
||||||
|
) : (
|
||||||
|
<MessageCircle className="w-7 h-7 text-white mx-auto" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Chatbot Window */}
|
||||||
|
{isOpen && (
|
||||||
|
<div
|
||||||
|
className="fixed bottom-24 right-6 w-80 bg-white rounded-2xl shadow-2xl border-0 z-50 flex flex-col overflow-hidden"
|
||||||
|
style={{
|
||||||
|
height: '500px',
|
||||||
|
boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.1)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Header with gradient background */}
|
||||||
|
<div
|
||||||
|
className="p-6 flex items-center justify-between relative"
|
||||||
|
style={{
|
||||||
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||||
|
borderRadius: '16px 16px 0 0'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<div className="w-10 h-10 bg-white/20 backdrop-blur-sm rounded-full flex items-center justify-center">
|
||||||
|
<Bot className="w-6 h-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-white font-semibold text-lg">Welcome to KLC</h3>
|
||||||
|
<p className="text-white/80 text-sm">How can I help you today?</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsOpen(false)}
|
||||||
|
className="text-white/70 hover:text-white transition-colors p-1"
|
||||||
|
>
|
||||||
|
<Minimize2 className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsOpen(false)}
|
||||||
|
className="text-white/70 hover:text-white transition-colors p-1"
|
||||||
|
>
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Messages Container */}
|
||||||
|
<div className="flex-1 p-5 overflow-y-auto space-y-4" style={{ backgroundColor: '#f8fafc' }}>
|
||||||
|
{messages.map((message) => (
|
||||||
|
<div
|
||||||
|
key={message.id}
|
||||||
|
className={`flex ${message.sender === 'user' ? 'justify-end' : 'justify-start'}`}
|
||||||
|
>
|
||||||
|
<div className={`flex items-start space-x-3 max-w-[85%] ${message.sender === 'user' ? 'flex-row-reverse space-x-reverse' : ''}`}>
|
||||||
|
<div className={`w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 ${
|
||||||
|
message.sender === 'user'
|
||||||
|
? 'bg-gradient-to-r from-blue-500 to-purple-600'
|
||||||
|
: 'bg-gradient-to-r from-purple-500 to-pink-500'
|
||||||
|
}`}>
|
||||||
|
{message.sender === 'user' ? (
|
||||||
|
<User className="w-4 h-4 text-white" />
|
||||||
|
) : (
|
||||||
|
<Bot className="w-4 h-4 text-white" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className={`p-4 rounded-2xl ${
|
||||||
|
message.sender === 'user'
|
||||||
|
? 'bg-gradient-to-r from-blue-500 to-purple-600 text-white'
|
||||||
|
: 'bg-white text-gray-800 shadow-sm border border-gray-100'
|
||||||
|
}`}>
|
||||||
|
<p className="text-sm leading-relaxed">{message.text}</p>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-gray-400 mt-2 px-2">
|
||||||
|
{formatTime(message.timestamp)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Typing Indicator */}
|
||||||
|
{isTyping && (
|
||||||
|
<div className="flex justify-start">
|
||||||
|
<div className="flex items-start space-x-3">
|
||||||
|
<div className="w-8 h-8 rounded-full bg-gradient-to-r from-purple-500 to-pink-500 flex items-center justify-center">
|
||||||
|
<Bot className="w-4 h-4 text-white" />
|
||||||
|
</div>
|
||||||
|
<div className="bg-white p-4 rounded-2xl shadow-sm border border-gray-100">
|
||||||
|
<div className="flex space-x-1">
|
||||||
|
<div className="w-2 h-2 bg-purple-400 rounded-full animate-bounce"></div>
|
||||||
|
<div className="w-2 h-2 bg-purple-400 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }}></div>
|
||||||
|
<div className="w-2 h-2 bg-purple-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div ref={messagesEndRef} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Input Area */}
|
||||||
|
<div className="p-5 bg-white border-t border-gray-100">
|
||||||
|
<div className="flex space-x-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={inputValue}
|
||||||
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
|
onKeyPress={handleKeyPress}
|
||||||
|
placeholder="Type your message..."
|
||||||
|
className="flex-1 px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-sm bg-gray-50"
|
||||||
|
style={{ fontFamily: 'var(--font-family-brand)' }}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleSendMessage}
|
||||||
|
disabled={!inputValue.trim()}
|
||||||
|
className="px-4 py-3 rounded-xl text-white transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none focus:ring-2 focus:ring-purple-500"
|
||||||
|
style={{
|
||||||
|
background: inputValue.trim()
|
||||||
|
? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
|
||||||
|
: '#e5e7eb'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Send className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
492
.gitea/workflows/src/components/Contact.tsx
Normal file
@@ -0,0 +1,492 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { MapPin, Phone, Mail, Clock, CheckCircle2, ArrowRight, Send, Calendar, Users } from 'lucide-react';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Input } from './ui/input';
|
||||||
|
import { Textarea } from './ui/textarea';
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
|
||||||
|
import { BrandedTag } from './about/BrandedTag';
|
||||||
|
import { PrimaryCTAButton } from './PrimaryCTAButton';
|
||||||
|
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||||
|
import { navigateTo } from './Router';
|
||||||
|
import kautilyabg from '../assets/Kautilya.png';
|
||||||
|
import { useCreateLeadMutation, useGetLeadCategoriesQuery } from '../redux/services/contactUsApi';
|
||||||
|
|
||||||
|
interface ContactProps {
|
||||||
|
topic?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Contact({ topic }: ContactProps) {
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: '',
|
||||||
|
mobileNumber: '',
|
||||||
|
emailId: '',
|
||||||
|
interestedIn: '',
|
||||||
|
message: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
|
const { data: categoryData } = useGetLeadCategoriesQuery({
|
||||||
|
limit: 20,
|
||||||
|
offset: 0,
|
||||||
|
status: "active"
|
||||||
|
});
|
||||||
|
|
||||||
|
const categories = categoryData?.data?.items || [];
|
||||||
|
|
||||||
|
const [createLead] = useCreateLeadMutation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('Contact component mounted with topic:', topic);
|
||||||
|
// Set default interested in based on topic parameter
|
||||||
|
if (topic) {
|
||||||
|
const interestedIn = getTopicSubject(topic);
|
||||||
|
console.log('Setting form interestedIn to:', interestedIn);
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
interestedIn: interestedIn
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [topic]);
|
||||||
|
|
||||||
|
const getTopicSubject = (topicParam: string) => {
|
||||||
|
switch (topicParam) {
|
||||||
|
case 'leadership-pipeline':
|
||||||
|
return 'Leadership Pipeline Development';
|
||||||
|
case 'consulting':
|
||||||
|
return 'Consulting Services';
|
||||||
|
case 'executive-coaching':
|
||||||
|
return 'Executive Coaching';
|
||||||
|
case 'learning-facility':
|
||||||
|
return 'Learning Facility';
|
||||||
|
default:
|
||||||
|
return topicParam.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (field: string, value: string) => {
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
[field]: value
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsSubmitting(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await createLead({
|
||||||
|
full_name: formData.name,
|
||||||
|
phone_number: formData.mobileNumber,
|
||||||
|
email_address: formData.emailId,
|
||||||
|
lead_category_xid: formData.interestedIn,
|
||||||
|
message: formData.message,
|
||||||
|
lead_status: "NEW"
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
setIsSubmitted(true);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Lead creation failed", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubmitting(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isSubmitted) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen" style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
<div className="section-margin-x py-24">
|
||||||
|
<div className="max-w-2xl mx-auto text-center">
|
||||||
|
<div className="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||||
|
<CheckCircle2 className="w-10 h-10 text-green-600" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="text-h2 mb-4">Thank You!</h1>
|
||||||
|
|
||||||
|
<p className="text-body-lg text-muted mb-8">
|
||||||
|
We've received your message and will get back to you within 24 hours.
|
||||||
|
Our leadership development experts are excited to help transform your organization.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<PrimaryCTAButton
|
||||||
|
text="Return to Home"
|
||||||
|
onClick={() => navigateTo('/')}
|
||||||
|
ariaLabel="Return to homepage"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen" style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
{/* Hero Section with Google Map Background */}
|
||||||
|
<section className="relative h-[600px] overflow-hidden">
|
||||||
|
{/* Google Map Background */}
|
||||||
|
<div className="absolute inset-0">
|
||||||
|
<ImageWithFallback
|
||||||
|
src={kautilyabg}
|
||||||
|
alt="Google Maps showing office location and navigation"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Dark overlay for better text readability */}
|
||||||
|
<div className="absolute inset-0 bg-black/60" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content Container */}
|
||||||
|
<div className="relative h-full flex items-center justify-center section-margin-x">
|
||||||
|
{/* Hero Content Block */}
|
||||||
|
<div className="text-center max-w-4xl mx-auto">
|
||||||
|
{/* Branded Tag */}
|
||||||
|
<BrandedTag text="Contact Us" variant="white" />
|
||||||
|
|
||||||
|
{/* Main Headline */}
|
||||||
|
<h1 className="text-h1-white mb-6">
|
||||||
|
Reach us
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{/* Supporting Text */}
|
||||||
|
<p className="text-body-lg-white opacity-90 max-w-3xl mx-auto">
|
||||||
|
Get in touch with us to start your leadership development journey.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Main Content Section */}
|
||||||
|
<div className="section-margin-x py-16">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-12">
|
||||||
|
{/* Contact Information - Enhanced Design */}
|
||||||
|
<div className="lg:col-span-1">
|
||||||
|
<div className="bg-white rounded-xl shadow-lg border border-gray-100 h-fit overflow-hidden">
|
||||||
|
{/* Contact Info Header */}
|
||||||
|
<div
|
||||||
|
className="p-8 text-white"
|
||||||
|
style={{ backgroundColor: 'var(--color-brand-primary)' }}
|
||||||
|
>
|
||||||
|
<h2 className="text-h3-white mb-2">Contact Information</h2>
|
||||||
|
<p className="text-body-white opacity-90">
|
||||||
|
We're here to help you on your leadership journey
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contact Details */}
|
||||||
|
<div className="p-8 space-y-8">
|
||||||
|
{/* Corporate Office */}
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
className="w-12 h-12 rounded-lg flex items-center justify-center flex-shrink-0"
|
||||||
|
style={{ backgroundColor: 'rgba(248, 195, 1, 0.1)' }}
|
||||||
|
>
|
||||||
|
<MapPin className="w-6 h-6" style={{ color: 'var(--color-brand-primary)' }} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-h3 mb-2">Corporate Office</h3>
|
||||||
|
<p className="text-body text-muted">
|
||||||
|
Leadership Centre Pvt. Ltd.<br />
|
||||||
|
No. 107, 1st Floor<br />
|
||||||
|
T.V. Industrial Estate, Worli<br />
|
||||||
|
Mumbai 400030
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Registered Office */}
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
className="w-12 h-12 rounded-lg flex items-center justify-center flex-shrink-0"
|
||||||
|
style={{ backgroundColor: 'rgba(248, 195, 1, 0.1)' }}
|
||||||
|
>
|
||||||
|
<MapPin className="w-6 h-6" style={{ color: 'var(--color-brand-primary)' }} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-h3 mb-2">Registered Office</h3>
|
||||||
|
<p className="text-body text-muted">
|
||||||
|
Kautilya Leadership Services<br />
|
||||||
|
R.S. No. 5/6, Savaroli Kharpada Road<br />
|
||||||
|
Dhamani Taluk, Khalapur District<br />
|
||||||
|
Raigad – 410202
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Email */}
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
className="w-12 h-12 rounded-lg flex items-center justify-center flex-shrink-0"
|
||||||
|
style={{ backgroundColor: 'rgba(248, 195, 1, 0.1)' }}
|
||||||
|
>
|
||||||
|
<Mail className="w-6 h-6" style={{ color: 'var(--color-brand-primary)' }} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-h3 mb-2">Email</h3>
|
||||||
|
<p className="text-body text-muted">
|
||||||
|
connect@leadershipcentre.in<br />
|
||||||
|
connect@kautilyaservices.in
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Phone */}
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
className="w-12 h-12 rounded-lg flex items-center justify-center flex-shrink-0"
|
||||||
|
style={{ backgroundColor: 'rgba(248, 195, 1, 0.1)' }}
|
||||||
|
>
|
||||||
|
<Phone className="w-6 h-6" style={{ color: 'var(--color-brand-primary)' }} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-h3 mb-2">Phone</h3>
|
||||||
|
<p className="text-body text-muted">
|
||||||
|
+91-22-2084 0097<br />
|
||||||
|
+91-8928738661
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quick Actions */}
|
||||||
|
<div
|
||||||
|
className="p-8 border-t"
|
||||||
|
style={{ borderColor: 'var(--color-border)' }}
|
||||||
|
>
|
||||||
|
<h3 className="text-h3 mb-4">Quick Actions</h3>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full justify-start hover:bg-blue-50 hover:border-blue-200 transition-all duration-300"
|
||||||
|
onClick={() => navigateTo('/services/learning-facility')}
|
||||||
|
style={{
|
||||||
|
borderColor: 'var(--color-brand-primary)',
|
||||||
|
color: 'var(--color-brand-primary)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Calendar className="w-4 h-4 mr-2" />
|
||||||
|
Schedule Facility Tour
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full justify-start hover:bg-blue-50 hover:border-blue-200 transition-all duration-300"
|
||||||
|
onClick={() => navigateTo('/leadership-journey')}
|
||||||
|
style={{
|
||||||
|
borderColor: 'var(--color-brand-primary)',
|
||||||
|
color: 'var(--color-brand-primary)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Users className="w-4 h-4 mr-2" />
|
||||||
|
Start Leadership Assessment
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contact Form - Enhanced Design */}
|
||||||
|
<div className="lg:col-span-2">
|
||||||
|
<div className="bg-white rounded-xl shadow-lg border border-gray-100">
|
||||||
|
{/* Form Header */}
|
||||||
|
<div className="p-8 border-b" style={{ borderColor: 'var(--color-border)' }}>
|
||||||
|
<div className="flex items-center gap-3 mb-2">
|
||||||
|
<div
|
||||||
|
className="w-10 h-10 rounded-lg flex items-center justify-center"
|
||||||
|
style={{ backgroundColor: 'rgba(248, 195, 1, 0.1)' }}
|
||||||
|
>
|
||||||
|
<Send className="w-5 h-5" style={{ color: 'var(--color-brand-primary)' }} />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-h3">Enquiry Form</h2>
|
||||||
|
</div>
|
||||||
|
<p className="text-body text-muted">
|
||||||
|
Fill out the form below and we'll get back to you within 24 hours.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Form Content */}
|
||||||
|
<div className="p-8">
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<label className="text-body font-medium mb-3 block" style={{ color: 'var(--color-black)' }}>
|
||||||
|
Name *
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => handleInputChange('name', e.target.value)}
|
||||||
|
placeholder="Enter your full name"
|
||||||
|
className="w-full h-12 border-gray-200 focus:border-blue-500 focus:ring-blue-500"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontSize: 'var(--font-body)'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-body font-medium mb-3 block" style={{ color: 'var(--color-black)' }}>
|
||||||
|
Mobile Number *
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
type="tel"
|
||||||
|
required
|
||||||
|
value={formData.mobileNumber}
|
||||||
|
onChange={(e) => handleInputChange('mobileNumber', e.target.value)}
|
||||||
|
placeholder="+91 98765 43210"
|
||||||
|
className="w-full h-12 border-gray-200 focus:border-blue-500 focus:ring-blue-500"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontSize: 'var(--font-body)'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-body font-medium mb-3 block" style={{ color: 'var(--color-black)' }}>
|
||||||
|
Email Id *
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
value={formData.emailId}
|
||||||
|
onChange={(e) => handleInputChange('emailId', e.target.value)}
|
||||||
|
placeholder="example@company.com"
|
||||||
|
className="w-full h-12 border-gray-200 focus:border-blue-500 focus:ring-blue-500"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontSize: 'var(--font-body)'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-body font-medium mb-3 block" style={{ color: 'var(--color-black)' }}>
|
||||||
|
Interested In *
|
||||||
|
</label>
|
||||||
|
<Select
|
||||||
|
value={formData.interestedIn}
|
||||||
|
onValueChange={(value: string) => handleInputChange('interestedIn', value)}
|
||||||
|
>
|
||||||
|
<SelectTrigger
|
||||||
|
className="w-full h-12 border-gray-200 focus:border-blue-500 focus:ring-blue-500"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontSize: 'var(--font-body)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectValue placeholder="Select your area of interest" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectContent>
|
||||||
|
{categories.map((cat: any) => (
|
||||||
|
<SelectItem key={cat.id} value={cat.id}>
|
||||||
|
{cat.category_type}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-body font-medium mb-3 block" style={{ color: 'var(--color-black)' }}>
|
||||||
|
Enter Message *
|
||||||
|
</label>
|
||||||
|
<Textarea
|
||||||
|
required
|
||||||
|
value={formData.message}
|
||||||
|
onChange={(e) => handleInputChange('message', e.target.value)}
|
||||||
|
placeholder="Please enter your message or inquiry..."
|
||||||
|
rows={6}
|
||||||
|
className="w-full border-gray-200 focus:border-blue-500 focus:ring-blue-500 resize-none"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontSize: 'var(--font-body)'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-6 border-t" style={{ borderColor: 'var(--color-border)' }}>
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 justify-between items-start sm:items-center">
|
||||||
|
<div className="text-small text-muted">
|
||||||
|
* Required fields. We'll respond within 24 hours.
|
||||||
|
</div>
|
||||||
|
<PrimaryCTAButton
|
||||||
|
text={isSubmitting ? "Sending Message..." : "Send Message"}
|
||||||
|
onClick={() => { handleSubmit }}
|
||||||
|
ariaLabel="Send contact message"
|
||||||
|
className="w-full sm:w-auto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Enhanced CTA Section - Matching Landing Page Style */}
|
||||||
|
<section className="relative h-[800px] overflow-hidden">
|
||||||
|
{/* Background Image */}
|
||||||
|
<div className="absolute inset-0">
|
||||||
|
<ImageWithFallback
|
||||||
|
src="https://images.unsplash.com/photo-1552664730-d307ca884978?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2940&q=80"
|
||||||
|
alt="Professional team collaborating in modern office"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Subtle dark overlay for overall image */}
|
||||||
|
<div className="absolute inset-0 bg-black/30" />
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content Container */}
|
||||||
|
<div className="relative h-full flex items-center justify-end section-margin-x">
|
||||||
|
{/* CTA Content Block */}
|
||||||
|
<div
|
||||||
|
className="bg-opacity-95 backdrop-blur-sm rounded-lg p-16 max-w-2xl"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-brand-primary)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Branded Tag */}
|
||||||
|
<BrandedTag text="Next Steps" variant="white" />
|
||||||
|
|
||||||
|
{/* Main Headline */}
|
||||||
|
<h2 className="text-h2-white mb-8">
|
||||||
|
Ready to transform your leadership?
|
||||||
|
<span
|
||||||
|
className="italic"
|
||||||
|
style={{ color: 'var(--color-brand-accent)' }}
|
||||||
|
>
|
||||||
|
{" "}Get in touch{" "}
|
||||||
|
</span>
|
||||||
|
to start your development journey now.
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<PrimaryCTAButton
|
||||||
|
text="Schedule a Consultation"
|
||||||
|
onClick={() => navigateTo('/contact?topic=consulting')}
|
||||||
|
ariaLabel="Schedule a consultation with our leadership experts"
|
||||||
|
className="cta-banner-yellow"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Supporting Text */}
|
||||||
|
<p className="text-body-white opacity-90">
|
||||||
|
Connect with our leadership experts to discuss your organization's specific development needs.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
527
.gitea/workflows/src/components/CorporateSignIn.tsx
Normal file
@@ -0,0 +1,527 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Input } from './ui/input';
|
||||||
|
import { Label } from './ui/label';
|
||||||
|
import { Card, CardContent } from './ui/card';
|
||||||
|
import { Checkbox } from './ui/checkbox';
|
||||||
|
import {
|
||||||
|
Users,
|
||||||
|
BarChart3,
|
||||||
|
Settings,
|
||||||
|
ArrowRight,
|
||||||
|
Eye,
|
||||||
|
EyeOff,
|
||||||
|
Building2,
|
||||||
|
BookOpen,
|
||||||
|
Target,
|
||||||
|
Award,
|
||||||
|
User
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { navigateTo } from './Router';
|
||||||
|
|
||||||
|
export function CorporateSignIn() {
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const [rememberMe, setRememberMe] = useState(false);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleSignIn = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
// Simulate authentication
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
// Navigate to dashboard or success page
|
||||||
|
navigateTo('https://klc-hr.wdiprojects.com');
|
||||||
|
}, 1500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformationFeatures = [
|
||||||
|
{
|
||||||
|
icon: BookOpen,
|
||||||
|
title: 'World-Class Content',
|
||||||
|
description: 'Access premium leadership courses from industry experts'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Users,
|
||||||
|
title: 'Global Community',
|
||||||
|
description: 'Join 25,000+ leaders from top organizations worldwide'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Award,
|
||||||
|
title: 'Recognized Credentials',
|
||||||
|
description: 'Earn certificates valued by Fortune 500 companies'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Target,
|
||||||
|
title: 'Personalized Path',
|
||||||
|
description: 'Get customized learning recommendations based on your goals'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const corporateFeatures = [
|
||||||
|
{
|
||||||
|
icon: Users,
|
||||||
|
title: 'Bulk Enrollment',
|
||||||
|
description: 'Enroll entire teams with volume discounts up to 30%'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: BarChart3,
|
||||||
|
title: 'Progress Analytics',
|
||||||
|
description: 'Real-time dashboards to track team learning outcomes'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Settings,
|
||||||
|
title: 'Custom Programs',
|
||||||
|
description: 'Tailored leadership development aligned with your goals'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
{/* Main Content */}
|
||||||
|
<div className="section-margin-x py-16">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
|
||||||
|
{/* Header Section */}
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<div className="mb-6">
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center gap-2 px-4 py-2 rounded-full text-sm font-medium"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'rgba(248, 195, 1, 0.15)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Building2 className="w-4 h-4" />
|
||||||
|
Corporate Solutions
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1
|
||||||
|
className="mb-6"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h2)',
|
||||||
|
fontWeight: 'var(--font-weight-h2)',
|
||||||
|
lineHeight: 'var(--line-height-h2)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Welcome Back, KLC Partner
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p
|
||||||
|
className="mb-12 max-w-2xl mx-auto"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body-lg)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: 'var(--line-height-body-lg)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Access your corporate dashboard to manage team enrollments, track learning
|
||||||
|
progress, and continue building your organization's leadership capabilities.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Two Column Layout */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-start">
|
||||||
|
|
||||||
|
{/* Left Column - Content */}
|
||||||
|
<div>
|
||||||
|
{/* Leadership Transformation Section */}
|
||||||
|
<div className="mb-12">
|
||||||
|
<div className="mb-6">
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center gap-2 px-4 py-2 rounded-full text-sm font-medium"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'rgba(248, 195, 1, 0.15)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
⭐ Join KLC Community
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2
|
||||||
|
className="mb-4"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h3)',
|
||||||
|
fontWeight: 'var(--font-weight-h3)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Start Your Leadership Transformation
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p
|
||||||
|
className="mb-8"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: 'var(--line-height-body)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Join thousands of professionals who have accelerated their careers through KLC's
|
||||||
|
proven leadership development programs. Your journey to becoming an exceptional leader starts here.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
{transformationFeatures.map((feature, index) => (
|
||||||
|
<div key={index} className="flex items-start gap-3">
|
||||||
|
<div
|
||||||
|
className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0"
|
||||||
|
style={{ backgroundColor: 'var(--color-primary)' }}
|
||||||
|
>
|
||||||
|
<feature.icon className="w-4 h-4 text-white" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3
|
||||||
|
className="font-medium mb-1"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{feature.title}
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: '1.3'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{feature.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Corporate Dashboard Features */}
|
||||||
|
<div>
|
||||||
|
<h2
|
||||||
|
className="mb-8"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h4)',
|
||||||
|
fontWeight: 'var(--font-weight-h4)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Corporate Dashboard Features
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
{corporateFeatures.map((feature, index) => (
|
||||||
|
<div key={index} className="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
className="w-12 h-12 rounded-lg flex items-center justify-center flex-shrink-0"
|
||||||
|
style={{ backgroundColor: 'var(--color-primary)' }}
|
||||||
|
>
|
||||||
|
<feature.icon className="w-6 h-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3
|
||||||
|
className="font-medium mb-2"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{feature.title}
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: 'var(--line-height-small)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{feature.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Existing Customer Note */}
|
||||||
|
<div
|
||||||
|
className="mt-8 p-4 rounded-lg border"
|
||||||
|
style={{ backgroundColor: 'rgba(0, 0, 0, 0.02)' }}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
className="mb-2"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontWeight: '500',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Already have a corporate account?
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={() => navigateTo('/corporate-login')}
|
||||||
|
className="text-blue-600 hover:text-blue-700 transition-colors"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Sign in to your dashboard
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Column - Sign In Form */}
|
||||||
|
<div>
|
||||||
|
<Card className="shadow-lg border-0">
|
||||||
|
<CardContent className="p-8">
|
||||||
|
|
||||||
|
{/* Form Header */}
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<h2
|
||||||
|
className="mb-2"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h4)',
|
||||||
|
fontWeight: 'var(--font-weight-h4)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Sign In to Your Dashboard
|
||||||
|
</h2>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Manage your corporate learning programs
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sign In Form */}
|
||||||
|
<form onSubmit={handleSignIn} className="space-y-6">
|
||||||
|
|
||||||
|
{/* Email Field */}
|
||||||
|
<div>
|
||||||
|
<Label
|
||||||
|
htmlFor="email"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontWeight: '500',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Work Email *
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
placeholder="your.work@company.com"
|
||||||
|
required
|
||||||
|
className="mt-2 h-12"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Password Field */}
|
||||||
|
<div>
|
||||||
|
<Label
|
||||||
|
htmlFor="password"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontWeight: '500',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Password *
|
||||||
|
</Label>
|
||||||
|
<div className="relative mt-2">
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type={showPassword ? 'text' : 'password'}
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
placeholder="Enter your password"
|
||||||
|
required
|
||||||
|
className="h-12 pr-12"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700"
|
||||||
|
>
|
||||||
|
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Remember Me */}
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="remember"
|
||||||
|
checked={rememberMe}
|
||||||
|
onCheckedChange={setRememberMe}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
htmlFor="remember"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Remember me
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sign In Button */}
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isLoading}
|
||||||
|
className="w-full h-14 text-white font-medium flex items-center justify-center gap-2"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-primary)',
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
borderRadius: '10px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
Sign In
|
||||||
|
<ArrowRight className="w-5 h-5" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* Demo Credentials */}
|
||||||
|
<div
|
||||||
|
className="mt-8 p-4 rounded-lg border"
|
||||||
|
style={{ backgroundColor: 'rgba(0, 0, 0, 0.02)' }}
|
||||||
|
>
|
||||||
|
<h4
|
||||||
|
className="mb-2 flex items-center gap-2"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontWeight: '500',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="w-4 h-4 rounded-full bg-gray-300 flex items-center justify-center">
|
||||||
|
<span className="text-xs text-gray-600">i</span>
|
||||||
|
</div>
|
||||||
|
Demo credentials:
|
||||||
|
</h4>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: 'var(--line-height-small)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>Email: demo@company.com | Password: demo123</p>
|
||||||
|
<p>MFA Demo: mfa@company.com | Password: demo123 (Code: 123456)</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sign Up Link */}
|
||||||
|
<div className="text-center mt-8">
|
||||||
|
<button
|
||||||
|
onClick={() => navigateTo('/corporate-signup')}
|
||||||
|
className="text-blue-600 hover:text-blue-700 transition-colors"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
New to KLC? Sign up your company
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Need Help Section - Separate Section Below */}
|
||||||
|
<div
|
||||||
|
className="section-margin-x py-12"
|
||||||
|
style={{ backgroundColor: 'rgba(0, 0, 0, 0.02)' }}
|
||||||
|
>
|
||||||
|
<div className="max-w-4xl mx-auto text-center">
|
||||||
|
<h3
|
||||||
|
className="mb-4"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h4)',
|
||||||
|
fontWeight: 'var(--font-weight-h4)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Need Help with Onboarding?
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
className="mb-6"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: 'var(--line-height-body)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Our team is ready to help you set up your corporate learning program and answer any questions about our enterprise solutions.
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="border-2 border-blue-600 text-blue-600 hover:bg-blue-600 hover:text-white"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontWeight: '500',
|
||||||
|
padding: '0.75rem 2rem',
|
||||||
|
borderRadius: '10px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Contact Sales Team
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
655
.gitea/workflows/src/components/CorporateSignUp.tsx
Normal file
@@ -0,0 +1,655 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Input } from './ui/input';
|
||||||
|
import { Label } from './ui/label';
|
||||||
|
import { Card, CardContent } from './ui/card';
|
||||||
|
import { Checkbox } from './ui/checkbox';
|
||||||
|
import {
|
||||||
|
Users,
|
||||||
|
BarChart3,
|
||||||
|
Settings,
|
||||||
|
ArrowRight,
|
||||||
|
Eye,
|
||||||
|
EyeOff,
|
||||||
|
Building2,
|
||||||
|
Globe,
|
||||||
|
User,
|
||||||
|
Mail,
|
||||||
|
Lock,
|
||||||
|
Phone,
|
||||||
|
BookOpen,
|
||||||
|
Target,
|
||||||
|
Award
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { navigateTo } from './Router';
|
||||||
|
|
||||||
|
export function CorporateSignUp() {
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
organisationName: '',
|
||||||
|
officialDomain: '',
|
||||||
|
contactName: '',
|
||||||
|
workEmail: '',
|
||||||
|
password: '',
|
||||||
|
phoneNumber: ''
|
||||||
|
});
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleInputChange = (field: string, value: string) => {
|
||||||
|
setFormData(prev => ({ ...prev, [field]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSignUp = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
// Simulate registration
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
// Navigate to dashboard or success page
|
||||||
|
navigateTo('/dashboard');
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformationFeatures = [
|
||||||
|
{
|
||||||
|
icon: BookOpen,
|
||||||
|
title: 'World-Class Content',
|
||||||
|
description: 'Access premium leadership courses from industry experts'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Users,
|
||||||
|
title: 'Global Community',
|
||||||
|
description: 'Join 25,000+ leaders from top organizations worldwide'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Award,
|
||||||
|
title: 'Recognized Credentials',
|
||||||
|
description: 'Earn certificates valued by Fortune 500 companies'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Target,
|
||||||
|
title: 'Personalized Path',
|
||||||
|
description: 'Get customized learning recommendations based on your goals'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const corporateFeatures = [
|
||||||
|
{
|
||||||
|
icon: Users,
|
||||||
|
title: 'Team Management',
|
||||||
|
description: 'Centralized dashboard to manage multiple learner accounts and progress tracking'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: BarChart3,
|
||||||
|
title: 'Advanced Analytics',
|
||||||
|
description: 'Comprehensive reporting and insights on team learning outcomes and ROI'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Settings,
|
||||||
|
title: 'Custom Solutions',
|
||||||
|
description: 'Tailored programs designed specifically for your organization\'s goals'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
{/* Main Content */}
|
||||||
|
<div className="section-margin-x py-16">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
|
||||||
|
{/* Header Section */}
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<div className="mb-6">
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center gap-2 px-4 py-2 rounded-full text-sm font-medium"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'rgba(248, 195, 1, 0.15)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Building2 className="w-4 h-4" />
|
||||||
|
Corporate Registration
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1
|
||||||
|
className="mb-6"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h2)',
|
||||||
|
fontWeight: 'var(--font-weight-h2)',
|
||||||
|
lineHeight: 'var(--line-height-h2)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Transform Your Organization's Leadership
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p
|
||||||
|
className="mb-12 max-w-2xl mx-auto"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body-lg)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: 'var(--line-height-body-lg)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Join thousands of organizations worldwide who trust KLC to develop their leaders.
|
||||||
|
Get started with enterprise-grade learning solutions and dedicated support.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Two Column Layout */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-start">
|
||||||
|
|
||||||
|
{/* Left Column - Content */}
|
||||||
|
<div>
|
||||||
|
{/* Leadership Transformation Section */}
|
||||||
|
<div className="mb-12">
|
||||||
|
<div className="mb-6">
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center gap-2 px-4 py-2 rounded-full text-sm font-medium"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'rgba(248, 195, 1, 0.15)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
⭐ Join KLC Community
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2
|
||||||
|
className="mb-4"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h3)',
|
||||||
|
fontWeight: 'var(--font-weight-h3)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Start Your Leadership Transformation
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p
|
||||||
|
className="mb-8"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: 'var(--line-height-body)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Join thousands of professionals who have accelerated their careers through KLC's
|
||||||
|
proven leadership development programs. Your journey to becoming an exceptional leader starts here.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
{transformationFeatures.map((feature, index) => (
|
||||||
|
<div key={index} className="flex items-start gap-3">
|
||||||
|
<div
|
||||||
|
className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0"
|
||||||
|
style={{ backgroundColor: 'var(--color-primary)' }}
|
||||||
|
>
|
||||||
|
<feature.icon className="w-4 h-4 text-white" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3
|
||||||
|
className="font-medium mb-1"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{feature.title}
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: '1.3'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{feature.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* What You'll Get with Corporate Access */}
|
||||||
|
<div>
|
||||||
|
<h2
|
||||||
|
className="mb-8"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h4)',
|
||||||
|
fontWeight: 'var(--font-weight-h4)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
What You'll Get with Corporate Access
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
{corporateFeatures.map((feature, index) => (
|
||||||
|
<div key={index} className="flex items-start gap-4">
|
||||||
|
<div
|
||||||
|
className="w-12 h-12 rounded-lg flex items-center justify-center flex-shrink-0"
|
||||||
|
style={{ backgroundColor: 'var(--color-primary)' }}
|
||||||
|
>
|
||||||
|
<feature.icon className="w-6 h-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3
|
||||||
|
className="font-medium mb-2"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{feature.title}
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: 'var(--line-height-small)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{feature.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Existing Customer Note */}
|
||||||
|
<div
|
||||||
|
className="mt-8 p-4 rounded-lg border"
|
||||||
|
style={{ backgroundColor: 'rgba(0, 0, 0, 0.02)' }}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
className="mb-2"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontWeight: '500',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Already have a corporate account?
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={() => navigateTo('/corporate-login')}
|
||||||
|
className="text-blue-600 hover:text-blue-700 transition-colors"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Sign in to your dashboard
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Column - Sign Up Form */}
|
||||||
|
<div>
|
||||||
|
<Card className="shadow-lg border-0">
|
||||||
|
<CardContent className="p-8">
|
||||||
|
|
||||||
|
{/* Form Header */}
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<h2
|
||||||
|
className="mb-2"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h4)',
|
||||||
|
fontWeight: 'var(--font-weight-h4)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create Your KLC HR Account
|
||||||
|
</h2>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Get started with KLC's corporate leadership development programs
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sign Up Form */}
|
||||||
|
<form onSubmit={handleSignUp} className="space-y-6">
|
||||||
|
|
||||||
|
{/* Organisation Name */}
|
||||||
|
<div>
|
||||||
|
<Label
|
||||||
|
htmlFor="organisationName"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontWeight: '500',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Organisation Name *
|
||||||
|
</Label>
|
||||||
|
<div className="relative mt-2">
|
||||||
|
<div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500">
|
||||||
|
<Building2 className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
id="organisationName"
|
||||||
|
type="text"
|
||||||
|
value={formData.organisationName}
|
||||||
|
onChange={(e) => handleInputChange('organisationName', e.target.value)}
|
||||||
|
placeholder="Your Company Name"
|
||||||
|
required
|
||||||
|
className="h-12 pl-12"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
backgroundColor: '#F3F3F5'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Official Domain */}
|
||||||
|
<div>
|
||||||
|
<Label
|
||||||
|
htmlFor="officialDomain"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontWeight: '500',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Official Domain *
|
||||||
|
</Label>
|
||||||
|
<div className="relative mt-2">
|
||||||
|
<div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500">
|
||||||
|
<Globe className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
id="officialDomain"
|
||||||
|
type="text"
|
||||||
|
value={formData.officialDomain}
|
||||||
|
onChange={(e) => handleInputChange('officialDomain', e.target.value)}
|
||||||
|
placeholder="company.com"
|
||||||
|
required
|
||||||
|
className="h-12 pl-12"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
backgroundColor: '#F3F3F5'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contact Name */}
|
||||||
|
<div>
|
||||||
|
<Label
|
||||||
|
htmlFor="contactName"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontWeight: '500',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Contact Name *
|
||||||
|
</Label>
|
||||||
|
<div className="relative mt-2">
|
||||||
|
<div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500">
|
||||||
|
<User className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
id="contactName"
|
||||||
|
type="text"
|
||||||
|
value={formData.contactName}
|
||||||
|
onChange={(e) => handleInputChange('contactName', e.target.value)}
|
||||||
|
placeholder="Your full name"
|
||||||
|
required
|
||||||
|
className="h-12 pl-12"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
backgroundColor: '#F3F3F5'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Work Email */}
|
||||||
|
<div>
|
||||||
|
<Label
|
||||||
|
htmlFor="workEmail"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontWeight: '500',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Work Email *
|
||||||
|
</Label>
|
||||||
|
<div className="relative mt-2">
|
||||||
|
<div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500">
|
||||||
|
<Mail className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
id="workEmail"
|
||||||
|
type="email"
|
||||||
|
value={formData.workEmail}
|
||||||
|
onChange={(e) => handleInputChange('workEmail', e.target.value)}
|
||||||
|
placeholder="you@company.com"
|
||||||
|
required
|
||||||
|
className="h-12 pl-12"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
backgroundColor: '#F3F3F5'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Password */}
|
||||||
|
<div>
|
||||||
|
<Label
|
||||||
|
htmlFor="password"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontWeight: '500',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Password *
|
||||||
|
</Label>
|
||||||
|
<div className="relative mt-2">
|
||||||
|
<div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500">
|
||||||
|
<Lock className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type={showPassword ? 'text' : 'password'}
|
||||||
|
value={formData.password}
|
||||||
|
onChange={(e) => handleInputChange('password', e.target.value)}
|
||||||
|
placeholder="Create a strong password"
|
||||||
|
required
|
||||||
|
className="h-12 pl-12 pr-12"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
backgroundColor: '#F3F3F5'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700"
|
||||||
|
>
|
||||||
|
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Phone Number (Optional) */}
|
||||||
|
<div>
|
||||||
|
<Label
|
||||||
|
htmlFor="phoneNumber"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontWeight: '500',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Phone Number (Optional)
|
||||||
|
</Label>
|
||||||
|
<div className="relative mt-2">
|
||||||
|
<div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500">
|
||||||
|
<Phone className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
id="phoneNumber"
|
||||||
|
type="tel"
|
||||||
|
value={formData.phoneNumber}
|
||||||
|
onChange={(e) => handleInputChange('phoneNumber', e.target.value)}
|
||||||
|
placeholder="+1 234 567 8900"
|
||||||
|
className="h-12 pl-12"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
backgroundColor: '#F3F3F5'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Create Account Button */}
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isLoading}
|
||||||
|
className="w-full h-14 text-white font-medium flex items-center justify-center gap-2"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-primary)',
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
borderRadius: '10px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
Create Account
|
||||||
|
<ArrowRight className="w-5 h-5" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* Terms Text */}
|
||||||
|
<div className="text-center mt-6">
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: 'var(--line-height-small)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
By creating an account, you agree to our{' '}
|
||||||
|
<span className="text-blue-600 hover:text-blue-700 cursor-pointer">
|
||||||
|
Corporate Terms
|
||||||
|
</span>{' '}
|
||||||
|
and{' '}
|
||||||
|
<span className="text-blue-600 hover:text-blue-700 cursor-pointer">
|
||||||
|
Privacy Policy
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sign In Link */}
|
||||||
|
<div className="text-center mt-6">
|
||||||
|
<button
|
||||||
|
onClick={() => navigateTo('/corporate-login')}
|
||||||
|
className="text-blue-600 hover:text-blue-700 transition-colors"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Already have an account? Sign in instead
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Need Help Section - Separate Section Below */}
|
||||||
|
{/* <div
|
||||||
|
className="section-margin-x py-12"
|
||||||
|
style={{ backgroundColor: 'rgba(0, 0, 0, 0.02)' }}
|
||||||
|
>
|
||||||
|
<div className="max-w-4xl mx-auto text-center">
|
||||||
|
<h3
|
||||||
|
className="mb-4"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h4)',
|
||||||
|
fontWeight: 'var(--font-weight-h4)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Need Help Getting Started?
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
className="mb-6"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
lineHeight: 'var(--line-height-body)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Our team is ready to help you set up your corporate learning program and answer any questions about our enterprise solutions.
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="border-2 border-blue-600 text-blue-600 hover:bg-blue-600 hover:text-white"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontWeight: '500',
|
||||||
|
padding: '0.75rem 2rem',
|
||||||
|
borderRadius: '10px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Contact Sales Team
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
268
.gitea/workflows/src/components/CourseCard.tsx
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Badge } from './ui/badge';
|
||||||
|
import {
|
||||||
|
Users,
|
||||||
|
Clock,
|
||||||
|
Star,
|
||||||
|
ArrowRight,
|
||||||
|
ShoppingCart
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { motion } from 'motion/react';
|
||||||
|
import { navigateTo } from './Router';
|
||||||
|
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||||
|
import type { CartItem } from './CartPopup';
|
||||||
|
|
||||||
|
// Course type interface
|
||||||
|
export interface Course {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
thumbnail: string;
|
||||||
|
duration: string;
|
||||||
|
level: string;
|
||||||
|
format: string;
|
||||||
|
rating: number;
|
||||||
|
reviews: string;
|
||||||
|
category: string;
|
||||||
|
description: string;
|
||||||
|
price: string;
|
||||||
|
originalPrice?: string; // For showing discounted prices
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CourseCardProps {
|
||||||
|
course: Course;
|
||||||
|
onClick?: () => void;
|
||||||
|
className?: string;
|
||||||
|
onAddToCart?: (item: CartItem) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CourseCard({ course, onClick, className, onAddToCart }: CourseCardProps) {
|
||||||
|
const handleClick = () => {
|
||||||
|
if (onClick) {
|
||||||
|
onClick();
|
||||||
|
} else {
|
||||||
|
navigateTo(`/course/${course.id}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddToCart = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation(); // Prevent card click when clicking Add to Cart
|
||||||
|
|
||||||
|
if (onAddToCart) {
|
||||||
|
const cartItem: CartItem = {
|
||||||
|
id: course.id,
|
||||||
|
title: course.title,
|
||||||
|
thumbnail: course.thumbnail,
|
||||||
|
price: course.price,
|
||||||
|
originalPrice: course.originalPrice,
|
||||||
|
category: course.category,
|
||||||
|
level: course.level
|
||||||
|
};
|
||||||
|
onAddToCart(cartItem);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className={`flex-shrink-0 w-96 h-[560px] bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden hover:shadow-xl transition-all duration-300 cursor-pointer group flex flex-col ${className || ''}`}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{/* Course Image with Category Badge */}
|
||||||
|
<div className="relative aspect-[16/9] overflow-hidden flex-shrink-0">
|
||||||
|
<ImageWithFallback
|
||||||
|
src={course.thumbnail}
|
||||||
|
alt={course.title}
|
||||||
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||||
|
/>
|
||||||
|
<div className="absolute top-4 left-4">
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
className="px-3 py-1 font-medium"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'rgba(248, 195, 1, 0.95)',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
backdropFilter: 'blur(4px)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{course.category}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Card Content - Reduced horizontal padding */}
|
||||||
|
<div className="p-5 flex flex-col flex-1">
|
||||||
|
{/* Course Title */}
|
||||||
|
<h3
|
||||||
|
className="mb-3 group-hover:text-blue-600 transition-colors leading-snug"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-h4)',
|
||||||
|
fontWeight: 'var(--font-weight-h4)',
|
||||||
|
lineHeight: '1.3',
|
||||||
|
color: 'var(--color-black)',
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{course.title}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{/* Course Description - Limited to 2 lines with ellipsis */}
|
||||||
|
<p
|
||||||
|
className="mb-5 line-clamp-2 leading-relaxed"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
lineHeight: '1.5',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
display: '-webkit-box',
|
||||||
|
WebkitLineClamp: 2,
|
||||||
|
WebkitBoxOrient: 'vertical',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{course.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Course Meta Information - Reduced bottom margin */}
|
||||||
|
<div className="flex items-center justify-between mb-5 pt-3 border-t border-gray-100">
|
||||||
|
<div className="flex items-center gap-5">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Clock className="w-4 h-4 text-gray-400" />
|
||||||
|
<span style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontWeight: '500'
|
||||||
|
}}>
|
||||||
|
{course.duration}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Users className="w-4 h-4 text-gray-400" />
|
||||||
|
<span style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
color: 'var(--color-gray-muted)',
|
||||||
|
fontWeight: '500'
|
||||||
|
}}>
|
||||||
|
{course.reviews}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Star className="w-4 h-4 fill-current text-yellow-400" />
|
||||||
|
<span
|
||||||
|
className="font-semibold"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-small)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
color: 'var(--color-black)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{course.rating}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Pricing Section - Reduced bottom margin */}
|
||||||
|
<div className="mb-5">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-baseline gap-3">
|
||||||
|
<span
|
||||||
|
className="font-bold"
|
||||||
|
style={{
|
||||||
|
fontSize: '1.75rem',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
color: '#04045B'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{course.price}
|
||||||
|
</span>
|
||||||
|
{course.originalPrice && (
|
||||||
|
<span
|
||||||
|
className="line-through"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
color: 'var(--color-gray-muted)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{course.originalPrice}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{/* {course.originalPrice && (
|
||||||
|
<div className="text-right">
|
||||||
|
<span
|
||||||
|
className="text-green-600 font-semibold text-sm"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'var(--font-family-base)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save {Math.round(((parseFloat(course.originalPrice.replace('$', '')) - parseFloat(course.price.replace('$', ''))) / parseFloat(course.originalPrice.replace('$', ''))) * 100)}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)} */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action Buttons - Horizontal Layout with reduced gap */}
|
||||||
|
<div className="flex flex-row gap-2 mt-auto">
|
||||||
|
{/* Add to Cart */}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleAddToCart}
|
||||||
|
className="flex-1 flex items-center justify-center gap-1.5 h-9 rounded-md transition-all duration-200 font-medium px-2"
|
||||||
|
style={{
|
||||||
|
borderColor: '#04045B',
|
||||||
|
color: '#04045B',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
fontSize: '12px', // ⬅️ reduced
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontWeight: '500',
|
||||||
|
borderWidth: '1px',
|
||||||
|
padding: '8px'
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e: any) => {
|
||||||
|
e.currentTarget.style.backgroundColor = '#04045B';
|
||||||
|
e.currentTarget.style.color = 'white';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e: any) => {
|
||||||
|
e.currentTarget.style.backgroundColor = 'transparent';
|
||||||
|
e.currentTarget.style.color = '#04045B';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ShoppingCart className="w-3.5 h-3.5" /> {/* ⬅️ smaller icon */}
|
||||||
|
Add to Cart
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Learn More */}
|
||||||
|
<Button
|
||||||
|
className="flex-1 flex items-center justify-center gap-1.5 h-9 rounded-md transition-all duration-200 font-medium px-2"
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#04045B',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: '12px', // ⬅️ reduced
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
fontWeight: '500',
|
||||||
|
border: 'none',
|
||||||
|
padding: '8px'
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e: any) => {
|
||||||
|
e.currentTarget.style.backgroundColor = '#030359';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e: any) => {
|
||||||
|
e.currentTarget.style.backgroundColor = '#04045B';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Learn More
|
||||||
|
<ArrowRight className="w-3.5 h-3.5" /> {/* ⬅️ smaller icon */}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
235
.gitea/workflows/src/components/ExpertiseSections.tsx
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Badge } from './ui/badge';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||||
|
import { navigateTo } from './Router';
|
||||||
|
import { CheckCircle, ArrowRight, Clock, Users, Star } from 'lucide-react';
|
||||||
|
|
||||||
|
// Service Card Component
|
||||||
|
export function ServiceCard({ service }) {
|
||||||
|
const IconComponent = service.icon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="border hover:shadow-lg transition-all duration-300 group h-full" style={{ backgroundColor: 'var(--color-bg-white)', borderColor: 'var(--color-border)' }}>
|
||||||
|
<CardHeader className="pb-4">
|
||||||
|
<div className="w-16 h-16 rounded-xl flex items-center justify-center mb-4 group-hover:scale-110 transition-transform" style={{ backgroundColor: 'var(--color-primary)' }}>
|
||||||
|
<IconComponent className="w-8 h-8 text-white" />
|
||||||
|
</div>
|
||||||
|
<CardTitle className="text-h4 mb-3">{service.title}</CardTitle>
|
||||||
|
<p className="text-body text-muted leading-relaxed">{service.description}</p>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent className="pt-0">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h4 className="text-subhead mb-3">Key Features:</h4>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{service.features.map((feature, index) => (
|
||||||
|
<div key={index} className="flex items-start gap-2">
|
||||||
|
<CheckCircle className="w-4 h-4 text-green-600 mt-0.5 flex-shrink-0" />
|
||||||
|
<span className="text-small text-muted">{feature}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-4 border-t" style={{ borderColor: 'var(--color-border)' }}>
|
||||||
|
<p className="text-small text-primary">
|
||||||
|
<strong>Outcome:</strong> {service.outcome}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Theme Card Component
|
||||||
|
export function ThemeCard({ theme }) {
|
||||||
|
const IconComponent = theme.icon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="border hover:shadow-md transition-all duration-300 text-center group" style={{ backgroundColor: 'var(--color-bg-white)', borderColor: 'var(--color-border)' }}>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className={`w-16 h-16 rounded-xl bg-gradient-to-r ${theme.color} flex items-center justify-center mx-auto mb-4 group-hover:scale-110 transition-transform`}>
|
||||||
|
<IconComponent className="w-8 h-8 text-white" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-subhead mb-2">{theme.title}</h3>
|
||||||
|
<p className="text-small text-muted">{theme.description}</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Program Card Component
|
||||||
|
export function ProgramCard({ program }) {
|
||||||
|
return (
|
||||||
|
<Card className="border hover:shadow-lg transition-all duration-300 h-full" style={{ backgroundColor: 'var(--color-bg-white)', borderColor: 'var(--color-border)' }}>
|
||||||
|
<CardHeader className="pb-4">
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
<Badge className="text-small px-3 py-1" style={{ backgroundColor: 'var(--color-primary)', color: 'white' }}>
|
||||||
|
{program.level}
|
||||||
|
</Badge>
|
||||||
|
<Badge variant="outline" className="text-small px-3 py-1">
|
||||||
|
{program.format}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CardTitle className="text-h4 mb-3">{program.title}</CardTitle>
|
||||||
|
<p className="text-body text-muted leading-relaxed">{program.description}</p>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent className="pt-0">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4 text-small">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Clock className="w-4 h-4 text-primary" />
|
||||||
|
<span>{program.duration}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Users className="w-4 h-4 text-primary" />
|
||||||
|
<span>{program.participants}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="text-subhead mb-3">Program Highlights:</h4>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{program.highlights.map((highlight, index) => (
|
||||||
|
<div key={index} className="flex items-start gap-2">
|
||||||
|
<CheckCircle className="w-4 h-4 text-green-600 mt-0.5 flex-shrink-0" />
|
||||||
|
<span className="text-small text-muted">{highlight}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced Feature Card Component for Platform Features
|
||||||
|
export function FeatureCard({ feature }) {
|
||||||
|
const IconComponent = feature.icon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="border border-gray-200 hover:border-primary/30 transition-all duration-300 shadow-sm hover:shadow-lg group h-full" style={{ backgroundColor: 'var(--color-bg-white)' }}>
|
||||||
|
<CardContent className="p-8">
|
||||||
|
{/* Feature Header */}
|
||||||
|
<div className="flex items-start gap-4 mb-6">
|
||||||
|
<div
|
||||||
|
className="w-14 h-14 rounded-xl flex items-center justify-center group-hover:scale-110 transition-transform duration-300 flex-shrink-0"
|
||||||
|
style={{ backgroundColor: 'rgba(4, 4, 91, 0.08)' }}
|
||||||
|
>
|
||||||
|
<IconComponent className="w-7 h-7 text-primary" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="text-h4 mb-3 text-black">{feature.title}</h3>
|
||||||
|
<p className="text-body text-muted leading-relaxed">{feature.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Feature List */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
{feature.features.map((item, itemIndex) => (
|
||||||
|
<div key={itemIndex} className="flex items-center gap-3">
|
||||||
|
<div
|
||||||
|
className="w-5 h-5 rounded-full flex items-center justify-center flex-shrink-0"
|
||||||
|
style={{ backgroundColor: 'var(--color-accent)' }}
|
||||||
|
>
|
||||||
|
<CheckCircle className="w-3 h-3 text-white" />
|
||||||
|
</div>
|
||||||
|
<span className="text-body text-muted">{item}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case Study Card Component
|
||||||
|
export function CaseStudyCard({ caseStudy }) {
|
||||||
|
return (
|
||||||
|
<Card className="border hover:shadow-lg transition-all duration-300 overflow-hidden group" style={{ backgroundColor: 'var(--color-bg-white)', borderColor: 'var(--color-border)' }}>
|
||||||
|
<div className="relative h-48">
|
||||||
|
<ImageWithFallback
|
||||||
|
src={caseStudy.image}
|
||||||
|
alt={caseStudy.title}
|
||||||
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||||
|
/>
|
||||||
|
<div className="absolute top-4 left-4">
|
||||||
|
<Badge className="text-small px-3 py-1" style={{ backgroundColor: 'var(--color-accent)', color: 'var(--color-black)' }}>
|
||||||
|
{caseStudy.industry}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<h3 className="text-h4 mb-3">{caseStudy.title}</h3>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h4 className="text-subhead mb-2">Challenge:</h4>
|
||||||
|
<p className="text-small text-muted">{caseStudy.challenge}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="text-subhead mb-2">Solution:</h4>
|
||||||
|
<p className="text-small text-muted">{caseStudy.solution}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="text-subhead mb-2">Results:</h4>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{caseStudy.results.map((result, index) => (
|
||||||
|
<div key={index} className="flex items-start gap-2">
|
||||||
|
<Star className="w-4 h-4 text-yellow-500 mt-0.5 flex-shrink-0" />
|
||||||
|
<span className="text-small text-muted">{result}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4 pt-2 text-small">
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<Clock className="w-4 h-4 text-primary" />
|
||||||
|
{caseStudy.duration}
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<Users className="w-4 h-4 text-primary" />
|
||||||
|
{caseStudy.participants} participants
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sticky Tabs Component
|
||||||
|
export function StickyTabs({ pillars, activeTab, onTabClick, isSticky }) {
|
||||||
|
return (
|
||||||
|
<div className={`bg-white border-b transition-all duration-300 ${isSticky ? 'fixed top-20 left-0 right-0 z-40 shadow-md' : 'relative'}`} style={{ borderColor: 'var(--color-border)' }}>
|
||||||
|
<nav className="section-margin-x mx-auto" aria-label="Section navigation">
|
||||||
|
{/* Mobile: compact dropdown */}
|
||||||
|
<div className="md:hidden py-3">
|
||||||
|
<label htmlFor="section-select" className="sr-only">Select section</label>
|
||||||
|
<select
|
||||||
|
id="section-select"
|
||||||
|
className="w-full border rounded-lg p-3 bg-white text-body focus-ring"
|
||||||
|
value={activeTab}
|
||||||
|
onChange={(e) => onTabClick(e.target.value)}
|
||||||
|
aria-controls="page-sections"
|
||||||
|
>
|
||||||
|
{pillars.map((pillar) => (
|
||||||
|
<option key={pillar.id} value={pillar.id}>{pillar.title}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
327
.gitea/workflows/src/components/Footer.tsx
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
import { Facebook, X, Linkedin, Instagram, User } from "lucide-react";
|
||||||
|
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||||
|
import { navigateTo } from './Router';
|
||||||
|
import klcLogo from 'figma:asset/e98caa8afd8d11246bbff1dde75bbaae6f6a0894.png';
|
||||||
|
|
||||||
|
export function Footer() {
|
||||||
|
return (
|
||||||
|
<footer
|
||||||
|
className="relative py-16"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-brand-primary)',
|
||||||
|
color: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Main Footer Content */}
|
||||||
|
<div className="max-w-7xl mx-auto section-margin-x">
|
||||||
|
<div className="flex flex-col lg:flex-row lg:justify-between lg:items-start gap-12 lg:gap-8">
|
||||||
|
|
||||||
|
{/* Logo and Description Column */}
|
||||||
|
<div className="lg:flex-shrink-0 lg:w-80">
|
||||||
|
{/* Logo */}
|
||||||
|
<div className="flex items-center mb-6">
|
||||||
|
<img
|
||||||
|
src={klcLogo}
|
||||||
|
alt="Kautilya Leadership Centre"
|
||||||
|
className="h-16 w-auto object-contain"
|
||||||
|
style={{ filter: 'brightness(0) invert(1)' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Company Description */}
|
||||||
|
<p
|
||||||
|
className="text-sm leading-relaxed mb-8"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.85)' }}
|
||||||
|
>
|
||||||
|
Empowering leaders with transformative development opportunities for achieving excellence, growth, and lasting impact.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{/* Social Media Icons */}
|
||||||
|
<div className="flex space-x-4">
|
||||||
|
{[
|
||||||
|
{ icon: X, label: 'X' },
|
||||||
|
{ icon: Facebook, label: 'Facebook' },
|
||||||
|
{ icon: Linkedin, label: 'LinkedIn' },
|
||||||
|
{ icon: Instagram, label: 'Instagram' }
|
||||||
|
].map(({ icon: Icon, label }) => (
|
||||||
|
<a
|
||||||
|
key={label}
|
||||||
|
href="#"
|
||||||
|
className="w-8 h-8 rounded-full flex items-center justify-center transition-all duration-300 group"
|
||||||
|
style={{ backgroundColor: 'rgba(192, 192, 192, 0.1)' }}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = 'var(--color-brand-accent)';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = 'rgba(192, 192, 192, 0.1)';
|
||||||
|
}}
|
||||||
|
aria-label={label}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
className="w-4 h-4 transition-colors duration-300"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.85)' }}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.color = 'var(--color-brand-black)';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = 'rgba(255, 255, 255, 0.85)';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Services */}
|
||||||
|
<div className="lg:w-auto lg:flex-shrink-0">
|
||||||
|
<h4
|
||||||
|
className="font-semibold mb-6 text-sm"
|
||||||
|
style={{ color: 'white' }}
|
||||||
|
>
|
||||||
|
Services
|
||||||
|
</h4>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{[
|
||||||
|
{ text: 'Leadership Development', href: '/services/leadership-development', badge: null },
|
||||||
|
{ text: 'Executive Coaching', href: '/services/executive-coaching', badge: null },
|
||||||
|
{ text: 'Management Development', href: '/services/management-development', badge: null },
|
||||||
|
{ text: 'Consulting Services', href: '/services/consulting', badge: null },
|
||||||
|
{ text: 'Culture & Competence', href: '/services/culture-competence', badge: null },
|
||||||
|
{ text: 'Learning Facility', href: '/services/learning-facility', badge: 'Popular' }
|
||||||
|
].map((item) => (
|
||||||
|
<div key={item.text} className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => navigateTo(item.href)}
|
||||||
|
className="text-sm transition-colors duration-300 cursor-pointer text-left"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.85)' }}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.color = 'white';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = 'rgba(255, 255, 255, 0.85)';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.text}
|
||||||
|
</button>
|
||||||
|
{item.badge && (
|
||||||
|
<span
|
||||||
|
className="px-2 py-0.5 text-xs rounded"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-brand-accent)',
|
||||||
|
color: 'var(--color-brand-black)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.badge}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Company */}
|
||||||
|
<div className="lg:w-auto lg:flex-shrink-0">
|
||||||
|
<h4
|
||||||
|
className="font-semibold mb-6 text-sm"
|
||||||
|
style={{ color: 'white' }}
|
||||||
|
>
|
||||||
|
About Us
|
||||||
|
</h4>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{[
|
||||||
|
{ text: 'Our vision', href: '/about/our-vision' },
|
||||||
|
{ text: 'Our Impact', href: '/about/our-impact' },
|
||||||
|
{ text: 'Our Expertise', href: '/about/our-expertise' }
|
||||||
|
].map((link) => (
|
||||||
|
<button
|
||||||
|
key={link.text}
|
||||||
|
onClick={() => navigateTo(link.href)}
|
||||||
|
className="block text-sm transition-colors duration-300 cursor-pointer text-left"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.85)' }}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.color = 'white';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = 'rgba(255, 255, 255, 0.85)';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{link.text}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Explore */}
|
||||||
|
<div className="lg:w-auto lg:flex-shrink-0">
|
||||||
|
<h4
|
||||||
|
className="font-semibold mb-6 text-sm"
|
||||||
|
style={{ color: 'white' }}
|
||||||
|
>
|
||||||
|
Explore
|
||||||
|
</h4>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{[
|
||||||
|
{ text: 'Learning facility', href: '/services/learning-facility' },
|
||||||
|
{ text: 'Online Courses', href: '/learning-online' },
|
||||||
|
{ text: 'Contact Us', href: '/contact' }
|
||||||
|
].map((link) => (
|
||||||
|
<button
|
||||||
|
key={link.text}
|
||||||
|
onClick={() => navigateTo(link.href)}
|
||||||
|
className="block text-sm transition-colors duration-300 cursor-pointer text-left"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.85)' }}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.color = 'white';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = 'rgba(255, 255, 255, 0.85)';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{link.text}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Resources */}
|
||||||
|
<div className="lg:w-auto lg:flex-shrink-0">
|
||||||
|
<h4
|
||||||
|
className="font-semibold mb-6 text-sm"
|
||||||
|
style={{ color: 'white' }}
|
||||||
|
>
|
||||||
|
Resources
|
||||||
|
</h4>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{[
|
||||||
|
{ text: 'Blog article', href: '/learning/articles' },
|
||||||
|
{ text: 'Webcast', href: '/learning/webcast' }
|
||||||
|
].map((link) => (
|
||||||
|
<button
|
||||||
|
key={link.text}
|
||||||
|
onClick={() => navigateTo(link.href)}
|
||||||
|
className="block text-sm transition-colors duration-300 cursor-pointer text-left"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.85)' }}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.color = 'white';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = 'rgba(255, 255, 255, 0.85)';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{link.text}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Back To Top Button - Kept in Resources section */}
|
||||||
|
<div className="pt-12">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2 text-sm transition-all duration-300 group"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.85)' }}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.color = 'var(--color-brand-accent)';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = 'rgba(255, 255, 255, 0.85)';
|
||||||
|
}}
|
||||||
|
aria-label="Back to top"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-4 h-4 transition-transform duration-300 group-hover:-translate-y-1"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M5 10l7-7m0 0l7 7m-7-7v18"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="transition-colors duration-300">
|
||||||
|
Back To Top
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Divider */}
|
||||||
|
<div
|
||||||
|
className="w-full h-px my-12"
|
||||||
|
style={{ backgroundColor: 'rgba(192, 192, 192, 0.2)' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Bottom Footer */}
|
||||||
|
<div className="flex flex-col lg:flex-row justify-between items-start lg:items-center space-y-6 lg:space-y-0">
|
||||||
|
|
||||||
|
{/* Copyright and Legal Links */}
|
||||||
|
<div className="flex flex-col lg:flex-row lg:items-center space-y-4 lg:space-y-0 lg:space-x-8">
|
||||||
|
<p
|
||||||
|
className="text-sm"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.85)' }}
|
||||||
|
>
|
||||||
|
Copyright 2025 Kautilya Leadership Centre
|
||||||
|
</p>
|
||||||
|
<div className="flex space-x-6">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="text-sm transition-colors duration-300"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.85)' }}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.color = 'var(--color-brand-accent)';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = 'rgba(255, 255, 255, 0.85)';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Privacy and Cookie Policy
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
onClick={() => navigateTo('/terms')}
|
||||||
|
className="text-sm transition-colors duration-300 cursor-pointer text-left"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.85)' }}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.color = 'var(--color-brand-accent)';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = 'rgba(255, 255, 255, 0.85)';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Terms of Service
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contact Info */}
|
||||||
|
<div className="text-right">
|
||||||
|
<p
|
||||||
|
className="text-sm font-medium"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.85)' }}
|
||||||
|
>
|
||||||
|
Follow Our Leadership Journey
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="text-xs"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.7)' }}
|
||||||
|
>
|
||||||
|
Discover leadership excellence with KLC
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
264
.gitea/workflows/src/components/FooterNew.tsx
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
import { ArrowUp, Facebook, X, Linkedin, Instagram } from "lucide-react";
|
||||||
|
import { navigateTo } from './Router';
|
||||||
|
import klcLogo from 'figma:asset/e98caa8afd8d11246bbff1dde75bbaae6f6a0894.png';
|
||||||
|
|
||||||
|
export function FooterNew() {
|
||||||
|
return (
|
||||||
|
<footer
|
||||||
|
className="relative py-16"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-brand-primary)',
|
||||||
|
color: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Main Footer Content */}
|
||||||
|
<div className="max-w-7xl mx-auto section-margin-x">
|
||||||
|
<div className="flex flex-col lg:flex-row lg:justify-between lg:items-start gap-12 lg:gap-16">
|
||||||
|
|
||||||
|
{/* Logo and Quick Links Column */}
|
||||||
|
<div className="lg:flex-shrink-0 lg:max-w-md">
|
||||||
|
{/* Logo */}
|
||||||
|
<div className="flex items-center mb-8">
|
||||||
|
<img
|
||||||
|
src={klcLogo}
|
||||||
|
alt="Kautilya Leadership Centre"
|
||||||
|
className="h-16 w-auto object-contain"
|
||||||
|
style={{ filter: 'brightness(0) invert(1)' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quick Links Section */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<h3 className="text-subhead-white mb-6 text-[16px]">
|
||||||
|
Quicklinks
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{/* Navigation Links */}
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 mb-8">
|
||||||
|
<button
|
||||||
|
onClick={() => navigateTo('/')}
|
||||||
|
className="block text-small-white transition-all duration-300 text-left"
|
||||||
|
style={{
|
||||||
|
color: 'white',
|
||||||
|
opacity: 0.5
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => e.currentTarget.style.opacity = '1'}
|
||||||
|
onMouseLeave={(e) => e.currentTarget.style.opacity = '0.5'}
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => navigateTo('/learning/webcast')}
|
||||||
|
className="block text-small-white transition-all duration-300 text-left"
|
||||||
|
style={{
|
||||||
|
color: 'white',
|
||||||
|
opacity: 0.5
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => e.currentTarget.style.opacity = '1'}
|
||||||
|
onMouseLeave={(e) => e.currentTarget.style.opacity = '0.5'}
|
||||||
|
>
|
||||||
|
Webcast
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => navigateTo('/about-us')}
|
||||||
|
className="block text-small-white transition-all duration-300 text-left"
|
||||||
|
style={{
|
||||||
|
color: 'white',
|
||||||
|
opacity: 0.5
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => e.currentTarget.style.opacity = '1'}
|
||||||
|
onMouseLeave={(e) => e.currentTarget.style.opacity = '0.5'}
|
||||||
|
>
|
||||||
|
About Us
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => navigateTo('/learning/articles')}
|
||||||
|
className="block text-small-white transition-all duration-300 text-left"
|
||||||
|
style={{
|
||||||
|
color: 'white',
|
||||||
|
opacity: 0.5
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => e.currentTarget.style.opacity = '1'}
|
||||||
|
onMouseLeave={(e) => e.currentTarget.style.opacity = '0.5'}
|
||||||
|
>
|
||||||
|
Blogs
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => navigateTo('/services')}
|
||||||
|
className="block text-small-white transition-all duration-300 text-left"
|
||||||
|
style={{
|
||||||
|
color: 'white',
|
||||||
|
opacity: 0.5
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => e.currentTarget.style.opacity = '1'}
|
||||||
|
onMouseLeave={(e) => e.currentTarget.style.opacity = '0.5'}
|
||||||
|
>
|
||||||
|
Services
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => navigateTo('/faq')}
|
||||||
|
className="block text-small-white transition-all duration-300 text-left"
|
||||||
|
style={{
|
||||||
|
color: 'white',
|
||||||
|
opacity: 0.5
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => e.currentTarget.style.opacity = '1'}
|
||||||
|
onMouseLeave={(e) => e.currentTarget.style.opacity = '0.5'}
|
||||||
|
>
|
||||||
|
FAQs
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => navigateTo('/learning-facility')}
|
||||||
|
className="block text-small-white transition-all duration-300 text-left"
|
||||||
|
style={{
|
||||||
|
color: 'white',
|
||||||
|
opacity: 0.5
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => e.currentTarget.style.opacity = '1'}
|
||||||
|
onMouseLeave={(e) => e.currentTarget.style.opacity = '0.5'}
|
||||||
|
>
|
||||||
|
Learning facility
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => navigateTo('/learning-online')}
|
||||||
|
className="block text-small-white transition-all duration-300 text-left"
|
||||||
|
style={{
|
||||||
|
color: 'white',
|
||||||
|
opacity: 0.5
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => e.currentTarget.style.opacity = '1'}
|
||||||
|
onMouseLeave={(e) => e.currentTarget.style.opacity = '0.5'}
|
||||||
|
>
|
||||||
|
Online courses
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* DISCOVER LEADERSHIP Column */}
|
||||||
|
<div className="lg:flex-shrink-0 lg:max-w-3xl flex flex-col h-full">
|
||||||
|
{/* Discover Leadership Header */}
|
||||||
|
<h2 className="text-h3-white mb-6 text-[32px] text-[24px]">
|
||||||
|
DISCOVER LEADERSHIP
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{/* Company Description */}
|
||||||
|
<p
|
||||||
|
className="text-small-white leading-relaxed mb-6"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.85)' }}
|
||||||
|
>
|
||||||
|
Kautilya Leadership Centre is dedicated to building a world-class organisation, in thought and practice of leadership. We help people gain insights into their leadership orientations and enable them on a journey towards leadership development. This online platform allows a wide range of learning experiences, exposure to leadership thinking and concepts through online tools such as modules, webcasts of leaders, webinars, one-on-one consulting sessions.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p
|
||||||
|
className="text-small-white leading-relaxed mb-6"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.85)' }}
|
||||||
|
>
|
||||||
|
Kautilya Leadership Centre also offers access to Kautilya Leadership Services, which is a learning space developed to discover, deliberate, reflect on one's capabilities and developments. This learning centre built over 2 acres is equipped with world-class facilities to help ease learning and development.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Learning Journey Text */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<p
|
||||||
|
className="text-small-white"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.75)' }}
|
||||||
|
>
|
||||||
|
Become Aware | Gain Insights | Discover Leadership
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Back To Top Button - Positioned to Bottom Right */}
|
||||||
|
<div className="flex-1 flex justify-end items-end">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="group flex items-center gap-3 px-6 py-3 rounded-lg transition-all duration-300"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-brand-accent)',
|
||||||
|
color: 'var(--color-brand-black)'
|
||||||
|
}}
|
||||||
|
aria-label="Back to top"
|
||||||
|
>
|
||||||
|
<ArrowUp className="w-5 h-5 transition-transform duration-300 group-hover:-translate-y-1" />
|
||||||
|
<span className="font-semibold">BACK TO TOP</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom Footer */}
|
||||||
|
<div className="flex flex-col lg:flex-row justify-between items-start lg:items-center space-y-6 lg:space-y-0 mt-16 pt-8"
|
||||||
|
style={{ borderTop: '1px solid rgba(255, 255, 255, 0.2)' }}>
|
||||||
|
|
||||||
|
{/* Social Media Icons - Moved from left column */}
|
||||||
|
<div className="flex space-x-4">
|
||||||
|
{[
|
||||||
|
{ icon: X, label: 'X' },
|
||||||
|
{ icon: Facebook, label: 'Facebook' },
|
||||||
|
{ icon: Linkedin, label: 'LinkedIn' },
|
||||||
|
{ icon: Instagram, label: 'Instagram' }
|
||||||
|
].map(({ icon: Icon, label }) => (
|
||||||
|
<a
|
||||||
|
key={label}
|
||||||
|
href="#"
|
||||||
|
className="w-8 h-8 rounded-full flex items-center justify-center transition-all duration-300 group"
|
||||||
|
style={{ backgroundColor: 'rgba(192, 192, 192, 0.1)' }}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = 'var(--color-brand-accent)';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = 'rgba(192, 192, 192, 0.1)';
|
||||||
|
}}
|
||||||
|
aria-label={label}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
className="w-4 h-4 transition-colors duration-300"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.85)' }}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.color = 'var(--color-brand-black)';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = 'rgba(255, 255, 255, 0.85)';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Copyright - Centered */}
|
||||||
|
<div className="flex-1 flex justify-center lg:absolute lg:left-1/2 lg:transform lg:-translate-x-1/2">
|
||||||
|
<p
|
||||||
|
className="text-body-white text-center"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.85)' }}
|
||||||
|
>
|
||||||
|
Copyright 2025 Kautilya Leadership Centre
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Legal Links */}
|
||||||
|
<div className="flex flex-col lg:flex-row lg:items-center space-y-2 lg:space-y-0 lg:space-x-8">
|
||||||
|
<button
|
||||||
|
onClick={() => navigateTo('/privacy-policy')}
|
||||||
|
className="text-body-white transition-colors duration-300 hover:text-yellow-300 text-left cursor-pointer"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.85)' }}
|
||||||
|
>
|
||||||
|
Privacy and Cookie Policy
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => navigateTo('/term-condition')}
|
||||||
|
className="text-body-white transition-colors duration-300 cursor-pointer text-left hover:text-yellow-300"
|
||||||
|
style={{ color: 'rgba(255, 255, 255, 0.85)' }}
|
||||||
|
>
|
||||||
|
Terms of Service
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
.gitea/workflows/src/components/FullScreenLoader.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Loader } from "./Loader";
|
||||||
|
|
||||||
|
interface FullScreenLoaderProps {
|
||||||
|
text?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FullScreenLoader: React.FC<FullScreenLoaderProps> = ({
|
||||||
|
text = "Loading...",
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 flex flex-col items-center justify-center bg-white/100 z-[9999]">
|
||||||
|
<Loader />
|
||||||
|
|
||||||
|
{text && (
|
||||||
|
<p className="mt-6 text-lg text-gray-600">
|
||||||
|
{text}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
226
.gitea/workflows/src/components/HeroSection.tsx
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||||
|
import { navigateTo } from "./Router";
|
||||||
|
import PrimaryCTAButton from "./PrimaryCTAButton";
|
||||||
|
|
||||||
|
interface HeroSectionItem {
|
||||||
|
id: string;
|
||||||
|
headline: string;
|
||||||
|
subtext: string;
|
||||||
|
background_image_url: string;
|
||||||
|
cta_text: string;
|
||||||
|
cta_destination: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SlideData {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
backgroundImage: string;
|
||||||
|
shortTitle: string;
|
||||||
|
ctaText: string;
|
||||||
|
route: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HeroSectionProps {
|
||||||
|
heroSections: HeroSectionItem[];
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function HeroSection({
|
||||||
|
heroSections,
|
||||||
|
isLoading,
|
||||||
|
}: HeroSectionProps) {
|
||||||
|
const slides: SlideData[] = heroSections.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
title: item.headline,
|
||||||
|
backgroundImage: item.background_image_url,
|
||||||
|
shortTitle: item.subtext,
|
||||||
|
ctaText: item.cta_text,
|
||||||
|
route: item.cta_destination,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const totalSlides = slides.length;
|
||||||
|
|
||||||
|
const [currentSlide, setCurrentSlide] = useState(0);
|
||||||
|
const [isAutoPlaying, setIsAutoPlaying] = useState(true);
|
||||||
|
const [progressValues, setProgressValues] = useState<number[]>([]);
|
||||||
|
|
||||||
|
const slideDuration = 5000;
|
||||||
|
|
||||||
|
/* Initialize progress array when slides load */
|
||||||
|
useEffect(() => {
|
||||||
|
if (totalSlides > 0) {
|
||||||
|
setProgressValues(new Array(totalSlides).fill(0));
|
||||||
|
}
|
||||||
|
}, [totalSlides]);
|
||||||
|
|
||||||
|
/* Auto slide */
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isAutoPlaying || totalSlides === 0) return;
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setCurrentSlide((prev) => (prev + 1) % totalSlides);
|
||||||
|
}, slideDuration);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [isAutoPlaying, totalSlides]);
|
||||||
|
|
||||||
|
/* Progress animation */
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isAutoPlaying || totalSlides === 0) return;
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setProgressValues((prev) => {
|
||||||
|
const newProgress = [...prev];
|
||||||
|
|
||||||
|
newProgress[currentSlide] = Math.min(
|
||||||
|
newProgress[currentSlide] + 100 / (slideDuration / 100),
|
||||||
|
100
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newProgress[currentSlide] >= 100) {
|
||||||
|
newProgress[currentSlide] = 0;
|
||||||
|
|
||||||
|
newProgress.forEach((_, index) => {
|
||||||
|
if (index !== currentSlide) newProgress[index] = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return newProgress;
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [currentSlide, isAutoPlaying, totalSlides]);
|
||||||
|
|
||||||
|
/* Reset progress when slide changes */
|
||||||
|
useEffect(() => {
|
||||||
|
setProgressValues(new Array(totalSlides).fill(0));
|
||||||
|
}, [currentSlide, totalSlides]);
|
||||||
|
|
||||||
|
const goToSlide = useCallback(
|
||||||
|
(slideIndex: number) => {
|
||||||
|
if (slideIndex !== currentSlide) {
|
||||||
|
setCurrentSlide(slideIndex);
|
||||||
|
setIsAutoPlaying(false);
|
||||||
|
|
||||||
|
setTimeout(() => setIsAutoPlaying(true), 3000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[currentSlide]
|
||||||
|
);
|
||||||
|
|
||||||
|
const nextSlide = useCallback(() => {
|
||||||
|
const next = (currentSlide + 1) % totalSlides;
|
||||||
|
goToSlide(next);
|
||||||
|
}, [currentSlide, totalSlides, goToSlide]);
|
||||||
|
|
||||||
|
const prevSlide = useCallback(() => {
|
||||||
|
const prev = (currentSlide - 1 + totalSlides) % totalSlides;
|
||||||
|
goToSlide(prev);
|
||||||
|
}, [currentSlide, totalSlides, goToSlide]);
|
||||||
|
|
||||||
|
const handleMouseEnter = () => setIsAutoPlaying(false);
|
||||||
|
const handleMouseLeave = () => setIsAutoPlaying(true);
|
||||||
|
|
||||||
|
if (isLoading || slides.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
className="hero-section"
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
>
|
||||||
|
{/* Background Slides */}
|
||||||
|
{slides.map((slide, index) => (
|
||||||
|
<div
|
||||||
|
key={slide.id}
|
||||||
|
className={`hero-slide ${index === currentSlide ? "active" : ""}`}
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url('${slide.backgroundImage}')`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="hero-overlay" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Hero Content */}
|
||||||
|
<div className="hero-content">
|
||||||
|
<div className="hero-text-section">
|
||||||
|
<h1 className="hero-title" style={{ whiteSpace: "pre-line" }}>
|
||||||
|
{slides[currentSlide].title}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<PrimaryCTAButton
|
||||||
|
text={slides[currentSlide].ctaText}
|
||||||
|
onClick={() => navigateTo(slides[currentSlide].route)}
|
||||||
|
ariaLabel="Learn more about KLC"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom Navigation */}
|
||||||
|
<div className="hero-navigation">
|
||||||
|
<div className="hero-progress-container">
|
||||||
|
{slides.map((slide, index) => (
|
||||||
|
<div
|
||||||
|
key={slide.id}
|
||||||
|
className="hero-progress-item"
|
||||||
|
onClick={() => goToSlide(index)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`hero-progress-segment ${
|
||||||
|
index === currentSlide ? "active" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="hero-progress-fill"
|
||||||
|
style={{
|
||||||
|
width:
|
||||||
|
index === currentSlide
|
||||||
|
? `${progressValues[index] ?? 0}%`
|
||||||
|
: "0%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`hero-progress-number ${
|
||||||
|
index === currentSlide ? "active" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{String(index + 1).padStart(2, "0")}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`hero-progress-text ${
|
||||||
|
index === currentSlide ? "active" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{slide.shortTitle}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="hero-controls">
|
||||||
|
<button
|
||||||
|
className="hero-nav-button"
|
||||||
|
onClick={prevSlide}
|
||||||
|
aria-label="Previous slide"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="hero-nav-button"
|
||||||
|
onClick={nextSlide}
|
||||||
|
aria-label="Next slide"
|
||||||
|
>
|
||||||
|
<ChevronRight className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
363
.gitea/workflows/src/components/InsightsSection.tsx
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
import { ArrowUpRight } from "lucide-react";
|
||||||
|
import { ImageWithFallback } from "./figma/ImageWithFallback";
|
||||||
|
import { BrandedTag } from "./about/BrandedTag";
|
||||||
|
import { StandardCTAButton } from "./StandardCTAButton";
|
||||||
|
import { navigateTo } from "./Router";
|
||||||
|
import { useGetFeaturedBlogsQuery } from "../redux/services/homepageApi";
|
||||||
|
import { FullScreenLoader } from "./FullScreenLoader";
|
||||||
|
import { getSlugWithId } from "../utils/urlHelpers";
|
||||||
|
|
||||||
|
// Interface for featured blog items from API
|
||||||
|
interface FeaturedBlog {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
short_description: string | null;
|
||||||
|
slug_name: string;
|
||||||
|
banner_img: string | null;
|
||||||
|
updated_at: string;
|
||||||
|
content_category: string;
|
||||||
|
content_type: string;
|
||||||
|
tags: string[];
|
||||||
|
featured: boolean;
|
||||||
|
featured_order: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insight Card Component Interface
|
||||||
|
interface InsightCardData {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
date: string;
|
||||||
|
tags: string[];
|
||||||
|
image: string;
|
||||||
|
slug: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insight Tag Component
|
||||||
|
function InsightTag({ text }: { text: string }) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className="px-3 py-1 text-sm font-medium rounded-full"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-brand-accent)',
|
||||||
|
color: 'var(--color-brand-black)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arrow Icon Component
|
||||||
|
function ArrowIcon() {
|
||||||
|
return (
|
||||||
|
<div className="p-2 rounded-full transition-all duration-300 group-hover:bg-gray-100"
|
||||||
|
style={{ backgroundColor: 'rgba(0, 0, 0, 0.05)' }}>
|
||||||
|
<ArrowUpRight className="w-4 h-4" style={{ color: 'var(--color-brand-black)' }} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calendar Icon Component
|
||||||
|
function CalendarIcon() {
|
||||||
|
return (
|
||||||
|
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explore All Button Component
|
||||||
|
function ExploreAllButton() {
|
||||||
|
return (
|
||||||
|
<StandardCTAButton
|
||||||
|
text="Explore All"
|
||||||
|
onClick={() => navigateTo('/learning/articles')}
|
||||||
|
ariaLabel="Explore all leadership insights and ideas"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Large Insight Card Component
|
||||||
|
function LargeInsightCard({ card }: { card: InsightCardData }) {
|
||||||
|
const handleClick = () => {
|
||||||
|
if (card.slug && card.id) {
|
||||||
|
const url = getSlugWithId(card.slug, card.id);
|
||||||
|
navigateTo(`/learning/articles/${url}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasLink = !!(card.slug && card.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`relative h-[500px] rounded-xl overflow-hidden group ${hasLink ? 'cursor-pointer' : ''}`}
|
||||||
|
onClick={hasLink ? handleClick : undefined}
|
||||||
|
role={hasLink ? 'button' : undefined}
|
||||||
|
tabIndex={hasLink ? 0 : undefined}
|
||||||
|
onKeyDown={hasLink ? (e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
handleClick();
|
||||||
|
}
|
||||||
|
} : undefined}
|
||||||
|
>
|
||||||
|
{/* Background Image */}
|
||||||
|
<div className="absolute inset-0 transition-transform duration-300 group-hover:scale-105">
|
||||||
|
<ImageWithFallback
|
||||||
|
src={card.image}
|
||||||
|
alt={card.title}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* White Content Box */}
|
||||||
|
<div className="insight-card-white-box absolute bottom-6 left-6 right-6 bg-white rounded-xl p-6 shadow-lg transition-all duration-300 group-hover:shadow-xl">
|
||||||
|
{/* Top section with tags and arrow */}
|
||||||
|
<div className="insight-card-header flex items-start justify-between mb-4">
|
||||||
|
<div className="insight-card-tags flex gap-2 flex-wrap">
|
||||||
|
{card.tags.slice(0, 2).map((tag, index) => (
|
||||||
|
<InsightTag key={index} text={tag} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<ArrowIcon />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom section with content and date */}
|
||||||
|
<div className="insight-card-footer">
|
||||||
|
<div className="insight-card-text-content mb-4">
|
||||||
|
<h3 className="insight-card-title text-xl font-bold mb-3 leading-tight"
|
||||||
|
style={{ color: 'var(--color-brand-black)' }}>
|
||||||
|
{card.title}
|
||||||
|
</h3>
|
||||||
|
{card.description && (
|
||||||
|
<p className="insight-card-description text-gray-600 text-sm leading-relaxed">
|
||||||
|
{card.description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="insight-card-date flex items-center gap-2 text-gray-500">
|
||||||
|
<CalendarIcon />
|
||||||
|
<span className="insight-card-date-text text-sm font-medium">{card.date}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small Insight Card Component
|
||||||
|
function SmallInsightCard({ card }: { card: InsightCardData }) {
|
||||||
|
const handleClick = () => {
|
||||||
|
if (card.slug && card.id) {
|
||||||
|
const url = getSlugWithId(card.slug, card.id);
|
||||||
|
navigateTo(`/learning/articles/${url}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasLink = !!(card.slug && card.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`relative h-[235px] rounded-xl overflow-hidden group ${hasLink ? 'cursor-pointer' : ''}`}
|
||||||
|
onClick={hasLink ? handleClick : undefined}
|
||||||
|
role={hasLink ? 'button' : undefined}
|
||||||
|
tabIndex={hasLink ? 0 : undefined}
|
||||||
|
onKeyDown={hasLink ? (e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
handleClick();
|
||||||
|
}
|
||||||
|
} : undefined}
|
||||||
|
>
|
||||||
|
{/* Background Image */}
|
||||||
|
<div className="absolute inset-0 transition-transform duration-300 group-hover:scale-105">
|
||||||
|
<ImageWithFallback
|
||||||
|
src={card.image}
|
||||||
|
alt={card.title}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* White Content Box */}
|
||||||
|
<div className="insight-card-white-box absolute bottom-4 left-4 right-4 bg-white rounded-xl p-4 shadow-lg transition-all duration-300 group-hover:shadow-xl">
|
||||||
|
{/* Top section with tags and arrow */}
|
||||||
|
<div className="insight-card-header flex items-start justify-between mb-3">
|
||||||
|
<div className="insight-card-tags flex gap-2 flex-wrap">
|
||||||
|
{card.tags.slice(0, 2).map((tag, index) => (
|
||||||
|
<span
|
||||||
|
key={index}
|
||||||
|
className="px-2.5 py-1 text-xs font-medium rounded-full"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-brand-accent)',
|
||||||
|
color: 'var(--color-brand-black)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="p-1.5 rounded-full transition-all duration-300 group-hover:bg-gray-100"
|
||||||
|
style={{ backgroundColor: 'rgba(0, 0, 0, 0.05)' }}>
|
||||||
|
<ArrowUpRight className="w-3.5 h-3.5" style={{ color: 'var(--color-brand-black)' }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom section with content and date */}
|
||||||
|
<div className="insight-card-footer">
|
||||||
|
<div className="insight-card-text-content mb-3">
|
||||||
|
<h3 className="insight-card-title text-base font-bold leading-tight"
|
||||||
|
style={{ color: 'var(--color-brand-black)' }}>
|
||||||
|
{card.title}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="insight-card-date flex items-center gap-2 text-gray-500">
|
||||||
|
<CalendarIcon />
|
||||||
|
<span className="insight-card-date-text text-sm font-medium">{card.date}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format date function
|
||||||
|
const formatDate = (dateString: string) => {
|
||||||
|
return new Date(dateString).toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export function InsightsSection() {
|
||||||
|
// Fetch featured blogs from API
|
||||||
|
const { data: featuredBlogs, isLoading, isError } = useGetFeaturedBlogsQuery({ limit: 3 });
|
||||||
|
|
||||||
|
// Transform API data to match InsightCardData format
|
||||||
|
const transformToInsightCard = (blog: FeaturedBlog): InsightCardData => ({
|
||||||
|
id: blog.id,
|
||||||
|
title: blog.title,
|
||||||
|
description: blog.short_description || undefined,
|
||||||
|
date: formatDate(blog.updated_at),
|
||||||
|
tags: blog.tags || [],
|
||||||
|
image: blog.banner_img || 'https://images.unsplash.com/photo-1481627834876-b7833e8f5570?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&w=1080',
|
||||||
|
slug: blog.slug_name,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle loading state
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
className="py-24"
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#F7F7FD',
|
||||||
|
paddingTop: '8.75rem',
|
||||||
|
paddingBottom: '8.75rem'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="max-w-7xl mx-auto section-margin-x">
|
||||||
|
<div className="flex items-center justify-center min-h-[400px]">
|
||||||
|
<FullScreenLoader text="Loading insights..." />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle error or no data state
|
||||||
|
if (isError || !featuredBlogs || featuredBlogs.length === 0) {
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
className="py-24"
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#F7F7FD',
|
||||||
|
paddingTop: '8.75rem',
|
||||||
|
paddingBottom: '8.75rem'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="max-w-7xl mx-auto section-margin-x">
|
||||||
|
<BrandedTag text="Leadership Insights" />
|
||||||
|
|
||||||
|
<div className="insights-container">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="insights-header flex flex-col lg:flex-row lg:items-center lg:justify-between mb-16 gap-8 text-center lg:text-left">
|
||||||
|
<h2
|
||||||
|
className="insights-title text-3xl md:text-4xl lg:text-5xl font-bold leading-tight"
|
||||||
|
style={{ color: 'var(--color-brand-black)' }}
|
||||||
|
>
|
||||||
|
Leadership Insights & Ideas
|
||||||
|
</h2>
|
||||||
|
<ExploreAllButton />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Show message when no featured blogs available */}
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<p className="text-gray-600">No featured insights available at the moment. Check back soon!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we have at least 3 blogs for the layout (use placeholders if needed)
|
||||||
|
const cards = featuredBlogs.map(transformToInsightCard);
|
||||||
|
|
||||||
|
// The layout expects: first card (large) + two small cards
|
||||||
|
const largeCard = cards[0];
|
||||||
|
const smallCards = cards.slice(1, 3);
|
||||||
|
|
||||||
|
// If we don't have enough cards, we can still render with what we have
|
||||||
|
const hasLargeCard = !!largeCard;
|
||||||
|
const hasSmallCards = smallCards.length > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
className="py-24"
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#F7F7FD',
|
||||||
|
paddingTop: '8.75rem',
|
||||||
|
paddingBottom: '8.75rem'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="max-w-7xl mx-auto section-margin-x">
|
||||||
|
<BrandedTag text="Leadership Insights" />
|
||||||
|
|
||||||
|
<div className="insights-container">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="insights-header flex flex-col lg:flex-row lg:items-center lg:justify-between mb-16 gap-8 text-center lg:text-left">
|
||||||
|
<h2
|
||||||
|
className="insights-title text-3xl md:text-4xl lg:text-5xl font-bold leading-tight"
|
||||||
|
style={{ color: 'var(--color-brand-black)' }}
|
||||||
|
>
|
||||||
|
Leadership Insights & Ideas
|
||||||
|
</h2>
|
||||||
|
<ExploreAllButton />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Grid Layout */}
|
||||||
|
<div className="insights-grid grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6 lg:gap-8">
|
||||||
|
{/* Large Card - Takes full height on left */}
|
||||||
|
{hasLargeCard && (
|
||||||
|
<div className="lg:row-span-2">
|
||||||
|
<LargeInsightCard card={largeCard} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Small Cards - Stack on right */}
|
||||||
|
{hasSmallCards && (
|
||||||
|
<div className="flex flex-col gap-4 md:gap-6 lg:gap-8">
|
||||||
|
{smallCards.map((card:any, index:any) => (
|
||||||
|
<SmallInsightCard key={card.id || index} card={card} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
919
.gitea/workflows/src/components/KautilyaFacility.tsx
Normal file
@@ -0,0 +1,919 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Badge } from './ui/badge';
|
||||||
|
import { Card, CardContent } from './ui/card';
|
||||||
|
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||||
|
import { navigateTo } from './Router';
|
||||||
|
import { BrandedTag } from './about/BrandedTag';
|
||||||
|
import { PrimaryCTAButton } from './PrimaryCTAButton';
|
||||||
|
import { StandardCTAButton } from './StandardCTAButton';
|
||||||
|
import {
|
||||||
|
ArrowRight,
|
||||||
|
CheckCircle,
|
||||||
|
Settings,
|
||||||
|
Calendar,
|
||||||
|
Download,
|
||||||
|
Network,
|
||||||
|
Users,
|
||||||
|
Target,
|
||||||
|
Brain,
|
||||||
|
Eye,
|
||||||
|
TrendingUp,
|
||||||
|
BarChart3,
|
||||||
|
Award,
|
||||||
|
Lightbulb,
|
||||||
|
Shield,
|
||||||
|
ChevronDown,
|
||||||
|
ChevronUp,
|
||||||
|
ArrowLeft,
|
||||||
|
Star,
|
||||||
|
Zap,
|
||||||
|
Globe,
|
||||||
|
Clock,
|
||||||
|
BookOpen,
|
||||||
|
MessageCircle,
|
||||||
|
Building,
|
||||||
|
Heart,
|
||||||
|
Compass,
|
||||||
|
User,
|
||||||
|
UserCheck,
|
||||||
|
Home,
|
||||||
|
Car,
|
||||||
|
Coffee,
|
||||||
|
Wifi
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { TestimonialsSection } from './TestimonialsSection';
|
||||||
|
import { FullScreenLoader } from './FullScreenLoader';
|
||||||
|
import { useGetServiceListQuery } from '../redux/services/sercicesApi';
|
||||||
|
|
||||||
|
// Types based on API response (same as LeadershipDevelopment)
|
||||||
|
interface ServicePageData {
|
||||||
|
hero_section: {
|
||||||
|
id: string;
|
||||||
|
landing_page_type: string;
|
||||||
|
background_image_url: string;
|
||||||
|
background_image_alt_text: string;
|
||||||
|
headline: string;
|
||||||
|
subtext: string;
|
||||||
|
cta_text: string;
|
||||||
|
cta_destination: string;
|
||||||
|
};
|
||||||
|
overview: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
highlight_text: string;
|
||||||
|
overview_cards: Array<{
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
icon_url: string;
|
||||||
|
accessible_label: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
audience_section: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
audience_cards: Array<{
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
icon_url: string;
|
||||||
|
accessible_label: string;
|
||||||
|
challenges: string[];
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
use_case_section: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
use_case_cards: Array<{
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
icon_url: string;
|
||||||
|
accessible_label: string;
|
||||||
|
highlight_text: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
approach_section: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
approach_cards: Array<{
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
icon_url: string;
|
||||||
|
accessible_label: string;
|
||||||
|
bullets: string[];
|
||||||
|
}>;
|
||||||
|
outcomes: Array<{
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
icon_url: string;
|
||||||
|
accessible_label: string;
|
||||||
|
bullets: string[];
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
stats_section: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
stat_cards: Array<{
|
||||||
|
id: string;
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
icon_url: string;
|
||||||
|
accessible_label: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
program_section: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
program_phases: Array<{
|
||||||
|
phase: {
|
||||||
|
id: string;
|
||||||
|
phase_number: number;
|
||||||
|
title: string;
|
||||||
|
duration: string;
|
||||||
|
};
|
||||||
|
activities: Array<{
|
||||||
|
id: string;
|
||||||
|
phase_id: string;
|
||||||
|
text: string;
|
||||||
|
}>;
|
||||||
|
outcomes: Array<{
|
||||||
|
id: string;
|
||||||
|
phase_id: string;
|
||||||
|
text: string;
|
||||||
|
}>;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
impact_section: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
impact_stats: Array<{
|
||||||
|
id: string;
|
||||||
|
value: string;
|
||||||
|
description: string;
|
||||||
|
label: string;
|
||||||
|
icon_url: string;
|
||||||
|
accessible_label: string;
|
||||||
|
}>;
|
||||||
|
impact_benefits: Array<{
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
icon_url: string;
|
||||||
|
accessible_label: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
testimonial_section: Array<{
|
||||||
|
id: string;
|
||||||
|
profile_xid: string;
|
||||||
|
name: string;
|
||||||
|
designation: string;
|
||||||
|
content: string;
|
||||||
|
video_url: string | null;
|
||||||
|
display_order: number;
|
||||||
|
}>;
|
||||||
|
cta_section: {
|
||||||
|
id: string;
|
||||||
|
background_image_url: string;
|
||||||
|
text: string;
|
||||||
|
cta_text: string;
|
||||||
|
cta_destination: string;
|
||||||
|
description: string;
|
||||||
|
landing_page_type: string;
|
||||||
|
service_type: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map API icons to Lucide icons (same mapping as LeadershipDevelopment)
|
||||||
|
const getIconComponent = (iconUrl: string) => {
|
||||||
|
const iconMap: Record<string, any> = {
|
||||||
|
'/icons/building.svg': Building,
|
||||||
|
'/icons/home.svg': Home,
|
||||||
|
'/icons/hotel.svg': Building,
|
||||||
|
'/icons/coffee.svg': Coffee,
|
||||||
|
'/icons/wifi.svg': Wifi,
|
||||||
|
'/icons/settings.svg': Settings,
|
||||||
|
'/icons/user-check.svg': UserCheck,
|
||||||
|
'/icons/heart.svg': Heart,
|
||||||
|
'/icons/trending-up.svg': TrendingUp,
|
||||||
|
'/icons/users.svg': Users,
|
||||||
|
'/icons/target.svg': Target,
|
||||||
|
'/icons/star.svg': Star,
|
||||||
|
'/icons/compass.svg': Compass,
|
||||||
|
'/icons/book-open.svg': BookOpen,
|
||||||
|
'/icons/message-circle.svg': MessageCircle,
|
||||||
|
'/icons/clock.svg': Clock,
|
||||||
|
};
|
||||||
|
|
||||||
|
const IconComponent = iconMap[iconUrl] || Building;
|
||||||
|
return IconComponent;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function KautilyaFacility() {
|
||||||
|
const [expandedUseCase, setExpandedUseCase] = useState<number | null>(null);
|
||||||
|
const [expandedFeature, setExpandedFeature] = useState<number | null>(0);
|
||||||
|
|
||||||
|
// API call with service_type = 'kautilya_facility'
|
||||||
|
const { data: apiResponse, isLoading, error } = useGetServiceListQuery({
|
||||||
|
service_type: 'kautilya_facility'
|
||||||
|
});
|
||||||
|
|
||||||
|
const apiData = apiResponse?.data as ServicePageData | undefined;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-white">
|
||||||
|
<FullScreenLoader text="Loading Kautilya Facility..." />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error || !apiData) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center min-h-screen">
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-red-600">Error loading content. Please try again later.</p>
|
||||||
|
<Button onClick={() => window.location.reload()} className="mt-4">Retry</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform data for UI (using API data with fallbacks to static data)
|
||||||
|
const targetAudience = apiData.audience_section?.audience_cards.map(card => ({
|
||||||
|
title: card.title,
|
||||||
|
description: card.description,
|
||||||
|
icon: getIconComponent(card.icon_url),
|
||||||
|
challenges: card.challenges || []
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
const useCases = apiData.use_case_section?.use_case_cards.map(card => ({
|
||||||
|
title: card.title,
|
||||||
|
description: card.description,
|
||||||
|
icon: getIconComponent(card.icon_url),
|
||||||
|
scenario: card.highlight_text
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
const facilityFeatures = apiData.program_section?.program_phases.map(phase => ({
|
||||||
|
phase: phase.phase.title,
|
||||||
|
duration: phase.phase.duration,
|
||||||
|
activities: phase.activities.map(activity => activity.text),
|
||||||
|
deliverables: phase.outcomes.map(outcome => outcome.text)
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
const expectedOutcomes = apiData.impact_section?.impact_benefits.map(benefit => ({
|
||||||
|
category: benefit.title,
|
||||||
|
description: benefit.description,
|
||||||
|
icon: getIconComponent(benefit.icon_url)
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
const testimonials = apiData.testimonial_section?.map(testimonial => {
|
||||||
|
const designationParts = testimonial.designation.split(',');
|
||||||
|
const role = designationParts[0]?.trim() || '';
|
||||||
|
const company = designationParts[1]?.trim() || '';
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: parseInt(testimonial.id) || 0,
|
||||||
|
name: testimonial.name,
|
||||||
|
role: role,
|
||||||
|
company: company,
|
||||||
|
avatar: `https://ui-avatars.com/api/?name=${encodeURIComponent(testimonial.name)}&background=04045B&color=fff&size=128`,
|
||||||
|
quote: testimonial.content,
|
||||||
|
rating: 5,
|
||||||
|
isVideo: !!testimonial.video_url,
|
||||||
|
videoThumbnail: testimonial.video_url ? `/images/testimonials/thumbnails/${testimonial.id}.jpg` : undefined,
|
||||||
|
videoUrl: testimonial.video_url || undefined
|
||||||
|
};
|
||||||
|
}) || [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ backgroundColor: '#FFFFFF', fontFamily: 'var(--font-family-base)' }}>
|
||||||
|
{/* Hero Section */}
|
||||||
|
<section className="relative min-h-[85vh] flex flex-col">
|
||||||
|
<div className="absolute inset-0 z-0">
|
||||||
|
<div
|
||||||
|
className="w-full h-full bg-cover bg-center bg-no-repeat"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url('${apiData.hero_section.background_image_url}')`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-black/85 via-black/75 to-black/65"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative z-10 flex-1 flex items-center">
|
||||||
|
<div className="w-full section-margin-x">
|
||||||
|
<div className="max-w-4xl">
|
||||||
|
{/* Back Navigation */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => navigateTo('/services')}
|
||||||
|
className="text-white hover:text-white hover:bg-white/10 p-2 -ml-2"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||||
|
Back to Services
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-8">
|
||||||
|
<h1 className="text-h1-white">
|
||||||
|
{apiData.hero_section.headline}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-body-lg-white mb-8 max-w-3xl">
|
||||||
|
<strong>{apiData.hero_section.subtext}</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex justify-start">
|
||||||
|
<PrimaryCTAButton
|
||||||
|
text={apiData.hero_section.cta_text}
|
||||||
|
onClick={() => navigateTo(apiData.hero_section.cta_destination)}
|
||||||
|
ariaLabel={apiData.hero_section.cta_text}
|
||||||
|
className="primary-cta-button-blue cta-text-white"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 1. What Is This Service */}
|
||||||
|
<section className="py-24 lg:py-32" style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
<div className="section-margin-x">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<BrandedTag text="What Is This Service?" />
|
||||||
|
<h2 className="text-h2 mb-8 text-[#26231A]">{apiData.overview.title}</h2>
|
||||||
|
<div className="max-w-4xl mx-auto space-y-6">
|
||||||
|
<p className="text-body-lg text-[#6F6F6F] leading-relaxed">
|
||||||
|
{apiData.overview.description}
|
||||||
|
</p>
|
||||||
|
<div className="bg-[#04045B]/5 border-l-4 border-[#04045B] p-6 rounded-lg">
|
||||||
|
<p className="text-body-lg text-[#26231A] leading-relaxed">
|
||||||
|
<span className="font-semibold text-[#04045B]">The Business Problem It Solves:</span> {apiData.overview.highlight_text}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
{apiData.overview.overview_cards.map((card, index) => {
|
||||||
|
const IconComponent = getIconComponent(card.icon_url);
|
||||||
|
return (
|
||||||
|
<div key={card.id} className="group bg-white border border-gray-200 rounded-xl p-8 hover:border-[#04045B]/20 hover:shadow-lg transition-all duration-300">
|
||||||
|
<div className="flex flex-col items-center text-center">
|
||||||
|
<div
|
||||||
|
className="w-16 h-16 rounded-xl flex items-center justify-center mb-6 group-hover:scale-105 transition-transform duration-300"
|
||||||
|
style={{ backgroundColor: '#04045B' }}
|
||||||
|
>
|
||||||
|
<IconComponent className="w-8 h-8 text-white" />
|
||||||
|
</div>
|
||||||
|
<h4 className="text-h4 mb-4 text-[#26231A] group-hover:text-[#04045B] transition-colors duration-300">
|
||||||
|
{card.title}
|
||||||
|
</h4>
|
||||||
|
<p className="text-body text-[#6F6F6F] leading-relaxed">
|
||||||
|
{card.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 2. Who Is It For */}
|
||||||
|
<section className="py-24 lg:py-32" style={{ backgroundColor: '#F9F9F9' }}>
|
||||||
|
<div className="section-margin-x">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<BrandedTag text="Who Is It For?" />
|
||||||
|
<h2 className="text-h2 mb-8">{apiData.audience_section.title}</h2>
|
||||||
|
<p className="text-body-lg text-muted max-w-3xl mx-auto">
|
||||||
|
{apiData.audience_section.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
|
{targetAudience.map((audience, index) => (
|
||||||
|
<Card key={index} className="h-full hover:shadow-lg transition-all duration-300">
|
||||||
|
<CardContent className="p-8">
|
||||||
|
<div className="flex items-center gap-4 mb-6">
|
||||||
|
<div
|
||||||
|
className="w-16 h-16 rounded-2xl flex items-center justify-center"
|
||||||
|
style={{ backgroundColor: 'var(--color-primary)' }}
|
||||||
|
>
|
||||||
|
<audience.icon className="w-8 h-8 text-white" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-h4 mb-4">{audience.title}</h3>
|
||||||
|
<p className="text-body text-muted mb-6">{audience.description}</p>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-small font-semibold text-primary mb-3">Common Challenges:</h4>
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{audience.challenges.map((challenge, challengeIndex) => (
|
||||||
|
<li key={challengeIndex} className="text-small text-muted flex items-start gap-2">
|
||||||
|
<div className="w-1 h-1 bg-primary rounded-full mt-2 flex-shrink-0"></div>
|
||||||
|
{challenge}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 3. When to Use It */}
|
||||||
|
<section className="py-24 lg:py-32" style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
<div className="section-margin-x">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<BrandedTag text="When to Use It?" />
|
||||||
|
<h2 className="text-h2 mb-8">{apiData.use_case_section.title}</h2>
|
||||||
|
<p className="text-body-lg text-muted max-w-2xl mx-auto">
|
||||||
|
{apiData.use_case_section.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
|
{useCases.map((useCase, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="bg-white rounded-xl border border-gray-200 p-6 hover:border-[#04045B] hover:shadow-lg transition-all duration-300"
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-4 mb-4">
|
||||||
|
<div
|
||||||
|
className="w-12 h-12 rounded-lg flex items-center justify-center flex-shrink-0"
|
||||||
|
style={{ backgroundColor: '#04045B' }}
|
||||||
|
>
|
||||||
|
<useCase.icon className="w-6 h-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="text-h4 mb-2 text-[#26231A]">
|
||||||
|
{useCase.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-body text-muted">
|
||||||
|
{useCase.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 text-small text-[#04045B] bg-[#04045B]/5 px-3 py-2 rounded-lg">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-[#F8C301]" />
|
||||||
|
<span className="font-medium">
|
||||||
|
{useCase.scenario}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 4. Our Approach */}
|
||||||
|
<section className="py-24 lg:py-32" style={{ backgroundColor: '#F9F9F9' }}>
|
||||||
|
<div className="section-margin-x">
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<BrandedTag text="Our Approach" />
|
||||||
|
<h2 className="text-h2 mb-8 text-[#26231A]">{apiData.approach_section.title}</h2>
|
||||||
|
<p className="text-body-lg text-[#6F6F6F] max-w-3xl mx-auto">
|
||||||
|
{apiData.approach_section.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Flowchart Container with Connecting Lines */}
|
||||||
|
<div className="relative mb-16 flex flex-col items-center">
|
||||||
|
{/* Desktop: Horizontal Flowchart */}
|
||||||
|
<div className="hidden lg:block w-full max-w-5xl">
|
||||||
|
<div className="relative">
|
||||||
|
{/* Row 1: First 3 approach cards from API */}
|
||||||
|
<div className="grid grid-cols-3 gap-8 mb-12 relative w-full">
|
||||||
|
{apiData.approach_section.approach_cards.slice(0, 3).map((card, idx) => {
|
||||||
|
const IconComponent = getIconComponent(card.icon_url);
|
||||||
|
return (
|
||||||
|
<div key={card.id} className={`bg-white border-2 ${idx === 1 ? 'border-[#F8C301]' : 'border-[#04045B]'} rounded-xl p-6 hover:shadow-lg transition-all duration-300 relative z-10`}>
|
||||||
|
<div className={`w-12 h-12 ${idx === 1 ? 'bg-[#F8C301]' : 'bg-[#04045B]'} rounded-lg flex items-center justify-center mb-4`}>
|
||||||
|
<IconComponent className="w-6 h-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-h4 text-[#26231A] mb-3">{card.title}</h3>
|
||||||
|
<p className="text-body text-[#6F6F6F] mb-4">{card.description}</p>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{card.bullets.slice(0, 3).map((bullet, bulletIdx) => (
|
||||||
|
<div key={bulletIdx} className="text-small text-[#6F6F6F] bg-gray-50 px-3 py-2 rounded-lg">
|
||||||
|
{bullet}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* Arrows between first 3 cards */}
|
||||||
|
{apiData.approach_section.approach_cards.length >= 2 && (
|
||||||
|
<div className="absolute top-1/2 left-[calc(33.33%-2rem)] -translate-y-1/2 z-0 flex items-center">
|
||||||
|
<div className="w-16 h-0.5 bg-[#F8C301]"></div>
|
||||||
|
<ArrowRight className="w-6 h-6 text-[#F8C301] -ml-1" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{apiData.approach_section.approach_cards.length >= 3 && (
|
||||||
|
<div className="absolute top-1/2 left-[calc(66.66%-2rem)] -translate-y-1/2 z-0 flex items-center">
|
||||||
|
<div className="w-16 h-0.5 bg-[#04045B]"></div>
|
||||||
|
<ArrowRight className="w-6 h-6 text-[#04045B] -ml-1" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Vertical Connector - Center Flow Down */}
|
||||||
|
<div className="flex justify-center mb-6">
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<div className="w-0.5 h-12 bg-[#F8C301]"></div>
|
||||||
|
<ArrowRight className="w-6 h-6 text-[#F8C301] rotate-90" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Row 2: Next 2 approach cards (if available) */}
|
||||||
|
{apiData.approach_section.approach_cards.length >= 4 && (
|
||||||
|
<div className="grid grid-cols-2 gap-8 w-full max-w-3xl mx-auto mb-12 relative">
|
||||||
|
{apiData.approach_section.approach_cards.slice(3, 5).map((card, idx) => {
|
||||||
|
const IconComponent = getIconComponent(card.icon_url);
|
||||||
|
const isFirstOfPair = idx === 0;
|
||||||
|
return (
|
||||||
|
<div key={card.id} className={`bg-white border-2 ${isFirstOfPair ? 'border-[#F8C301]' : 'border-[#04045B]'} rounded-xl p-6 hover:shadow-lg transition-all duration-300 relative z-10`}>
|
||||||
|
<div className={`w-12 h-12 ${isFirstOfPair ? 'bg-[#F8C301]' : 'bg-[#04045B]'} rounded-lg flex items-center justify-center mb-4`}>
|
||||||
|
<IconComponent className="w-6 h-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-h4 text-[#26231A] mb-3">{card.title}</h3>
|
||||||
|
<p className="text-body text-[#6F6F6F] mb-4">{card.description}</p>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{card.bullets.slice(0, 3).map((bullet, bulletIdx) => (
|
||||||
|
<div key={bulletIdx} className="text-small text-[#6F6F6F] bg-gray-50 px-3 py-2 rounded-lg">
|
||||||
|
{bullet}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* Arrow between the two cards */}
|
||||||
|
{apiData.approach_section.approach_cards.length >= 5 && (
|
||||||
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-0 flex items-center">
|
||||||
|
<div className="w-16 h-0.5 bg-[#04045B]"></div>
|
||||||
|
<ArrowRight className="w-6 h-6 text-[#04045B] -ml-1" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Final Vertical Connector - Center Flow Down to Outcome */}
|
||||||
|
<div className="flex justify-center mb-6">
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<div className="w-0.5 h-12 bg-[#04045B]"></div>
|
||||||
|
<ArrowRight className="w-6 h-6 text-[#04045B] rotate-90" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Row 3: Expected Outcome - Use API outcomes data */}
|
||||||
|
<div className="flex justify-center w-full">
|
||||||
|
<div className="bg-[#04045B] text-white rounded-xl p-8 w-full max-w-2xl border-4 border-[#F8C301] shadow-xl">
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
{apiData.approach_section.outcomes && apiData.approach_section.outcomes[0] && (() => {
|
||||||
|
const OutcomeIcon = getIconComponent(apiData.approach_section.outcomes[0].icon_url);
|
||||||
|
return <OutcomeIcon className="w-10 h-10 text-[#F8C301]" />;
|
||||||
|
})() || <TrendingUp className="w-10 h-10 text-[#F8C301]" />}
|
||||||
|
<h3 className="text-h4 text-white">
|
||||||
|
{apiData.approach_section.outcomes?.[0]?.title || "Expected Outcome"}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<p className="text-body text-white mb-4">
|
||||||
|
{apiData.approach_section.outcomes?.[0]?.description || "Transformational executive leaders with strategic capability, executive presence, and proven business impact."}
|
||||||
|
</p>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{apiData.approach_section.outcomes?.[0]?.bullets?.slice(0, 2).map((bullet, idx) => (
|
||||||
|
<div key={idx} className="flex items-center gap-2 text-[#F8C301]">
|
||||||
|
<CheckCircle className="w-6 h-6" />
|
||||||
|
<span className="text-body text-white">{bullet}</span>
|
||||||
|
</div>
|
||||||
|
)) || (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center gap-2 text-[#F8C301]">
|
||||||
|
<CheckCircle className="w-6 h-6" />
|
||||||
|
<span className="text-body text-white">Exceptional Learning Experience</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tablet & Mobile: Vertical Flowchart */}
|
||||||
|
<div className="lg:hidden space-y-8">
|
||||||
|
{/* Map all approach cards vertically */}
|
||||||
|
{apiData.approach_section.approach_cards.map((card, idx) => {
|
||||||
|
const IconComponent = getIconComponent(card.icon_url);
|
||||||
|
const isEven = idx % 2 === 0;
|
||||||
|
return (
|
||||||
|
<div key={card.id} className="relative">
|
||||||
|
<div className={`bg-white border-2 ${isEven ? 'border-[#04045B]' : 'border-[#F8C301]'} rounded-xl p-6 hover:shadow-lg transition-all duration-300`}>
|
||||||
|
<div className={`w-12 h-12 ${isEven ? 'bg-[#04045B]' : 'bg-[#F8C301]'} rounded-lg flex items-center justify-center mb-4`}>
|
||||||
|
<IconComponent className="w-6 h-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-h4 text-[#26231A] mb-3">{card.title}</h3>
|
||||||
|
<p className="text-body text-[#6F6F6F] mb-4">{card.description}</p>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{card.bullets.map((bullet, bulletIdx) => (
|
||||||
|
<div key={bulletIdx} className="text-small text-[#6F6F6F] bg-gray-50 px-3 py-2 rounded-lg">
|
||||||
|
{bullet}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Connector Arrow */}
|
||||||
|
{idx < apiData.approach_section.approach_cards.length - 1 && (
|
||||||
|
<div className="flex justify-center my-4">
|
||||||
|
<ArrowRight className={`w-8 h-8 ${isEven ? 'text-[#F8C301]' : 'text-[#04045B]'} rotate-90`} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* Expected Outcome - Use API outcomes data */}
|
||||||
|
<div className="bg-[#04045B] text-white rounded-xl p-8 border-4 border-[#F8C301] shadow-xl">
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
{apiData.approach_section.outcomes && apiData.approach_section.outcomes[0] && (() => {
|
||||||
|
const OutcomeIcon = getIconComponent(apiData.approach_section.outcomes[0].icon_url);
|
||||||
|
return <OutcomeIcon className="w-10 h-10 text-[#F8C301]" />;
|
||||||
|
})() || <TrendingUp className="w-10 h-10 text-[#F8C301]" />}
|
||||||
|
<h3 className="text-h4 text-white">
|
||||||
|
{apiData.approach_section.outcomes?.[0]?.title || "Expected Outcome"}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<p className="text-body text-white mb-4">
|
||||||
|
{apiData.approach_section.outcomes?.[0]?.description || "Transformational executive leaders with strategic capability, executive presence, and proven business impact."}
|
||||||
|
</p>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{apiData.approach_section.outcomes?.[0]?.bullets?.slice(0, 2).map((bullet, idx) => (
|
||||||
|
<div key={idx} className="flex items-center gap-2 text-[#F8C301]">
|
||||||
|
<CheckCircle className="w-6 h-6" />
|
||||||
|
<span className="text-body text-white">{bullet}</span>
|
||||||
|
</div>
|
||||||
|
)) || (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center gap-2 text-[#F8C301]">
|
||||||
|
<CheckCircle className="w-6 h-6" />
|
||||||
|
<span className="text-body text-white">Exceptional Learning Experience</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Framework Effectiveness - Stats Section */}
|
||||||
|
{apiData.stats_section && (
|
||||||
|
<div className="bg-gray-50 rounded-xl p-8">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<h3 className="text-h3 text-[#26231A] mb-4">{apiData.stats_section.title}</h3>
|
||||||
|
<p className="text-body text-[#6F6F6F] max-w-2xl mx-auto">
|
||||||
|
{apiData.stats_section.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
{apiData.stats_section.stat_cards.map((stat) => {
|
||||||
|
const IconComponent = getIconComponent(stat.icon_url);
|
||||||
|
return (
|
||||||
|
<div key={stat.id} className="text-center bg-white rounded-lg p-6">
|
||||||
|
<div className="w-14 h-14 bg-[#04045B] rounded-lg flex items-center justify-center mx-auto mb-3">
|
||||||
|
<IconComponent className="w-7 h-7 text-white" />
|
||||||
|
</div>
|
||||||
|
<div className="text-h2 text-[#04045B] mb-2">{stat.value}</div>
|
||||||
|
<p className="text-body text-[#6F6F6F]">{stat.label}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 5. Sample Program Format */}
|
||||||
|
<section className="py-24 lg:py-32" style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
<div className="section-margin-x">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<BrandedTag text="Sample Program Format" />
|
||||||
|
<h2 className="text-h2 mb-8">{apiData.program_section.title}</h2>
|
||||||
|
<p className="text-body-lg text-muted max-w-3xl mx-auto">
|
||||||
|
{apiData.program_section.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-8">
|
||||||
|
{facilityFeatures.map((feature, index) => (
|
||||||
|
<Card
|
||||||
|
key={index}
|
||||||
|
className="border border-gray-200 hover:border-primary/20 transition-all duration-300 hover:shadow-lg"
|
||||||
|
>
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-between p-6 cursor-pointer"
|
||||||
|
onClick={() => setExpandedFeature(expandedFeature === index ? null : index)}
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-4 flex-1">
|
||||||
|
<div
|
||||||
|
className="w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0"
|
||||||
|
style={{ backgroundColor: 'var(--color-primary)' }}
|
||||||
|
>
|
||||||
|
<span className="text-white font-semibold">{index + 1}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="text-h4 mb-2">{feature.phase}</h3>
|
||||||
|
<div className="flex items-center gap-2 mb-3">
|
||||||
|
<Building className="w-4 h-4 text-muted" />
|
||||||
|
<span className="text-small text-muted">{feature.duration}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-4">
|
||||||
|
{expandedFeature === index ? (
|
||||||
|
<ChevronUp className="w-5 h-5 text-muted" />
|
||||||
|
) : (
|
||||||
|
<ChevronDown className="w-5 h-5 text-muted" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{expandedFeature === index && (
|
||||||
|
<div className="px-6 pb-6 border-t border-gray-100">
|
||||||
|
<div className="pt-6 grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||||
|
<div>
|
||||||
|
<h4 className="text-h4 mb-4">Available Features</h4>
|
||||||
|
<ul className="space-y-3">
|
||||||
|
{feature.activities.map((activity, activityIndex) => (
|
||||||
|
<li key={activityIndex} className="flex items-start gap-3">
|
||||||
|
<CheckCircle className="w-4 h-4 text-primary flex-shrink-0 mt-1" />
|
||||||
|
<span className="text-body text-muted">{activity}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-h4 mb-4">Experience Benefits</h4>
|
||||||
|
<ul className="space-y-3">
|
||||||
|
{feature.deliverables.map((deliverable, deliverableIndex) => (
|
||||||
|
<li key={deliverableIndex} className="flex items-start gap-3">
|
||||||
|
<Star className="w-4 h-4 text-accent flex-shrink-0 mt-1" />
|
||||||
|
<span className="text-body text-muted">{deliverable}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 6. Impact You Can Expect */}
|
||||||
|
<section className="py-24 lg:py-32" style={{ backgroundColor: '#F9F9F9' }}>
|
||||||
|
<div className="section-margin-x">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<BrandedTag text="Impact You Can Expect" />
|
||||||
|
<h2 className="text-h2 mb-8">{apiData.impact_section.title}</h2>
|
||||||
|
<p className="text-body-lg text-muted max-w-3xl mx-auto">
|
||||||
|
{apiData.impact_section.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 mb-12">
|
||||||
|
{apiData.impact_section.impact_stats.map((stat) => {
|
||||||
|
const IconComponent = getIconComponent(stat.icon_url);
|
||||||
|
return (
|
||||||
|
<Card key={stat.id} className="text-center bg-white hover:shadow-lg transition-all duration-300">
|
||||||
|
<CardContent className="p-8">
|
||||||
|
<div
|
||||||
|
className="w-16 h-16 rounded-2xl flex items-center justify-center mx-auto mb-6"
|
||||||
|
style={{ backgroundColor: 'var(--color-primary)' }}
|
||||||
|
>
|
||||||
|
<IconComponent className="w-8 h-8 text-white" />
|
||||||
|
</div>
|
||||||
|
<div className="text-5xl font-medium mb-4" style={{ color: 'var(--color-primary)' }}>
|
||||||
|
{stat.value}
|
||||||
|
</div>
|
||||||
|
<p className="text-body text-muted mb-2">{stat.description}</p>
|
||||||
|
<p className="text-small text-primary font-medium">{stat.label}</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white p-8 rounded-2xl shadow-lg">
|
||||||
|
<h3 className="text-h3 mb-6 text-center">Additional Facility Benefits</h3>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{apiData.impact_section.impact_benefits.map((benefit) => {
|
||||||
|
const IconComponent = getIconComponent(benefit.icon_url);
|
||||||
|
return (
|
||||||
|
<div key={benefit.id} className="text-center">
|
||||||
|
<div className="w-12 h-12 bg-primary/10 rounded-lg flex items-center justify-center mx-auto mb-3">
|
||||||
|
<IconComponent className="w-6 h-6 text-primary" />
|
||||||
|
</div>
|
||||||
|
<h4 className="text-h4 mb-2">{benefit.title}</h4>
|
||||||
|
<p className="text-small text-muted">{benefit.description}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 7. Client Examples / Testimonials */}
|
||||||
|
<TestimonialsSection
|
||||||
|
title="What Our Clients Say About Kautilya Facility"
|
||||||
|
subtitle="Hear from organizations that have experienced our world-class learning environment"
|
||||||
|
tagText="Client Success Stories"
|
||||||
|
customTestimonials={testimonials}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 8. CTA Section */}
|
||||||
|
{apiData.cta_section && (
|
||||||
|
<section className="relative h-[700px] overflow-hidden">
|
||||||
|
<div className="absolute inset-0">
|
||||||
|
<ImageWithFallback
|
||||||
|
src={apiData.cta_section.background_image_url}
|
||||||
|
alt={apiData.cta_section.text}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-black/30" />
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-black/20 via-transparent to-black/60" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative h-full flex items-center justify-end section-margin-x">
|
||||||
|
<div
|
||||||
|
className="bg-opacity-95 backdrop-blur-sm rounded-lg p-16 max-w-2xl"
|
||||||
|
style={{ backgroundColor: 'var(--color-brand-primary)' }}
|
||||||
|
>
|
||||||
|
<BrandedTag text="Next Steps" variant="white" />
|
||||||
|
<h2 className="text-h2-white mb-8">
|
||||||
|
{apiData.cta_section.text}
|
||||||
|
<span className="italic" style={{ color: 'var(--color-brand-accent)' }}>
|
||||||
|
{" "}Get in touch{" "}
|
||||||
|
</span>
|
||||||
|
to schedule your facility tour.
|
||||||
|
</h2>
|
||||||
|
<StandardCTAButton
|
||||||
|
text={apiData.cta_section.cta_text}
|
||||||
|
onClick={() => navigateTo(apiData.cta_section.cta_destination)}
|
||||||
|
ariaLabel={apiData.cta_section.cta_text}
|
||||||
|
/>
|
||||||
|
{apiData.cta_section.description && (
|
||||||
|
<p className="text-body-white mt-6 opacity-90">
|
||||||
|
{apiData.cta_section.description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
188
.gitea/workflows/src/components/LeadershipJourneyPage.tsx
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { motion } from 'motion/react';
|
||||||
|
import { Building2, User, ArrowRight, ChevronLeft, Shield, Users, Target, BookOpen } from 'lucide-react';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { navigateTo } from './Router';
|
||||||
|
import { BrandedTag } from './about/BrandedTag';
|
||||||
|
|
||||||
|
interface JourneyOption {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
icon: React.ReactNode;
|
||||||
|
benefits: string[];
|
||||||
|
buttonText: string;
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LeadershipJourneyPage() {
|
||||||
|
const [hoveredOption, setHoveredOption] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const journeyOptions: JourneyOption[] = [
|
||||||
|
{
|
||||||
|
id: 'corporate',
|
||||||
|
title: 'Corporate Login',
|
||||||
|
description: 'Access your organization\'s leadership development programs and track team progress.',
|
||||||
|
icon: <Building2 className="w-8 h-8" />,
|
||||||
|
benefits: [
|
||||||
|
'Team management and tracking',
|
||||||
|
'Enterprise-grade security',
|
||||||
|
'Customized learning paths',
|
||||||
|
'Advanced analytics and reporting'
|
||||||
|
],
|
||||||
|
buttonText: 'Access Corporate Portal',
|
||||||
|
onClick: () => navigateTo('/corporate-login')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'self-learner',
|
||||||
|
title: 'Self Learner',
|
||||||
|
description: 'Begin your personal leadership journey with our comprehensive individual programs.',
|
||||||
|
icon: <User className="w-8 h-8" />,
|
||||||
|
benefits: [
|
||||||
|
'Personalized learning experience',
|
||||||
|
'Self-paced development',
|
||||||
|
'Individual progress tracking',
|
||||||
|
'Direct access to resources'
|
||||||
|
],
|
||||||
|
buttonText: 'Start Personal Journey',
|
||||||
|
onClick: () => navigateTo('/self-learner-signup')
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen" style={{ backgroundColor: '#F7F7FD' }}>
|
||||||
|
{/* Header Section */}
|
||||||
|
<section className="py-20" style={{ backgroundColor: 'var(--color-brand-bg-white)' }}>
|
||||||
|
<div className="max-w-7xl mx-auto section-margin-x">
|
||||||
|
|
||||||
|
|
||||||
|
{/* Branded Tag */}
|
||||||
|
<motion.div
|
||||||
|
className="mb-6 text-center"
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
>
|
||||||
|
<BrandedTag text="Choose Your Path" />
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Main Heading */}
|
||||||
|
<motion.div
|
||||||
|
className="text-center mb-6"
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.7, delay: 0.2 }}
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
className="text-5xl font-bold leading-tight max-lg:text-4xl max-md:text-3xl mb-6"
|
||||||
|
style={{ color: 'var(--color-brand-black)' }}
|
||||||
|
>
|
||||||
|
Start Your Leadership Journey
|
||||||
|
</h1>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Journey Options Section */}
|
||||||
|
<section className="py-20">
|
||||||
|
<div className="max-w-7xl mx-auto section-margin-x">
|
||||||
|
<div className="grid md:grid-cols-2 gap-12">
|
||||||
|
{journeyOptions.map((option, index) => (
|
||||||
|
<motion.div
|
||||||
|
key={option.id}
|
||||||
|
className="relative"
|
||||||
|
initial={{ opacity: 0, y: 40 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.7, delay: 0.6 + index * 0.2 }}
|
||||||
|
onHoverStart={() => setHoveredOption(option.id)}
|
||||||
|
onHoverEnd={() => setHoveredOption(null)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="bg-white rounded-2xl p-8 h-full shadow-lg border border-gray-100 transition-all duration-300 hover:shadow-2xl hover:-translate-y-2"
|
||||||
|
style={{
|
||||||
|
transform: hoveredOption === option.id ? 'translateY(-8px)' : 'translateY(0)',
|
||||||
|
boxShadow: hoveredOption === option.id
|
||||||
|
? '0 25px 50px -12px rgba(0, 0, 0, 0.15)'
|
||||||
|
: '0 10px 25px -5px rgba(0, 0, 0, 0.1)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Icon Container */}
|
||||||
|
<div
|
||||||
|
className="w-16 h-16 rounded-xl flex items-center justify-center mb-6 transition-all duration-300"
|
||||||
|
style={{
|
||||||
|
backgroundColor: hoveredOption === option.id
|
||||||
|
? 'var(--color-brand-accent)'
|
||||||
|
: 'var(--color-brand-primary)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ color: hoveredOption === option.id ? 'var(--color-brand-black)' : 'white' }}>
|
||||||
|
{option.icon}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<h3
|
||||||
|
className="text-2xl font-bold mb-4"
|
||||||
|
style={{ color: 'var(--color-brand-black)' }}
|
||||||
|
>
|
||||||
|
{option.title}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p className="text-gray-600 text-lg leading-relaxed mb-6">
|
||||||
|
{option.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Benefits List */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<h4 className="text-sm font-semibold text-gray-800 mb-4 uppercase tracking-wider">
|
||||||
|
Key Benefits
|
||||||
|
</h4>
|
||||||
|
<ul className="space-y-3">
|
||||||
|
{option.benefits.map((benefit, benefitIndex) => (
|
||||||
|
<motion.li
|
||||||
|
key={benefitIndex}
|
||||||
|
className="flex items-start gap-3"
|
||||||
|
initial={{ opacity: 0, x: -10 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
transition={{ duration: 0.5, delay: 0.8 + index * 0.2 + benefitIndex * 0.1 }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="w-2 h-2 rounded-full mt-2 flex-shrink-0"
|
||||||
|
style={{ backgroundColor: 'var(--color-brand-accent)' }}
|
||||||
|
/>
|
||||||
|
<span className="text-gray-700 leading-relaxed">{benefit}</span>
|
||||||
|
</motion.li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CTA Button */}
|
||||||
|
<motion.button
|
||||||
|
className="w-full flex items-center justify-center gap-3 px-8 py-4 font-semibold text-lg transition-all duration-300 hover:shadow-lg hover:-translate-y-1"
|
||||||
|
style={{
|
||||||
|
borderRadius: '10px',
|
||||||
|
backgroundColor: hoveredOption === option.id
|
||||||
|
? 'var(--color-brand-primary)'
|
||||||
|
: 'var(--color-brand-accent)',
|
||||||
|
color: hoveredOption === option.id
|
||||||
|
? 'white'
|
||||||
|
: 'var(--color-brand-black)'
|
||||||
|
}}
|
||||||
|
onClick={option.onClick}
|
||||||
|
whileHover={{ scale: 1.02 }}
|
||||||
|
whileTap={{ scale: 0.98 }}
|
||||||
|
>
|
||||||
|
<span>{option.buttonText}</span>
|
||||||
|
<ArrowRight className="w-5 h-5" />
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
341
.gitea/workflows/src/components/LearningEnvionment.tsx
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
import { useState, useEffect, useRef } from "react";
|
||||||
|
import { motion } from "motion/react";
|
||||||
|
import {
|
||||||
|
Users,
|
||||||
|
Settings,
|
||||||
|
User,
|
||||||
|
Globe,
|
||||||
|
MessageSquare,
|
||||||
|
GraduationCap,
|
||||||
|
ArrowRight
|
||||||
|
} from "lucide-react";
|
||||||
|
import { BrandedTag } from "./about/BrandedTag";
|
||||||
|
import { PrimaryCTAButton } from "./PrimaryCTAButton";
|
||||||
|
import { navigateTo } from "./Router";
|
||||||
|
|
||||||
|
// Services data
|
||||||
|
const recognitionItems = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: "Leadership Development",
|
||||||
|
description: "Comprehensive programs designed to cultivate strategic thinking and emotional intelligence. Develop capabilities that drive organizational success through authentic leadership practices.",
|
||||||
|
icon: <Users size={28} />,
|
||||||
|
badge: "CORE PROGRAM",
|
||||||
|
badgeColor: "#F8C301"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: "Management Development",
|
||||||
|
description: "Essential skills training for first-time and experienced managers seeking growth. Focus on communication, delegation, and performance management excellence.",
|
||||||
|
icon: <Settings size={28} />,
|
||||||
|
badge: "POPULAR",
|
||||||
|
badgeColor: "#04045B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: "Culture Competence",
|
||||||
|
description: "Build cultural awareness and inclusive practices that enhance team collaboration. Navigate cultural differences with confidence and create inclusive environments.",
|
||||||
|
icon: <Globe size={28} />,
|
||||||
|
badge: "GLOBAL FOCUS",
|
||||||
|
badgeColor: "#F8C301"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
title: "Executive Coaching",
|
||||||
|
description: "One-on-one personalized development for senior leaders and high-potential talent. Strategic guidance for complex leadership challenges and career advancement.",
|
||||||
|
icon: <User size={28} />,
|
||||||
|
badge: "PREMIUM",
|
||||||
|
badgeColor: "#04045B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
title: "Communication Excellence",
|
||||||
|
description: "Master the art of influential communication across all organizational levels. Develop presentation skills, difficult conversation navigation, and stakeholder engagement.",
|
||||||
|
icon: <MessageSquare size={28} />,
|
||||||
|
badge: "ESSENTIAL",
|
||||||
|
badgeColor: "#F8C301"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
title: "Change Leadership",
|
||||||
|
description: "Guide organizations through transformation with confidence and clarity. Learn frameworks for managing resistance, building momentum, and sustaining change initiatives.",
|
||||||
|
icon: <GraduationCap size={28} />,
|
||||||
|
badge: "STRATEGIC",
|
||||||
|
badgeColor: "#04045B"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export function LearningEnvionment() {
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
const cardRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||||
|
const sectionRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Add card refs helper
|
||||||
|
const addCardRef = (el: HTMLDivElement | null, index: number) => {
|
||||||
|
cardRefs.current[index] = el;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Intersection observer for animations
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setIsVisible(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ threshold: 0.2 }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sectionRef.current) {
|
||||||
|
observer.observe(sectionRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Keyboard navigation
|
||||||
|
const handleKeyDown = (e: React.KeyboardEvent, index: number) => {
|
||||||
|
if (e.key === 'ArrowDown' && index < cardRefs.current.length - 1) {
|
||||||
|
cardRefs.current[index + 1]?.focus();
|
||||||
|
e.preventDefault();
|
||||||
|
} else if (e.key === 'ArrowUp' && index > 0) {
|
||||||
|
cardRefs.current[index - 1]?.focus();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
ref={sectionRef}
|
||||||
|
className="py-16 lg:py-20"
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#F7F7FD',
|
||||||
|
fontFamily: 'var(--font-family-brand)'
|
||||||
|
}}
|
||||||
|
aria-labelledby="recognition-section-heading"
|
||||||
|
>
|
||||||
|
<div className="section-margin-x">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
{/* Desktop Layout - Grid with Sticky Sidebar */}
|
||||||
|
<div className="hidden lg:grid grid-cols-12 gap-12 min-h-screen">
|
||||||
|
{/* Left Side - Sticky Content */}
|
||||||
|
<div className="col-span-5 sticky top-24 self-start">
|
||||||
|
<div className="recognition-header pr-8">
|
||||||
|
<BrandedTag
|
||||||
|
text="Our Services"
|
||||||
|
/>
|
||||||
|
<h2
|
||||||
|
id="recognition-section-heading"
|
||||||
|
className="text-h2 mb-6"
|
||||||
|
>
|
||||||
|
Shaping Leaders, Cultures, and Institutions
|
||||||
|
</h2>
|
||||||
|
<p className="text-body-lg text-muted mb-8">
|
||||||
|
No two institutions are alike — and neither are their leadership needs. That's why every KLC service is rooted in research, tailored to context, and aligned with strategy. From shaping leaders and managers to shaping culture, developing talent frameworks, and offering practical high impact learning, we partner with you to create leadership solutions that deliver lasting value.
|
||||||
|
</p>
|
||||||
|
{/* CTA Button - Left aligned */}
|
||||||
|
<div className="primary-cta-container-left cta-left-locked">
|
||||||
|
<PrimaryCTAButton
|
||||||
|
text="Services Page"
|
||||||
|
onClick={() => navigateTo('/services')}
|
||||||
|
ariaLabel="Explore our services"
|
||||||
|
className="services-cta-override"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Side - Scrolling Cards */}
|
||||||
|
<div className="col-span-7">
|
||||||
|
<div
|
||||||
|
className="recognition-cards space-y-6"
|
||||||
|
role="list"
|
||||||
|
aria-label="Leadership development services"
|
||||||
|
>
|
||||||
|
{recognitionItems.map((item, index) => (
|
||||||
|
<div
|
||||||
|
key={item.id}
|
||||||
|
ref={(el) => addCardRef(el, index)}
|
||||||
|
className={`recognition-card group scroll-animate-stagger focus-ring ${isVisible ? 'animate-in' : ''}`}
|
||||||
|
role="listitem"
|
||||||
|
aria-labelledby={`recognition-title-${item.id}`}
|
||||||
|
aria-describedby={`recognition-desc-${item.id}`}
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={(e) => handleKeyDown(e, index)}
|
||||||
|
style={{
|
||||||
|
transitionDelay: `${(index + 1) * 150}ms`,
|
||||||
|
opacity: isVisible ? 1 : 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="p-8 transition-all duration-300 hover:shadow-xl hover:-translate-y-1 border bg-white"
|
||||||
|
style={{
|
||||||
|
borderColor: 'var(--color-border)',
|
||||||
|
borderRadius: '12px',
|
||||||
|
fontFamily: 'var(--font-family-brand)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between mb-6">
|
||||||
|
<div
|
||||||
|
className="w-14 h-14 flex items-center justify-center transition-transform duration-300 group-hover:scale-110"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-brand-primary)',
|
||||||
|
borderRadius: '12px',
|
||||||
|
color: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
</div>
|
||||||
|
{item.badge && (
|
||||||
|
<div
|
||||||
|
className="px-3 py-1 text-xs font-bold uppercase tracking-wider"
|
||||||
|
style={{
|
||||||
|
backgroundColor: item.badgeColor,
|
||||||
|
color: item.badgeColor === '#F8C301' ? 'var(--color-brand-black)' : 'white',
|
||||||
|
borderRadius: '20px',
|
||||||
|
fontFamily: 'var(--font-family-brand)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.badge}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="recognition-card-content">
|
||||||
|
<h3
|
||||||
|
id={`recognition-title-${item.id}`}
|
||||||
|
className="text-h4 mb-4"
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
id={`recognition-desc-${item.id}`}
|
||||||
|
className="text-small text-muted leading-relaxed"
|
||||||
|
>
|
||||||
|
{item.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Layout - Stacked Header + Horizontal Scrollable Cards */}
|
||||||
|
<div className="lg:hidden">
|
||||||
|
{/* Mobile Header */}
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<BrandedTag
|
||||||
|
text="Our Services"
|
||||||
|
/>
|
||||||
|
<h2
|
||||||
|
id="recognition-section-heading-mobile"
|
||||||
|
className="text-h2 mb-6"
|
||||||
|
>
|
||||||
|
Shaping Leaders, Cultures, and Institutions
|
||||||
|
</h2>
|
||||||
|
<p className="text-body-lg text-muted mb-8">
|
||||||
|
No two institutions are alike — and neither are their leadership needs. That's why every KLC service is rooted in research, tailored to context, and aligned with strategy. From shaping leaders and managers to shaping culture, developing talent frameworks, and offering practical high impact learning, we partner with you to create leadership solutions that deliver lasting value.
|
||||||
|
</p>
|
||||||
|
{/* CTA Button - Left aligned for mobile */}
|
||||||
|
<div className="primary-cta-container-left cta-left-locked">
|
||||||
|
<PrimaryCTAButton
|
||||||
|
text="Services Page"
|
||||||
|
onClick={() => navigateTo('/services')}
|
||||||
|
ariaLabel="Explore our services"
|
||||||
|
className="services-cta-override"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Horizontal Scrollable Cards */}
|
||||||
|
<div className="relative">
|
||||||
|
<div
|
||||||
|
className="flex gap-6 overflow-x-auto scrollbar-hide pb-4"
|
||||||
|
style={{
|
||||||
|
scrollSnapType: 'x mandatory',
|
||||||
|
WebkitOverflowScrolling: 'touch'
|
||||||
|
}}
|
||||||
|
role="list"
|
||||||
|
aria-label="Leadership development services"
|
||||||
|
>
|
||||||
|
{recognitionItems.map((item, index) => (
|
||||||
|
<div
|
||||||
|
key={item.id}
|
||||||
|
className={`recognition-card-mobile group focus-ring flex-shrink-0 ${isVisible ? 'animate-in' : ''}`}
|
||||||
|
role="listitem"
|
||||||
|
aria-labelledby={`recognition-title-mobile-${item.id}`}
|
||||||
|
aria-describedby={`recognition-desc-mobile-${item.id}`}
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={(e) => handleKeyDown(e, index)}
|
||||||
|
style={{
|
||||||
|
scrollSnapAlign: 'start',
|
||||||
|
width: '320px',
|
||||||
|
transitionDelay: `${(index + 1) * 150}ms`,
|
||||||
|
opacity: isVisible ? 1 : 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="p-6 transition-all duration-300 hover:shadow-xl hover:-translate-y-1 border bg-white h-full"
|
||||||
|
style={{
|
||||||
|
borderColor: 'var(--color-border)',
|
||||||
|
borderRadius: '12px',
|
||||||
|
fontFamily: 'var(--font-family-brand)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between mb-6">
|
||||||
|
<div
|
||||||
|
className="w-12 h-12 flex items-center justify-center transition-transform duration-300 group-hover:scale-110"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-brand-primary)',
|
||||||
|
borderRadius: '12px',
|
||||||
|
color: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
</div>
|
||||||
|
{item.badge && (
|
||||||
|
<div
|
||||||
|
className="px-2 py-1 text-xs font-bold uppercase tracking-wider"
|
||||||
|
style={{
|
||||||
|
backgroundColor: item.badgeColor,
|
||||||
|
color: item.badgeColor === '#F8C301' ? 'var(--color-brand-black)' : 'white',
|
||||||
|
borderRadius: '20px',
|
||||||
|
fontFamily: 'var(--font-family-brand)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.badge}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="recognition-card-content">
|
||||||
|
<h3
|
||||||
|
id={`recognition-title-mobile-${item.id}`}
|
||||||
|
className="text-h4 mb-4"
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
id={`recognition-desc-mobile-${item.id}`}
|
||||||
|
className="text-small text-muted leading-relaxed"
|
||||||
|
>
|
||||||
|
{item.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
2300
.gitea/workflows/src/components/LearningFacilityNew.tsx
Normal file
773
.gitea/workflows/src/components/LearningOnline.tsx
Normal file
@@ -0,0 +1,773 @@
|
|||||||
|
import {
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
Clock,
|
||||||
|
DollarSign,
|
||||||
|
Filter,
|
||||||
|
Grid,
|
||||||
|
List,
|
||||||
|
Search,
|
||||||
|
Star,
|
||||||
|
Users,
|
||||||
|
X
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { motion } from 'motion/react';
|
||||||
|
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||||
|
import { useCart } from './CartContext';
|
||||||
|
import { CartItem, CartPopup } from './CartPopup';
|
||||||
|
import { CourseCard } from './CourseCard';
|
||||||
|
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||||
|
import { navigateTo } from './Router';
|
||||||
|
import { Badge } from './ui/badge';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Card } from './ui/card';
|
||||||
|
import { Input } from './ui/input';
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
|
||||||
|
import {
|
||||||
|
courseApi,
|
||||||
|
useGetCoursesQuery,
|
||||||
|
Course,
|
||||||
|
GetCoursesParams,
|
||||||
|
useGetCourseCategoriesQuery,
|
||||||
|
CourseCategory
|
||||||
|
} from '../redux/services/courseApi';
|
||||||
|
import { useDebounce } from '../redux/hooks/useDebounce';
|
||||||
|
|
||||||
|
// Helper function to parse rupee price from string (keep as is)
|
||||||
|
const parsePriceToNumber = (priceStr: string | number): number => {
|
||||||
|
if (typeof priceStr === 'number') return priceStr;
|
||||||
|
const numericStr = priceStr.toString().replace(/[^0-9.-]/g, '');
|
||||||
|
return parseFloat(numericStr) || 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format price with Rupee symbol (keep as is)
|
||||||
|
const formatPrice = (price: number): string => {
|
||||||
|
return `₹${price.toLocaleString('en-IN')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function LearningOnline() {
|
||||||
|
// UI state
|
||||||
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const [selectedCategoryId, setSelectedCategoryId] = useState<string>('');
|
||||||
|
const [selectedCategoryName, setSelectedCategoryName] = useState('All Categories');
|
||||||
|
const [selectedPriceRange, setSelectedPriceRange] = useState('All Prices');
|
||||||
|
const [selectedDuration, setSelectedDuration] = useState('All Durations');
|
||||||
|
const [selectedRating, setSelectedRating] = useState('All Ratings');
|
||||||
|
const [sortBy, setSortBy] = useState(''); // ✅ Changed to match API value
|
||||||
|
// const [sortBy, setSortBy] = useState('most_popular'); // ✅ Changed to match API value
|
||||||
|
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const coursesPerPage = 9;
|
||||||
|
const prefetchCourseById = courseApi.usePrefetch('getcoursebyid');
|
||||||
|
|
||||||
|
// Debounced search term to avoid too many API calls
|
||||||
|
const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
||||||
|
|
||||||
|
// Fetch course categories
|
||||||
|
const { data: categoriesData, isLoading: categoriesLoading } = useGetCourseCategoriesQuery({
|
||||||
|
limit: 100,
|
||||||
|
offset: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ Updated sort options to match backend API values
|
||||||
|
const sortOptions = [
|
||||||
|
{ value: 'most_popular', label: 'Most Popular' },
|
||||||
|
{ value: 'newest', label: 'Newest First' },
|
||||||
|
{ value: 'title_asc', label: 'Title A-Z' },
|
||||||
|
{ value: 'price_low', label: 'Price: Low to High' },
|
||||||
|
{ value: 'price_high', label: 'Price: High to Low' },
|
||||||
|
{ value: 'highest_rated', label: 'Highest Rated' },
|
||||||
|
{ value: 'duration', label: 'Duration' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const priceRanges = [
|
||||||
|
'All Prices',
|
||||||
|
'Under ₹20,000',
|
||||||
|
'₹20,000 - ₹35,000',
|
||||||
|
'₹35,000 - ₹50,000',
|
||||||
|
'Over ₹50,000'
|
||||||
|
];
|
||||||
|
|
||||||
|
const durations = [
|
||||||
|
'All Durations',
|
||||||
|
'Under 6 hours',
|
||||||
|
'6-10 hours',
|
||||||
|
'10-15 hours',
|
||||||
|
'Over 15 hours'
|
||||||
|
];
|
||||||
|
|
||||||
|
const ratings = [
|
||||||
|
'All Ratings',
|
||||||
|
'4.5+ Stars',
|
||||||
|
'4.0+ Stars',
|
||||||
|
'3.5+ Stars'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Build categories list
|
||||||
|
const categories = useMemo(() => {
|
||||||
|
const cats = [{ id: '', name: 'All Categories' }];
|
||||||
|
if (categoriesData?.data?.items) {
|
||||||
|
categoriesData.data.items.forEach((cat: CourseCategory) => {
|
||||||
|
cats.push({ id: cat.id, name: cat.category_name });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return cats;
|
||||||
|
}, [categoriesData]);
|
||||||
|
|
||||||
|
// ✅ Helper function to convert UI price range to API format
|
||||||
|
const getPriceRangeForApi = useCallback((priceRange: string): string | undefined => {
|
||||||
|
switch (priceRange) {
|
||||||
|
case 'Under ₹20,000':
|
||||||
|
return 'under_20000';
|
||||||
|
case '₹20,000 - ₹35,000':
|
||||||
|
return '20000_35000';
|
||||||
|
case '₹35,000 - ₹50,000':
|
||||||
|
return '35000_50000';
|
||||||
|
case 'Over ₹50,000':
|
||||||
|
return 'above_50000';
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// ✅ Helper function to convert UI duration to API format
|
||||||
|
const getDurationForApi = useCallback((duration: string): string | undefined => {
|
||||||
|
switch (duration) {
|
||||||
|
case 'Under 6 hours':
|
||||||
|
return 'under_6';
|
||||||
|
case '6-10 hours':
|
||||||
|
return '6_10';
|
||||||
|
case '10-15 hours':
|
||||||
|
return '10_15';
|
||||||
|
case 'Over 15 hours':
|
||||||
|
return 'above_15';
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Helper function to convert UI rating to API format
|
||||||
|
const getRatingForApi = useCallback((rating: string): number | undefined => {
|
||||||
|
switch (rating) {
|
||||||
|
case '4.5+ Stars':
|
||||||
|
return 4.5;
|
||||||
|
case '4.0+ Stars':
|
||||||
|
return 4.0;
|
||||||
|
case '3.5+ Stars':
|
||||||
|
return 3.5;
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// ✅ Helper function to convert sort option to API format
|
||||||
|
const getSortByForApi = useCallback((sort: string): string | undefined => {
|
||||||
|
// sort is already in API format (most_popular, newest, title_asc, etc.)
|
||||||
|
// Just return it as is
|
||||||
|
return sort;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Build API filters based on current UI state
|
||||||
|
const apiFilters: GetCoursesParams = useMemo(() => {
|
||||||
|
const filters: GetCoursesParams = {
|
||||||
|
limit: 100,
|
||||||
|
offset: 0,
|
||||||
|
status: 'publish'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Category filter
|
||||||
|
if (selectedCategoryId) {
|
||||||
|
filters.course_category = [selectedCategoryId];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search query
|
||||||
|
if (debouncedSearchTerm) {
|
||||||
|
filters.search_query = debouncedSearchTerm;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Price range
|
||||||
|
const apiPriceRange = getPriceRangeForApi(selectedPriceRange);
|
||||||
|
if (apiPriceRange) {
|
||||||
|
filters.price_range = apiPriceRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duration range
|
||||||
|
const apiDurationRange = getDurationForApi(selectedDuration);
|
||||||
|
if (apiDurationRange) {
|
||||||
|
filters.duration_range = apiDurationRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rating
|
||||||
|
const apiRating = getRatingForApi(selectedRating);
|
||||||
|
if (apiRating !== undefined) {
|
||||||
|
filters.min_rating = apiRating;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by
|
||||||
|
const apiSortBy = getSortByForApi(sortBy);
|
||||||
|
if (apiSortBy && sortBy !== '') {
|
||||||
|
filters.sort_by = apiSortBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters;
|
||||||
|
}, [
|
||||||
|
selectedCategoryId,
|
||||||
|
debouncedSearchTerm,
|
||||||
|
selectedPriceRange,
|
||||||
|
selectedDuration,
|
||||||
|
selectedRating,
|
||||||
|
sortBy,
|
||||||
|
getPriceRangeForApi,
|
||||||
|
getDurationForApi,
|
||||||
|
getRatingForApi,
|
||||||
|
getSortByForApi
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Fetch courses with API filters
|
||||||
|
const {
|
||||||
|
data: coursesData,
|
||||||
|
isLoading: coursesLoading,
|
||||||
|
isError,
|
||||||
|
isFetching // To show loading indicator while fetching
|
||||||
|
} = useGetCoursesQuery(apiFilters);
|
||||||
|
|
||||||
|
// Reset to page 1 when filters change
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}, [
|
||||||
|
selectedCategoryId,
|
||||||
|
debouncedSearchTerm,
|
||||||
|
selectedPriceRange,
|
||||||
|
selectedDuration,
|
||||||
|
selectedRating,
|
||||||
|
sortBy
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Transform API response to course format
|
||||||
|
const courses = useMemo(() => {
|
||||||
|
if (!coursesData?.data?.items) return [];
|
||||||
|
|
||||||
|
return coursesData.data.items.map((course: Course) => ({
|
||||||
|
id: course.id,
|
||||||
|
title: course.course_name,
|
||||||
|
thumbnail: course.thumbnail_img || 'https://images.unsplash.com/photo-1552664730-d307ca884978?w=400&h=250&fit=crop',
|
||||||
|
duration: `${course.total_duration || 0} hours`,
|
||||||
|
level: 'Intermediate',
|
||||||
|
format: course.retail_type === 'public' ? 'Cohort-based' : 'Self-paced',
|
||||||
|
rating: course.avg_rating || 4.5,
|
||||||
|
reviews: `${course.total_reviews || 0} review${(course.total_reviews || 0) === 1 ? '' : 's'}`,
|
||||||
|
category: course.course_category_name || 'General',
|
||||||
|
categoryId: course.course_category_xid || '',
|
||||||
|
description: course.course_desc || `Master ${course.course_name} with our comprehensive program.`,
|
||||||
|
price: formatPrice(course.best_value || 0),
|
||||||
|
originalPrice: formatPrice(course.price || 0),
|
||||||
|
course_status: course.course_status
|
||||||
|
}));
|
||||||
|
}, [coursesData]);
|
||||||
|
|
||||||
|
// Get total courses count from API response
|
||||||
|
const totalCoursesCount = coursesData?.data?.pagination_info?.total_count || 0;
|
||||||
|
|
||||||
|
// Paginate the courses (since API returns all courses based on filters, we paginate client-side)
|
||||||
|
const totalPages = Math.ceil(totalCoursesCount / coursesPerPage);
|
||||||
|
const startIndex = (currentPage - 1) * coursesPerPage;
|
||||||
|
const currentCourses = courses.slice(startIndex, startIndex + coursesPerPage);
|
||||||
|
|
||||||
|
// Handle category change
|
||||||
|
const handleCategoryChange = (value: string) => {
|
||||||
|
const selectedCat = categories.find(cat => cat.name === value);
|
||||||
|
if (selectedCat) {
|
||||||
|
setSelectedCategoryName(selectedCat.name);
|
||||||
|
setSelectedCategoryId(selectedCat.id);
|
||||||
|
} else {
|
||||||
|
setSelectedCategoryName('All Categories');
|
||||||
|
setSelectedCategoryId('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearAllFilters = () => {
|
||||||
|
setSearchTerm('');
|
||||||
|
handleCategoryChange('All Categories');
|
||||||
|
setSelectedPriceRange('All Prices');
|
||||||
|
setSelectedDuration('All Durations');
|
||||||
|
setSelectedRating('All Ratings');
|
||||||
|
setSortBy('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasActiveFilters = searchTerm ||
|
||||||
|
selectedCategoryName !== 'All Categories' ||
|
||||||
|
selectedPriceRange !== 'All Prices' ||
|
||||||
|
selectedDuration !== 'All Durations' ||
|
||||||
|
selectedRating !== 'All Ratings';
|
||||||
|
|
||||||
|
// Cart functionality
|
||||||
|
const { addToCart } = useCart();
|
||||||
|
const [isCartPopupOpen, setIsCartPopupOpen] = useState(false);
|
||||||
|
const [recentlyAddedItem, setRecentlyAddedItem] = useState<CartItem | null>(null);
|
||||||
|
|
||||||
|
const handleAddToCart = (item: CartItem) => {
|
||||||
|
addToCart(item);
|
||||||
|
setRecentlyAddedItem(item);
|
||||||
|
setIsCartPopupOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseCartPopup = () => {
|
||||||
|
setIsCartPopupOpen(false);
|
||||||
|
setRecentlyAddedItem(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCourseClick = useCallback((courseId: string) => {
|
||||||
|
prefetchCourseById(courseId, { force: true });
|
||||||
|
navigateTo(`/course/${courseId}`);
|
||||||
|
}, [prefetchCourseById]);
|
||||||
|
|
||||||
|
// Show loading state
|
||||||
|
if (coursesLoading || categoriesLoading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"></div>
|
||||||
|
<p className="text-gray-600">Loading courses...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show error state
|
||||||
|
if (isError) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
|
<div className="text-center">
|
||||||
|
<h2 className="text-2xl font-bold text-red-600 mb-4">Error Loading Courses</h2>
|
||||||
|
<p className="text-gray-600 mb-4">Failed to load courses. Please try again later.</p>
|
||||||
|
<Button onClick={() => window.location.reload()}>Retry</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
{/* Hero Banner */}
|
||||||
|
<section className="relative py-16 overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="absolute inset-0"
|
||||||
|
style={{
|
||||||
|
backgroundImage: 'url(https://images.unsplash.com/photo-1522202176988-66273c2fd55f?w=1200&h=600&fit=crop)',
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-black/80 via-black/60 to-black/40" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative section-margin-x">
|
||||||
|
<div className="max-w-6xl text-left">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.8 }}
|
||||||
|
>
|
||||||
|
<div className="branded-tag-system-white mb-6 justify-start">
|
||||||
|
<div className="dot"></div>
|
||||||
|
<span className="text">DIGITAL LEARNING PLATFORM</span>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-h1-white mb-8" style={{ lineHeight: 'var(--line-height-h1)' }}>
|
||||||
|
Discover Your Leadership<br />Potential Online
|
||||||
|
</h1>
|
||||||
|
<div className="max-w-5xl mb-8">
|
||||||
|
<p className="text-body-lg-white" style={{ lineHeight: '1.7' }}>
|
||||||
|
Our Leadership Courses are structured packages which are targeted towards building your leadership abilities. Each course is a wholesome package which not only helps you gain awareness about your leadership style but also gives insights to build your leadership abilities. Every course contains curated content targeted towards a specific leadership ability. Each course consists of our proprietary profiling instruments – Leadership Profilers, conceptual videos and experiences of leaders – Leadership Webcasts, as well as additional content to supplement learning.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Search and Controls Section */}
|
||||||
|
<section className="py-8" style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
<div className="section-margin-x">
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6">
|
||||||
|
<div className="relative max-w-md flex-1">
|
||||||
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search courses, topics, categories..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
className="pl-10 pr-4 py-3 text-body rounded-lg border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all duration-200 w-full bg-gray-50"
|
||||||
|
style={{
|
||||||
|
fontSize: 'var(--font-body)',
|
||||||
|
fontFamily: 'var(--font-family-base)',
|
||||||
|
height: '48px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex items-center border border-gray-300 rounded-lg overflow-hidden">
|
||||||
|
<button
|
||||||
|
onClick={() => setViewMode('grid')}
|
||||||
|
className={`p-2 transition-colors ${viewMode === 'grid'
|
||||||
|
? 'text-white'
|
||||||
|
: 'bg-white text-gray-600 hover:bg-gray-50'
|
||||||
|
}`}
|
||||||
|
style={{
|
||||||
|
backgroundColor: viewMode === 'grid' ? 'var(--color-primary)' : undefined
|
||||||
|
}}
|
||||||
|
aria-label="Grid view"
|
||||||
|
>
|
||||||
|
<Grid className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setViewMode('list')}
|
||||||
|
className={`p-2 transition-colors ${viewMode === 'list'
|
||||||
|
? 'text-white'
|
||||||
|
: 'bg-white text-gray-600 hover:bg-gray-50'
|
||||||
|
}`}
|
||||||
|
style={{
|
||||||
|
backgroundColor: viewMode === 'list' ? 'var(--color-primary)' : undefined
|
||||||
|
}}
|
||||||
|
aria-label="List view"
|
||||||
|
>
|
||||||
|
<List className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Select value={sortBy || undefined} onValueChange={setSortBy}>
|
||||||
|
<SelectTrigger className="w-40 text-body">
|
||||||
|
<SelectValue placeholder="Sort by" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{sortOptions.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Main Content Section with Sidebar */}
|
||||||
|
<section className="pb-16" style={{ backgroundColor: '#FFFFFF' }}>
|
||||||
|
<div className="section-margin-x">
|
||||||
|
<div className="grid grid-cols-12 gap-8">
|
||||||
|
{/* Left Sidebar - Sticky Filters */}
|
||||||
|
<div className="col-span-12 lg:col-span-3">
|
||||||
|
<div className="sticky top-4">
|
||||||
|
<Card className="bg-white border border-gray-200 rounded-lg shadow-md overflow-hidden">
|
||||||
|
{/* Filter Header */}
|
||||||
|
<div className="bg-gray-50 px-4 py-3 border-b border-gray-200">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="p-1.5 rounded-md" style={{ backgroundColor: 'rgba(4, 4, 91, 0.1)' }}>
|
||||||
|
<Filter className="w-3.5 h-3.5" style={{ color: 'var(--color-primary)' }} />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-body font-semibold text-gray-800">
|
||||||
|
Filters
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
{hasActiveFilters && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={clearAllFilters}
|
||||||
|
className="text-xs px-2 py-1 rounded-md transition-colors filter-clear-btn"
|
||||||
|
>
|
||||||
|
<X className="w-3 h-3 mr-1" />
|
||||||
|
Clear
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filter Content */}
|
||||||
|
<div className="p-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Category Filter */}
|
||||||
|
<div className="filter-section">
|
||||||
|
<label className="block text-small mb-2 font-medium text-gray-700">
|
||||||
|
Category
|
||||||
|
</label>
|
||||||
|
<Select value={selectedCategoryName} onValueChange={handleCategoryChange}>
|
||||||
|
<SelectTrigger className="w-full text-small h-9 border-gray-300 hover:border-gray-400 transition-colors">
|
||||||
|
<SelectValue placeholder="All Categories" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{categories.map((category) => (
|
||||||
|
<SelectItem key={category.name} value={category.name} className="text-small">
|
||||||
|
{category.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Price Range Filter */}
|
||||||
|
<div className="filter-section">
|
||||||
|
<label className="block text-small mb-2 font-medium text-gray-700">
|
||||||
|
Price Range
|
||||||
|
</label>
|
||||||
|
<Select value={selectedPriceRange} onValueChange={setSelectedPriceRange}>
|
||||||
|
<SelectTrigger className="w-full text-small h-9 border-gray-300 hover:border-gray-400 transition-colors">
|
||||||
|
<SelectValue placeholder="All Prices" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{priceRanges.map((priceRange) => (
|
||||||
|
<SelectItem key={priceRange} value={priceRange} className="text-small">
|
||||||
|
{priceRange}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Duration Filter */}
|
||||||
|
<div className="filter-section">
|
||||||
|
<label className="block text-small mb-2 font-medium text-gray-700">
|
||||||
|
Duration
|
||||||
|
</label>
|
||||||
|
<Select value={selectedDuration} onValueChange={setSelectedDuration}>
|
||||||
|
<SelectTrigger className="w-full text-small h-9 border-gray-300 hover:border-gray-400 transition-colors">
|
||||||
|
<SelectValue placeholder="All Durations" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{durations.map((duration) => (
|
||||||
|
<SelectItem key={duration} value={duration} className="text-small">
|
||||||
|
{duration}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Rating Filter */}
|
||||||
|
<div className="filter-section">
|
||||||
|
<label className="block text-small mb-2 font-medium text-gray-700">
|
||||||
|
Rating
|
||||||
|
</label>
|
||||||
|
<Select value={selectedRating} onValueChange={setSelectedRating}>
|
||||||
|
<SelectTrigger className="w-full text-small h-9 border-gray-300 hover:border-gray-400 transition-colors">
|
||||||
|
<SelectValue placeholder="All Ratings" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{ratings.map((rating) => (
|
||||||
|
<SelectItem key={rating} value={rating} className="text-small">
|
||||||
|
{rating}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Content Area */}
|
||||||
|
<div className="col-span-12 lg:col-span-9">
|
||||||
|
<div className="mb-4 text-small text-muted flex justify-between items-center">
|
||||||
|
<span>Showing {currentCourses.length} of {totalCoursesCount} courses</span>
|
||||||
|
{isFetching && (
|
||||||
|
<span className="text-xs text-blue-600 animate-pulse">Updating results...</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Courses Results */}
|
||||||
|
{currentCourses.length === 0 ? (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<p className="text-body-lg text-muted">
|
||||||
|
No courses found matching your criteria.
|
||||||
|
</p>
|
||||||
|
{hasActiveFilters && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={clearAllFilters}
|
||||||
|
className="mt-4"
|
||||||
|
>
|
||||||
|
Clear Filters
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* Grid View */}
|
||||||
|
{viewMode === 'grid' && (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
|
||||||
|
{currentCourses.map((course) => (
|
||||||
|
<div key={course.id} className="w-full">
|
||||||
|
<CourseCard
|
||||||
|
course={course}
|
||||||
|
className="h-[560px] flex flex-col w-full"
|
||||||
|
onClick={() => handleCourseClick(course.id)}
|
||||||
|
onAddToCart={handleAddToCart}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* List View */}
|
||||||
|
{viewMode === 'list' && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{currentCourses.map((course) => (
|
||||||
|
<Card
|
||||||
|
key={course.id}
|
||||||
|
className="overflow-hidden hover:shadow-lg transition-all duration-300 cursor-pointer group"
|
||||||
|
onClick={() => handleCourseClick(course.id)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col md:flex-row">
|
||||||
|
<div className="md:w-80 flex-shrink-0">
|
||||||
|
<div className="aspect-video md:aspect-square w-full bg-gray-100 overflow-hidden relative">
|
||||||
|
<ImageWithFallback
|
||||||
|
src={course.thumbnail}
|
||||||
|
alt={course.title}
|
||||||
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Duration and Level Badge */}
|
||||||
|
<div className="absolute top-2 left-2 flex gap-2">
|
||||||
|
<Badge variant="secondary" className="bg-black/60 text-white text-xs">
|
||||||
|
<Clock className="w-3 h-3 mr-1" />
|
||||||
|
{course.duration}
|
||||||
|
</Badge>
|
||||||
|
<Badge variant="secondary" className="bg-black/60 text-white text-xs">
|
||||||
|
{course.level}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Format Badge */}
|
||||||
|
<div className="absolute top-2 right-2">
|
||||||
|
<Badge variant="outline" className="bg-white text-xs">
|
||||||
|
{course.format}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 p-6 flex flex-col">
|
||||||
|
<div className="flex-1">
|
||||||
|
{/* Category */}
|
||||||
|
<Badge variant="outline" className="mb-3 text-xs border-blue-200 text-blue-700">
|
||||||
|
{course.category}
|
||||||
|
</Badge>
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<h3 className="text-h4 mb-3 group-hover:text-blue-600 transition-colors">
|
||||||
|
{course.title}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<p className="text-body text-gray-600 mb-4 line-clamp-2">
|
||||||
|
{course.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<div className="flex items-center gap-4 mb-4">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Star className="w-4 h-4 text-yellow-400 fill-current" />
|
||||||
|
<span className="text-small font-medium">{course.rating}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Users className="w-4 h-4 text-gray-400" />
|
||||||
|
<span className="text-small text-gray-600">{course.reviews}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom Row - Price and Button */}
|
||||||
|
<div className="flex items-center justify-between pt-4 border-t border-gray-100">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<span className="text-h4 text-gray-900">{course.price}</span>
|
||||||
|
<span className="text-small text-gray-500 line-through">{course.originalPrice}</span>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={(e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleAddToCart({
|
||||||
|
id: course.id,
|
||||||
|
title: course.title,
|
||||||
|
price: course.price,
|
||||||
|
originalPrice: course.originalPrice,
|
||||||
|
thumbnail: course.thumbnail,
|
||||||
|
category: course.category,
|
||||||
|
level: course.level,
|
||||||
|
type: 'course'
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2 hover:bg-blue-50 hover:border-blue-300"
|
||||||
|
>
|
||||||
|
<DollarSign className="w-4 h-4" />
|
||||||
|
Add to Cart
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<div className="flex items-center justify-center gap-2 mt-8">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-4 h-4" />
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
|
||||||
|
<Button
|
||||||
|
key={page}
|
||||||
|
variant={currentPage === page ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setCurrentPage(page)}
|
||||||
|
className="min-w-[40px]"
|
||||||
|
>
|
||||||
|
{page}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setCurrentPage(prev => Math.min(prev + 1, totalPages))}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
<ChevronRight className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Cart Popup */}
|
||||||
|
<CartPopup
|
||||||
|
isOpen={isCartPopupOpen}
|
||||||
|
onClose={handleCloseCartPopup}
|
||||||
|
recentlyAddedItem={recentlyAddedItem}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
175
.gitea/workflows/src/components/Loader.tsx
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface LoaderProps {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Loader: React.FC<LoaderProps> = ({ className = "" }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<style>{`
|
||||||
|
.pl {
|
||||||
|
width: 8em;
|
||||||
|
height: 8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pl circle {
|
||||||
|
transform-box: fill-box;
|
||||||
|
transform-origin: 50% 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pl__ring2 {
|
||||||
|
animation: ring2_ 4s 0.04s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pl__ring4 {
|
||||||
|
animation: ring4_ 4s 0.12s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pl__ring6 {
|
||||||
|
animation: ring6_ 4s 0.2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ring2_ {
|
||||||
|
from {
|
||||||
|
stroke-dashoffset: -329.207488554;
|
||||||
|
transform: rotate(-0.25turn);
|
||||||
|
animation-timing-function: ease-in;
|
||||||
|
}
|
||||||
|
23% {
|
||||||
|
stroke-dashoffset: -82.46680575;
|
||||||
|
transform: rotate(1turn);
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
46%, 50% {
|
||||||
|
stroke-dashoffset: -329.207488554;
|
||||||
|
transform: rotate(2.25turn);
|
||||||
|
animation-timing-function: ease-in;
|
||||||
|
}
|
||||||
|
73% {
|
||||||
|
stroke-dashoffset: -82.46680575;
|
||||||
|
transform: rotate(3.5turn);
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
96%, to {
|
||||||
|
stroke-dashoffset: -329.207488554;
|
||||||
|
transform: rotate(4.75turn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ring4_ {
|
||||||
|
from {
|
||||||
|
stroke-dashoffset: -253.9600625988;
|
||||||
|
transform: rotate(-0.25turn);
|
||||||
|
animation-timing-function: ease-in;
|
||||||
|
}
|
||||||
|
23% {
|
||||||
|
stroke-dashoffset: -63.61725015;
|
||||||
|
transform: rotate(1turn);
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
46%, 50% {
|
||||||
|
stroke-dashoffset: -253.9600625988;
|
||||||
|
transform: rotate(2.25turn);
|
||||||
|
animation-timing-function: ease-in;
|
||||||
|
}
|
||||||
|
73% {
|
||||||
|
stroke-dashoffset: -63.61725015;
|
||||||
|
transform: rotate(3.5turn);
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
96%, to {
|
||||||
|
stroke-dashoffset: -253.9600625988;
|
||||||
|
transform: rotate(4.75turn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ring6_ {
|
||||||
|
from {
|
||||||
|
stroke-dashoffset: -203.795111962;
|
||||||
|
transform: rotate(-0.25turn);
|
||||||
|
animation-timing-function: ease-in;
|
||||||
|
}
|
||||||
|
23% {
|
||||||
|
stroke-dashoffset: -51.05087975;
|
||||||
|
transform: rotate(1turn);
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
46%, 50% {
|
||||||
|
stroke-dashoffset: -203.795111962;
|
||||||
|
transform: rotate(2.25turn);
|
||||||
|
animation-timing-function: ease-in;
|
||||||
|
}
|
||||||
|
73% {
|
||||||
|
stroke-dashoffset: -51.05087975;
|
||||||
|
transform: rotate(3.5turn);
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
96%, to {
|
||||||
|
stroke-dashoffset: -203.795111962;
|
||||||
|
transform: rotate(4.75turn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-center {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
|
||||||
|
<div className={`loader-center ${className}`}>
|
||||||
|
<svg
|
||||||
|
className="pl"
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
{/* Ring 2 */}
|
||||||
|
<circle
|
||||||
|
className="pl__ring2"
|
||||||
|
cx="64"
|
||||||
|
cy="64"
|
||||||
|
r="52.5"
|
||||||
|
fill="none"
|
||||||
|
stroke="hsl(240,92%,19%)"
|
||||||
|
strokeWidth="12"
|
||||||
|
transform="rotate(-90,64,64)"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeDasharray="329.9 329.9"
|
||||||
|
strokeDashoffset="-329.3"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Ring 4 */}
|
||||||
|
<circle
|
||||||
|
className="pl__ring4"
|
||||||
|
cx="64"
|
||||||
|
cy="64"
|
||||||
|
r="37.5"
|
||||||
|
fill="none"
|
||||||
|
stroke="hsl(13,90%,55%)"
|
||||||
|
strokeWidth="9"
|
||||||
|
transform="rotate(-90,64,64)"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeDasharray="254.5 254.5"
|
||||||
|
strokeDashoffset="-254"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Ring 6 */}
|
||||||
|
<circle
|
||||||
|
className="pl__ring6"
|
||||||
|
cx="64"
|
||||||
|
cy="64"
|
||||||
|
r="22.5"
|
||||||
|
fill="none"
|
||||||
|
stroke="hsl(47,99%,49%)"
|
||||||
|
strokeWidth="9"
|
||||||
|
transform="rotate(-90,64,64)"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeDasharray="204.2 204.2"
|
||||||
|
strokeDashoffset="-203.9"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
12
.gitea/workflows/src/components/LogoTicker.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// This component has been consolidated into LogosSection.tsx
|
||||||
|
// All logo ticker functionality is now integrated into the main LogosSection component
|
||||||
|
// with enhanced features including:
|
||||||
|
// - Actual Figma logo assets
|
||||||
|
// - Enhanced scrolling animations
|
||||||
|
// - Responsive design
|
||||||
|
// - Brand guidelines compliance
|
||||||
|
// - Accessibility features
|
||||||
|
|
||||||
|
export default function LogoTicker() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
128
.gitea/workflows/src/components/LogosSection.tsx
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import accenture from "../assets/accenture.svg";
|
||||||
|
import adani from "../assets/adani-logo.svg";
|
||||||
|
import axis from "../assets/axis-bank.svg";
|
||||||
|
import ceat from "../assets/ceat-logo.svg";
|
||||||
|
import hsbc from "../assets/hsbc.svg";
|
||||||
|
import larsen from "../assets/larsen-toubro-logo.svg";
|
||||||
|
import levis from "../assets/levis.svg";
|
||||||
|
import tata from "../assets/tata-motors.svg";
|
||||||
|
import Frame1597884933 from "../imports/Frame1597884933-44-374";
|
||||||
|
|
||||||
|
// Logo data using Frame1597884944 logos with proper dimensions
|
||||||
|
const logoData = [
|
||||||
|
{ src: accenture, name: 'CANMOOR', width: 302, height: 54 }, // CANMOOR
|
||||||
|
{ src: ceat, name: 'BlackRock', width: 210, height: 54 }, // BlackRock
|
||||||
|
{ src: hsbc, name: 'Royal London', width: 145, height: 54 }, // Royal London
|
||||||
|
{ src: adani, name: 'Abstract', width: 172, height: 54 }, // Abstract
|
||||||
|
{ src: larsen, name: 'ARES', width: 163, height: 54 }, // ARES
|
||||||
|
{ src: axis, name: 'KADANS', width: 206, height: 54 }, // KADANS
|
||||||
|
{ src: levis, name: 'levis', width: 206, height: 54 }, // KADANS
|
||||||
|
{ src: tata, name: 'tata', width: 206, height: 54 },
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
// Top row logos - Original 6 unique logos
|
||||||
|
const topRowLogos = logoData.slice(0, 6);
|
||||||
|
|
||||||
|
// Bottom row logos - Repeated 6 logos for variety
|
||||||
|
const bottomRowLogos = logoData.slice(6, 12);
|
||||||
|
|
||||||
|
interface LogoItemProps {
|
||||||
|
logo: typeof logoData[0];
|
||||||
|
index: number;
|
||||||
|
duplicate?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function LogoItem({ logo, index, duplicate = false }: LogoItemProps) {
|
||||||
|
// Scale down larger logos while maintaining aspect ratio
|
||||||
|
const scaledWidth = logo.width > 250 ? Math.round(logo.width * 0.8) : logo.width;
|
||||||
|
const displayWidth = Math.max(120, Math.min(scaledWidth, 250)); // Min 120px, Max 250px
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="logo-ticker-item flex-shrink-0 flex items-center justify-center mx-8"
|
||||||
|
style={{
|
||||||
|
width: `${displayWidth}px`,
|
||||||
|
height: '54px',
|
||||||
|
minWidth: `${displayWidth}px`,
|
||||||
|
}}
|
||||||
|
aria-label={logo.name}
|
||||||
|
role="listitem"
|
||||||
|
tabIndex={duplicate ? -1 : 0}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={logo.src}
|
||||||
|
alt={`${logo.name} logo`}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: 'auto',
|
||||||
|
maxHeight: '54px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
}}
|
||||||
|
className="logo-ticker-item-image"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LogosSection() {
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
className="py-16 overflow-hidden"
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#F7F7FD', // Same as app background
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Reference Frame Component (Hidden - maintains import) */}
|
||||||
|
<div className="hidden" aria-hidden="true">
|
||||||
|
<Frame1597884933 />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Logo Ticker - Two Rows */}
|
||||||
|
<div
|
||||||
|
className="w-full relative"
|
||||||
|
role="region"
|
||||||
|
aria-label="Client logos showcase"
|
||||||
|
>
|
||||||
|
{/* Top Row - Scrolling Left to Right */}
|
||||||
|
<div
|
||||||
|
className="relative h-[54px] overflow-hidden"
|
||||||
|
role="list"
|
||||||
|
aria-label="Client logos row 1"
|
||||||
|
>
|
||||||
|
<div className="flex items-center h-full will-change-transform">
|
||||||
|
<div
|
||||||
|
className="scroll-left flex items-center h-full"
|
||||||
|
style={{
|
||||||
|
width: '400%',
|
||||||
|
gap: '80px',
|
||||||
|
paddingLeft: '40px',
|
||||||
|
paddingRight: '40px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Create multiple sets for seamless infinite scroll */}
|
||||||
|
{[1, 2, 3, 4].map((setNumber) => (
|
||||||
|
<div
|
||||||
|
key={`top-set-${setNumber}`}
|
||||||
|
className="flex items-center h-full"
|
||||||
|
style={{ gap: '80px' }}
|
||||||
|
>
|
||||||
|
{topRowLogos.map((logo, index) => (
|
||||||
|
<LogoItem
|
||||||
|
key={`top-${setNumber}-${index}`}
|
||||||
|
logo={logo}
|
||||||
|
index={index}
|
||||||
|
duplicate={setNumber > 1}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||