Tech4biz-channel/Channel-Frontend/src/pages/EcosystemPage.tsx
2026-07-17 15:05:23 +05:30

222 lines
9.5 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
Code,
Briefcase,
Shield,
Cloud,
Globe,
ExternalLink,
Layers
} from 'lucide-react';
import { PageHeader } from '../components/ui/PageHeader';
import { PageLayout } from '../components/layout/PageLayout';
import { getEcosystemOfferings } from '../services/ecosystem-api';
import type { EcosystemOffering } from '../services/ecosystem-api';
import { BrandLogo } from './admin/EcosystemManagerPage';
const iconMap: Record<string, any> = {
Code,
Briefcase,
Shield,
Cloud,
Globe
};
export const EcosystemPage: React.FC = () => {
const [offerings, setOfferings] = useState<EcosystemOffering[]>([]);
const [loading, setLoading] = useState(true);
const [filter, setFilter] = useState<'ALL' | 'PRODUCT' | 'SERVICE'>('ALL');
useEffect(() => {
const fetchData = async () => {
try {
const data = await getEcosystemOfferings();
setOfferings(data);
} catch (err) {
console.error('Failed to load ecosystem data:', err);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
const filteredOfferings = offerings.filter(o => {
if (filter === 'ALL') return true;
return o.type === filter;
});
const headerNode = (
<PageHeader
title="Ecosystem Explorer"
subtitle="Unlock access to Tech4Biz's complete network of industry-leading products, platforms, and specialized services."
/>
);
return (
<PageLayout header={headerNode}>
<div className="p-6 space-y-8">
{/* Segmented controls filter */}
<div className="flex justify-between items-center border-b border-ink-200 pb-4">
<div className="flex bg-ink-100 p-0.5 rounded-xl border border-ink-200 overflow-x-auto max-w-full">
{(['ALL', 'PRODUCT', 'SERVICE'] as const).map((type) => {
const labels: Record<string, string> = {
ALL: 'All Solutions',
PRODUCT: 'Products',
SERVICE: 'Services'
};
return (
<button
key={type}
onClick={() => setFilter(type)}
className={`px-4 py-2 rounded-lg text-xs font-black tracking-wider uppercase transition-all duration-300 whitespace-nowrap cursor-pointer ${
filter === type
? 'bg-ink-0 text-ink-900 shadow-sm border border-ink-200/50'
: 'text-ink-500 hover:text-ink-900'
}`}
>
{labels[type]}
</button>
);
})}
</div>
<div className="text-[10px] font-bold text-ink-400 uppercase tracking-widest hidden sm:block">
{filteredOfferings.length} Solutions Available
</div>
</div>
{loading ? (
<div className="flex flex-1 items-center justify-center py-20">
<div className="w-8 h-8 border-4 border-ink-900/30 border-t-ink-900 rounded-full animate-spin"></div>
</div>
) : (
<AnimatePresence mode="wait">
<motion.div
key="offerings-grid"
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -15 }}
transition={{ duration: 0.25 }}
className="grid grid-cols-1 md:grid-cols-2 gap-8"
>
{filteredOfferings.map((offering) => {
const IconComponent = iconMap[offering.logoIcon] || Globe;
const isProduct = offering.type === 'PRODUCT';
return (
<div
key={offering.id}
className="group flex flex-col justify-between bg-ink-0 border border-ink-200 hover:border-ink-450 rounded-3xl p-6 md:p-8 hover:shadow-xl transition-all duration-500"
>
<div>
{/* Logo and Badges */}
<div className="flex justify-between items-center mb-6">
<div className="h-10 flex items-center shrink-0">
{offering.logoUrl ? (
<BrandLogo name={offering.logoUrl} className="max-h-7 max-w-[150px] object-contain text-ink-900 dark:text-ink-0" />
) : (
<div className="w-10 h-10 rounded-xl flex items-center justify-center text-ink-0 bg-ink-900 shadow-md">
<IconComponent className="w-5 h-5" />
</div>
)}
</div>
<span className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-[9px] font-black tracking-widest uppercase border ${
isProduct
? 'bg-blue-500/10 text-blue-600 border-blue-500/20'
: 'bg-emerald-500/10 text-emerald-600 border-emerald-500/20'
}`}>
<Layers className="w-3 h-3" />
{offering.type}
</span>
</div>
{/* Info Copy */}
<div className="space-y-3">
<h3 className="text-lg font-black tracking-tight text-ink-900">
{offering.name}
</h3>
<p className="text-xs font-bold text-ink-700 leading-snug">
{offering.tagline}
</p>
<p className="text-xs font-medium leading-relaxed text-ink-500 pt-1">
{offering.description}
</p>
</div>
{/* Key Benefits */}
{offering.benefits && offering.benefits.length > 0 && (
<div className="pt-6 space-y-2">
<span className="text-[10px] font-extrabold uppercase tracking-widest text-ink-400">Key Benefits</span>
<ul className="grid grid-cols-1 gap-2.5 pt-1">
{offering.benefits.map((benefit, bIdx) => (
<li key={bIdx} className="flex items-start gap-2.5 text-xs font-medium text-ink-600">
<span className={`w-1.5 h-1.5 rounded-full shrink-0 mt-1.5 ${
isProduct ? 'bg-blue-500' : 'bg-emerald-500'
}`} />
<span>{benefit}</span>
</li>
))}
</ul>
</div>
)}
{/* Optional Media (GIF, Video, Image) */}
{offering.mediaUrl && offering.mediaType && offering.mediaType !== 'NONE' && (
<div className="mt-6 border border-ink-200 rounded-2xl overflow-hidden bg-ink-50 relative aspect-video shadow-sm">
{offering.mediaType === 'VIDEO' ? (
offering.mediaUrl.includes('youtube.com') || offering.mediaUrl.includes('youtube-nocookie.com') || offering.mediaUrl.includes('vimeo.com') ? (
<iframe
src={offering.mediaUrl}
title={`${offering.name} Video Preview`}
className="w-full h-full border-0 absolute inset-0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
) : (
<video
src={offering.mediaUrl}
controls
muted
loop
playsInline
className="w-full h-full object-cover"
/>
)
) : (
<img
src={offering.mediaUrl}
alt={`${offering.name} Screenshot`}
className="w-full h-full object-cover group-hover:scale-102 transition-transform duration-700"
/>
)}
</div>
)}
</div>
{/* CTA Button */}
<div className="pt-8 mt-auto">
<a
href={offering.websiteUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center justify-center gap-2 w-full px-5 py-3 rounded-xl text-xs font-black tracking-wider uppercase bg-ink-900 text-ink-0 hover:bg-ink-800 hover:shadow-lg transition-all duration-300 cursor-pointer"
>
<span>{offering.ctaText || 'Explore Solution'}</span>
<ExternalLink className="w-3.5 h-3.5" />
</a>
</div>
</div>
);
})}
</motion.div>
</AnimatePresence>
)}
</div>
</PageLayout>
);
};
export default EcosystemPage;