diff --git a/src/components/ApplicationsShowcase.astro b/src/components/ApplicationsShowcase.astro new file mode 100644 index 00000000..7d9cbe9c --- /dev/null +++ b/src/components/ApplicationsShowcase.astro @@ -0,0 +1,21 @@ +--- +import { ApplicationsShowcase } from './applications/ApplicationsShowcase'; + +// Import data +import applicationsData from '../data/developerhub/applications.json'; +import services from '../data/developerhub/services.json'; +import platforms from '../data/developerhub/platforms.json'; +import deployments from '../data/developerhub/deployments.json'; +import complexities from '../data/developerhub/complexities.json'; + +const applications = applicationsData.applications; +--- + + diff --git a/src/components/applications/ApplicationsShowcase.tsx b/src/components/applications/ApplicationsShowcase.tsx new file mode 100644 index 00000000..99ad0c0c --- /dev/null +++ b/src/components/applications/ApplicationsShowcase.tsx @@ -0,0 +1,642 @@ +import React, { useState, useMemo } from 'react'; + +interface Application { + title: string; + description: string; + url: string; + teaser: string; + services: string[]; + platform: string[]; + deployment: string[]; + tags: string[]; + complexity: string[]; + pro: boolean; + cloudPods: boolean; +} + +interface FilterState { + services: string[]; + platforms: string[]; + deployments: string[]; + complexities: string[]; + showProOnly: boolean; +} + +interface ApplicationsShowcaseProps { + applications: Application[]; + services: Record; + platforms: Record; + deployments: Record; + complexities: { data: Record; order: string[] }; +} + +const ApplicationCard: React.FC<{ + app: Application; + services: Record; + platforms: Record; + deployments: Record; +}> = ({ app, services, platforms, deployments }) => { + return ( +
+
+ {app.title} +
+ {app.pro && Pro} + {app.complexity[0]} +
+
+ +
+

{app.title}

+

{app.description}

+ +
+
+ {app.services.slice(0, 10).map((serviceCode) => ( +
+ {services[serviceCode] +
+ ))} + {app.services.length > 10 && ( +
+{app.services.length - 10}
+ )} +
+ + + View Project → + +
+
+
+ ); +}; + +export const ApplicationsShowcase: React.FC = ({ + applications, + services, + platforms, + deployments, + complexities, +}) => { + const [filters, setFilters] = useState({ + services: [], + platforms: [], + deployments: [], + complexities: [], + showProOnly: false, + }); + + const [searchTerm, setSearchTerm] = useState(''); + const [sortBy, setSortBy] = useState<'title' | 'complexity'>('title'); + + // Get unique values for filters + const uniqueServices = useMemo(() => { + const allServices = new Set(applications.flatMap(app => app.services)); + return Array.from(allServices).sort((a, b) => (services[a] || a).localeCompare(services[b] || b)); + }, [applications, services]); + + const uniquePlatforms = useMemo(() => { + const allPlatforms = new Set(applications.flatMap(app => app.platform)); + return Array.from(allPlatforms).sort((a, b) => (platforms[a] || a).localeCompare(platforms[b] || b)); + }, [applications, platforms]); + + const uniqueDeployments = useMemo(() => { + const allDeployments = new Set(applications.flatMap(app => app.deployment)); + return Array.from(allDeployments).sort((a, b) => (deployments[a] || a).localeCompare(deployments[b] || b)); + }, [applications, deployments]); + + const uniqueComplexities = useMemo(() => { + const allComplexities = new Set(applications.flatMap(app => app.complexity)); + return complexities.order.filter(complexity => allComplexities.has(complexity)); + }, [applications, complexities.order]); + + // Filter and sort applications + const filteredApplications = useMemo(() => { + let filtered = applications.filter(app => { + // Search filter + if (searchTerm) { + const searchLower = searchTerm.toLowerCase(); + const matchesSearch = + app.title.toLowerCase().includes(searchLower) || + app.description.toLowerCase().includes(searchLower) || + app.tags.some(tag => tag.toLowerCase().includes(searchLower)) || + app.services.some(service => (services[service] || service).toLowerCase().includes(searchLower)) || + app.platform.some(platform => (platforms[platform] || platform).toLowerCase().includes(searchLower)); + if (!matchesSearch) return false; + } + + // Other filters + if (filters.services.length > 0 && !filters.services.some(service => app.services.includes(service))) return false; + if (filters.platforms.length > 0 && !filters.platforms.some(platform => app.platform.includes(platform))) return false; + if (filters.deployments.length > 0 && !filters.deployments.some(deployment => app.deployment.includes(deployment))) return false; + if (filters.complexities.length > 0 && !filters.complexities.some(complexity => app.complexity.includes(complexity))) return false; + if (filters.showProOnly && !app.pro) return false; + + return true; + }); + + // Sort applications + return filtered.sort((a, b) => { + if (sortBy === 'title') { + return a.title.localeCompare(b.title); + } else { + const complexityOrder = { basic: 0, intermediate: 1, advanced: 2 }; + const aComplexity = complexityOrder[a.complexity[0] as keyof typeof complexityOrder] ?? 1; + const bComplexity = complexityOrder[b.complexity[0] as keyof typeof complexityOrder] ?? 1; + return aComplexity - bComplexity; + } + }); + }, [applications, filters, searchTerm, sortBy, services, platforms]); + + const toggleFilter = (filterType: keyof FilterState, item: string) => { + if (filterType === 'showProOnly') return; + + setFilters(prev => ({ + ...prev, + [filterType]: prev[filterType].includes(item) + ? prev[filterType].filter(i => i !== item) + : [...prev[filterType], item] + })); + }; + + const clearAllFilters = () => { + setFilters({ + services: [], + platforms: [], + deployments: [], + complexities: [], + showProOnly: false, + }); + setSearchTerm(''); + }; + + const hasActiveFilters = filters.services.length > 0 || + filters.platforms.length > 0 || + filters.deployments.length > 0 || + filters.complexities.length > 0 || + filters.showProOnly || + searchTerm.length > 0; + + return ( + <> + + +
+
+
+ setSearchTerm(e.target.value)} + className="search-input" + /> + {searchTerm && ( + + )} +
+ + + + + + + + + + + + + + {hasActiveFilters && ( + + )} +
+ +
+ {filteredApplications.length} application{filteredApplications.length !== 1 ? 's' : ''} +
+ +
+ {filteredApplications.map((app, index) => ( + + ))} + + {filteredApplications.length === 0 && ( +
+

No applications found

+

Try adjusting your search or filters.

+ +
+ )} +
+
+ + ); +}; \ No newline at end of file diff --git a/src/components/applications/types.ts b/src/components/applications/types.ts new file mode 100644 index 00000000..cd476c7e --- /dev/null +++ b/src/components/applications/types.ts @@ -0,0 +1,21 @@ +export interface Application { + title: string; + description: string; + url: string; + teaser: string; + services: string[]; + deployment: string[]; + platform: string[]; + tags: string[]; + complexity: string[]; + pro: boolean; + cloudPods: boolean; +} + +export interface FilterState { + services: string[]; + platforms: string[]; + deployments: string[]; + complexities: string[]; + showProOnly: boolean; +} \ No newline at end of file diff --git a/src/content/docs/aws/sample-apps.md b/src/content/docs/aws/sample-apps.mdx similarity index 82% rename from src/content/docs/aws/sample-apps.md rename to src/content/docs/aws/sample-apps.mdx index 85ad5063..d54af2b8 100644 --- a/src/content/docs/aws/sample-apps.md +++ b/src/content/docs/aws/sample-apps.mdx @@ -6,5 +6,9 @@ sidebar: order: 4 --- +import ApplicationsShowcase from "../../../components/ApplicationsShowcase.astro"; + # Applications LocalStack Applications provide sample templates to help LocalStack users adopt real-world scenarios to rapidly and conveniently create, configure, and deploy applications locally. These sample applications help you establish your foundations in LocalStack and offer you a wide range of use cases and scenarios, all supported by LocalStack, to help you develop and test cloud applications efficiently. + +