Advertisement

← vibesdk

src/routes/discover/index.tsx

4.0 KB Download file
import { useNavigate, useSearchParams } from 'react-router';
import { motion } from 'framer-motion';
import { usePaginatedApps } from '@/hooks/use-paginated-apps';
import { AppListContainer } from '@/components/shared/AppListContainer';
import { AppFiltersForm } from '@/components/shared/AppFiltersForm';
import { AppSortTabs } from '@/components/shared/AppSortTabs';
import type { AppSortOption } from '@/api-types';
import { GlobeIcon } from '@phosphor-icons/react';
import { BrandEmphasisIcon } from '@/components/shared/BrandEmphasisIcon';

export default function DiscoverPage() {
	const navigate = useNavigate();
	const [searchParams, setSearchParams] = useSearchParams();

	// Derive initial sort from URL or localStorage, fallback to 'popular'
	const allowedSorts: AppSortOption[] = [
		'recent',
		'popular',
		'trending',
		'starred',
	];
	const sortParam = searchParams.get('sort') as AppSortOption | null;
	const savedSort = (
		typeof localStorage !== 'undefined'
			? localStorage.getItem('discover.sort')
			: null
	) as AppSortOption | null;
	const initialSort: AppSortOption =
		sortParam && allowedSorts.includes(sortParam)
			? sortParam
			: savedSort && allowedSorts.includes(savedSort)
				? savedSort
				: 'popular';

	const {
		// Filter state
		searchQuery,
		setSearchQuery,
		filterFramework,
		sortBy,
		period,

		// Data state
		apps,
		loading,
		loadingMore,
		error,
		totalCount,
		hasMore,

		// Form handlers
		handleSearchSubmit,
		handlePeriodChange,
		handleFrameworkChange,

		handleSortChange,

		// Pagination handlers

		refetch,
		loadMore,
	} = usePaginatedApps({
		type: 'public',
		defaultSort: initialSort,
		defaultPeriod: 'week',
		limit: 20,
	});

	return (
		<div className="size-full">
			<title>Discover - Build</title>
			<div className="container max-w-6xl mx-auto px-4 py-8">
				<motion.div
					initial={{ opacity: 0, y: -20 }}
					animate={{ opacity: 1, y: 0 }}
					transition={{ duration: 0.5 }}
				>
					{/* Header */}
					<div className="mb-8 mt-6">
						<h1 className="flex items-center gap-2 text-6xl font-funky-mono font-bold mb-3 text-brand-emphasis">
							<BrandEmphasisIcon
								icon={GlobeIcon}
								weight="duotone"
								className="-rotate-30"
							/>
							Discover
						</h1>
						<p className="text-kumo-subtle text-lg font-funky-mono tracking-tight">
							Explore apps built by the community
						</p>
					</div>

					<div className="flex flex-wrap items-start gap-3 justify-between mb-8">
						{/* Search and Filters */}
						<AppFiltersForm
							searchQuery={searchQuery}
							onSearchChange={setSearchQuery}
							onSearchSubmit={handleSearchSubmit}
							searchPlaceholder="Search apps..."
							showSearchButton={true}
							filterFramework={filterFramework}
							period={period}
							onFrameworkChange={handleFrameworkChange}
							onPeriodChange={handlePeriodChange}
							sortBy={sortBy}
						/>

						{/* Sort Tabs */}
						<AppSortTabs
							value={sortBy}
							onValueChange={(v) => {
								handleSortChange(v);
								// Persist to URL and localStorage
								try {
									localStorage.setItem('discover.sort', v);
								} catch {
									console.error(
										'Failed to persist sort to localStorage',
									);
								}
								const next = new URLSearchParams(searchParams);
								next.set('sort', v);
								setSearchParams(next, { replace: true });
							}}
							availableSorts={[
								'recent',
								'popular',
								'trending',
								'starred',
							]}
						/>
					</div>

					{/* Unified App List */}
					<AppListContainer
						apps={apps}
						loading={loading}
						loadingMore={loadingMore}
						error={error}
						hasMore={hasMore}
						totalCount={totalCount}
						sortBy={sortBy}
						onAppClick={(appId) => navigate(`/app/${appId}`)}
						onLoadMore={loadMore}
						onRetry={refetch}
						showUser={true}
						showStats={true}
						infiniteScroll={true}
					/>
				</motion.div>
			</div>
		</div>
	);
}