Skip to content

Commit

Permalink
revamp ecosystem pg components
Browse files Browse the repository at this point in the history
  • Loading branch information
seiyan-writer committed Jan 20, 2025
1 parent e0e5a03 commit 4a69db2
Show file tree
Hide file tree
Showing 12 changed files with 359 additions and 236 deletions.
67 changes: 32 additions & 35 deletions components/AppCard/AppCard.v2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,43 @@ import { ExternalLinkIcon } from 'lucide-react';
import Image from 'next/image';
import type { EcosystemItem } from '../../data/ecosystemData';

interface AppCardProps {
interface AppCardV2Props {
app: EcosystemItem;
}
const AppCardV2 = ({ app }: AppCardProps) => {

export default function AppCardV2({ app }: AppCardV2Props) {
if (!app) return null;
const fields = app.fieldData;
const { name, logo, link, 'integration-guide-link': integration, 'short-description': shortDescription } = fields;
if (!logo) {
return null;
}
const { name, logo, link, 'short-description': desc, 'integration-guide-link': integration } = app.fieldData;

if (!logo) return null;

return (
<div className='group flex flex-col'>
<div className='border dark:border-gray-800 rounded-lg overflow-hidden flex flex-row lg:flex-col grow h-ull'>
<div className='relative overflow-hidden grid place-items-center aspect-square border-r lg:border-b lg:border-r-0 dark:border-gray-800'>
<a href={link} rel='noopener noreferrer' target='_blank' className='group'>
<Image src={logo.url} alt={`${name} logo`} width={300} height={300} className='transition-all group-hover:scale-[1.15]' />
<div className='group flex flex-col w-[180px] flex-shrink-0 rounded-lg overflow-hidden bg-[#121212] border border-[#2c2c2c] hover:border-[#424242] transition-colors'>
<div className='relative aspect-square flex items-center justify-center bg-black'>
{link ? (
<a href={link} target='_blank' rel='noopener noreferrer'>
<Image src={logo.url} alt={`${name} logo`} width={100} height={100} className='p-4 mx-auto transition-transform group-hover:scale-105' />
</a>
</div>
<div className='px-3 pt-2 pb-3 bg-gray-100 dark:bg-gray-800 w-full flex flex-col grow space-y-1'>
<h3 className='text-lg font-semibold inline-flex items-center gap-2' title={name}>
{name}
</h3>
{shortDescription && (
<p className='opacity-75 text-sm line-clamp-4' title={shortDescription}>
{shortDescription}
</p>
)}
{integration && (
<a
href={integration}
rel='noopener noreferrer'
target='_blank'
className='inline-flex items-center gap-2 bg-black text-white dark:bg-white dark:text-black px-3 py-1 self-start rounded-lg mt-2 text-sm font-medium tracking-tight'>
Integration <ExternalLinkIcon className='inline-block w-3 h-4 hover:underline' />
</a>
)}
</div>
) : (
<Image src={logo.url} alt={`${name} logo`} width={100} height={100} className='p-4 mx-auto' />
)}
</div>
<div className='p-3 text-white flex flex-col gap-1'>
<h3 className='text-sm font-semibold line-clamp-1' title={name}>
{name}
</h3>
{desc && <p className='text-xs text-gray-300 line-clamp-3'>{desc}</p>}
{integration && (
<a
href={integration}
target='_blank'
rel='noopener noreferrer'
className='mt-2 inline-flex items-center gap-1 px-2 py-1 bg-white text-black text-xs rounded hover:opacity-80 transition-opacity'>
Integration
<ExternalLinkIcon className='w-3 h-3' />
</a>
)}
</div>
</div>
);
};

export default AppCardV2;
}
120 changes: 83 additions & 37 deletions components/EcosystemMap/EcosystemDynamicSection.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,85 @@
import { useEffect, useState } from 'react'
import { EcosystemSection, EcosystemSkeleton } from '.'
import {
EcosystemResponse,
getSeiEcosystemAppsData,
} from '../../data/ecosystemData'

const EcosystemDynamicSection = ({ category }: { category: string }) => {
const [apps, setApps] = useState<EcosystemResponse['data'] | null>(null)
const [loading, setLoading] = useState(true)

useEffect(() => {
const fetchData = async () => {
await getSeiEcosystemAppsData()
.then((data) => {
setApps(data.data)
setLoading(false)
})
.catch((error) => {
console.error('Failed to fetch data:', error)
})
.finally(() => {
setLoading(false)
})
}
fetchData()
}, [])

if (!apps || loading) return <EcosystemSkeleton />

// filter out apps that don't have a categorie
const filteredApps = apps.filter((app) => app.fieldData.categorie !== undefined && app.fieldData['categorie-2'] !== undefined);

const appsByCategory = (category: string) =>
filteredApps.filter((app) => app.fieldData.categorie === category)
return <EcosystemSection apps={appsByCategory(category)} />
import { useEffect, useState } from 'react';
import { groupBy } from 'underscore';
import { EcosystemSection, EcosystemSkeleton } from '.';
import { EcosystemItem, EcosystemResponse, getSeiEcosystemAppsData } from '../../data/ecosystemData';

interface EcosystemDynamicSectionProps {
category: string;
searchTerm: string | undefined;
}

export default EcosystemDynamicSection
export default function EcosystemDynamicSection({ category, searchTerm }: EcosystemDynamicSectionProps) {
const [apps, setApps] = useState<EcosystemItem[] | null>(null);
const [loading, setLoading] = useState(true);
const [activeSubCat, setActiveSubCat] = useState<string | null>(null);

useEffect(() => {
(async () => {
try {
const data: EcosystemResponse = await getSeiEcosystemAppsData();
setApps(data.data);
} catch (err) {
console.error('Failed to fetch data:', err);
} finally {
setLoading(false);
}
})();
}, []);

let subCats: string[] = [];
let grouped: Record<string, EcosystemItem[]> = {};

if (apps) {
const catLower = category.toLowerCase();
const categoryApps = apps.filter((app) => (app.fieldData.categorie?.toLowerCase() || '') === catLower);
const normSearch = (searchTerm ?? '').toLowerCase();
const filtered = categoryApps.filter((app) => {
if (!normSearch) return true;
const name = app.fieldData.name?.toLowerCase() || '';
const subCat = app.fieldData['categorie-2']?.toLowerCase() || '';
return name.includes(normSearch) || subCat.includes(normSearch);
});
grouped = groupBy(filtered, (a) => a.fieldData['categorie-2'] || 'Other');
subCats = Object.keys(grouped).sort();
}

useEffect(() => {
if (!apps) return;
if (!activeSubCat && subCats.length > 0) {
setActiveSubCat(subCats[0]);
} else if (activeSubCat && !grouped[activeSubCat]) {
setActiveSubCat(subCats[0] ?? null);
}
}, [apps, subCats, activeSubCat, grouped]);

if (loading) {
return <EcosystemSkeleton />;
}

if (!apps) {
return <EcosystemSkeleton />;
}

const activeApps = activeSubCat && grouped[activeSubCat] ? grouped[activeSubCat] : [];

return (
<div className='mt-4'>
<div className='flex gap-4 overflow-x-auto pb-2 mb-4'>
{subCats.map((sc) => {
const isActive = sc === activeSubCat;
return (
<button
key={sc}
onClick={() => setActiveSubCat(sc)}
className={`whitespace-nowrap px-3 py-1 rounded-sm text-sm font-medium transition-colors ${
isActive ? 'bg-gray-700 text-white' : 'bg-gray-800 text-gray-200 hover:bg-gray-700'
}`}>
{sc}
</button>
);
})}
</div>
<EcosystemSection apps={activeApps} />
</div>
);
}
41 changes: 41 additions & 0 deletions components/EcosystemMap/EcosystemHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';

interface EcosystemHeaderProps {
searchTerm: string;
setSearchTerm: (val: string) => void;
}

export default function EcosystemHeader({ searchTerm, setSearchTerm }: EcosystemHeaderProps) {
return (
<div className='bg-black text-white px-6 py-10'>
<h1 className='text-4xl font-bold'>Sei Ecosystem Map</h1>
<p className='mt-2 max-w-2xl text-gray-300'>
Sei Ecosystem is the epicenter of technological advancement, bringing together creative minds and industry leaders to drive the future of Sei’s blockchain
technology.
</p>
<div className='mt-6 flex flex-wrap gap-4'>
<a
href='/'
className='inline-flex items-center justify-center rounded-md border-2 border-white px-4 py-2 text-white hover:bg-white hover:text-black transition-colors'>
Start Building
</a>
<a
href='https://sei-forms.typeform.com/join-ecosystem?typeform-source=p12rt1ecint.typeform.com'
target='_blank'
rel='noopener noreferrer'
className='inline-flex items-center justify-center rounded-md border-2 border-white px-4 py-2 text-white hover:bg-white hover:text-black transition-colors'>
Join the Ecosystem
</a>
</div>
<div className='mt-8 max-w-md'>
<input
type='search'
placeholder='Search for apps...'
value={searchTerm}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchTerm(e.target.value)}
className='w-full px-4 py-2 bg-black text-white border-2 border-red-600 rounded-md placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-red-600 focus:ring-offset-2 focus:ring-offset-black'
/>
</div>
</div>
);
}
Loading

0 comments on commit 4a69db2

Please sign in to comment.