Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: revamp iteration one #201

Merged
merged 26 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions components/APIEndpoint/APIEndpoint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ const response: ${responseType} = await ${routeNames.functionName}(params);`;
return (
<Flex direction='column' gap='xl'>
<Breadcrumbs mt='md'>
<Anchor href={`/endpoints/cosmos#${orgName}`}>endpoints</Anchor>
<Anchor href={`/endpoints/cosmos/api/${orgName}/${moduleName}`}>{moduleName}</Anchor>
<Anchor href={`/reference/cosmos#${orgName}`}>endpoints</Anchor>
<Anchor href={`/reference/api/${orgName}/${moduleName}`}>{moduleName}</Anchor>
</Breadcrumbs>
<Title>{functionName}</Title>
<Flex gap='sm' align='center'>
Expand Down
2 changes: 1 addition & 1 deletion components/APIEndpointRoute/APIEndpointRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ const APIEndpointRoute = () => {
if (route.length === 2) {
return (
<Flex direction={'column'} gap='md'>
<Link href={`/endpoints/cosmos#${splitRoutes[1]}`}>
<Link href={`/reference/cosmos#${splitRoutes[1]}`}>
<Button>Back</Button>
</Link>

Expand Down
2 changes: 1 addition & 1 deletion components/APIModule/APIModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import { Card } from 'nextra/components';

export const APIModule = ({ basePaths, prefix }: { basePaths: any[]; prefix: string }) => {
return Object.values(basePaths).map((path) => {
return <Card children={null} icon={null} key={path} title={path.replace(`/${prefix}`, '')} href={`/endpoints/cosmos/api${path}`} />;
return <Card children={null} icon={null} key={path} title={path.replace(`/${prefix}`, '')} href={`/reference/api${path}`} />;
});
};
2 changes: 1 addition & 1 deletion components/APIModulePaths/APIModulePaths.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const APIModulePaths = ({ basePaths, prefix }: { basePaths: any[]; prefix
return (
<Cards>
{Object.values(basePaths).map((path) => {
return <Card key={path} title={path} href={`/endpoints/cosmos/api/${prefix}/${path}`} icon={null} children={null} />;
return <Card key={path} title={path} href={`/reference/api/${prefix}/${path}`} icon={null} children={null} />;
})}
</Cards>
);
Expand Down
55 changes: 55 additions & 0 deletions components/AddressSearch/SeiTraceSearch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useState } from 'react';
import styles from '../../styles/SeiTraceSearch.module.css';

const SeiTraceSearch = () => {
const [address, setAddress] = useState('');
const [error, setError] = useState('');

const isValidAddress = (addr: string) => {
// Accept addresses starting with 'sei' or '0x' and of reasonable length
const seiPattern = /^sei[a-z0-9]{8,}$/i;
const evmPattern = /^0x[a-fA-F0-9]{40}$/;
return seiPattern.test(addr) || evmPattern.test(addr);
};

const getSeiTraceUrl = (addr: string) => {
const chainParam = '?chain=pacific-1';
// For all addresses, use '/address/' path
return `https://seitrace.com/address/${addr}${chainParam}`;
};

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const trimmedAddress = address.trim();
if (trimmedAddress) {
if (isValidAddress(trimmedAddress)) {
const url = getSeiTraceUrl(trimmedAddress);
window.open(url, '_blank');
setError('');
} else {
setError('Please enter a valid Sei or EVM address.');
}
}
};

return (
<form onSubmit={handleSubmit} className={styles.form}>
<div className={styles.inputContainer}>
<input
id="addressInput"
type="text"
placeholder="Enter address"
value={address}
onChange={(e) => setAddress(e.target.value)}
className={`${styles.input} ${error ? styles.error : ''}`}
/>
<button type="submit" className={styles.button}>
View on SEITRACE
</button>
</div>
{error && <div className={styles.errorMessage}>{error}</div>}
</form>
);
};

export default SeiTraceSearch;
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;
}
25 changes: 0 additions & 25 deletions components/DeveloperSurveyCallout/DeveloperSurveyCallout.tsx

This file was deleted.

1 change: 0 additions & 1 deletion components/DeveloperSurveyCallout/index.ts

This file was deleted.

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
Loading