130 lines
3.8 KiB
TypeScript
130 lines
3.8 KiB
TypeScript
import Head from 'next/head'
|
|
import type { Article } from '@/types/nostr'
|
|
import { ArticleFiltersComponent, type ArticleFilters } from '@/components/ArticleFilters'
|
|
import { CategoryTabs } from '@/components/CategoryTabs'
|
|
import { SearchBar } from '@/components/SearchBar'
|
|
import { ArticlesList } from '@/components/ArticlesList'
|
|
import { AuthorsList } from '@/components/AuthorsList'
|
|
import { PageHeader } from '@/components/PageHeader'
|
|
import { Footer } from '@/components/Footer'
|
|
import type { Dispatch, SetStateAction } from 'react'
|
|
import { t } from '@/lib/i18n'
|
|
|
|
interface HomeViewProps {
|
|
searchQuery: string
|
|
setSearchQuery: Dispatch<SetStateAction<string>>
|
|
selectedCategory: ArticleFilters['category']
|
|
setSelectedCategory: Dispatch<SetStateAction<ArticleFilters['category']>>
|
|
filters: ArticleFilters
|
|
setFilters: Dispatch<SetStateAction<ArticleFilters>>
|
|
articles: Article[]
|
|
allArticles: Article[]
|
|
authors: Article[]
|
|
allAuthors: Article[]
|
|
loading: boolean
|
|
error: string | null
|
|
onUnlock: (article: Article) => void
|
|
unlockedArticles: Set<string>
|
|
}
|
|
|
|
function HomeHead() {
|
|
return (
|
|
<Head>
|
|
<title>zapwall.fr</title>
|
|
<meta
|
|
name="description"
|
|
content="Plateforme de publication d'articles scientifiques et de science-fiction avec sponsoring et rémunération des avis"
|
|
/>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
|
</Head>
|
|
)
|
|
}
|
|
|
|
function ArticlesHero({
|
|
searchQuery,
|
|
setSearchQuery,
|
|
selectedCategory,
|
|
setSelectedCategory,
|
|
}: Pick<HomeViewProps, 'searchQuery' | 'setSearchQuery' | 'selectedCategory' | 'setSelectedCategory'>): React.ReactElement {
|
|
return (
|
|
<div className="mb-8">
|
|
<CategoryTabs selectedCategory={selectedCategory} onCategoryChange={setSelectedCategory} />
|
|
<div className="mb-4">
|
|
<SearchBar value={searchQuery} onChange={setSearchQuery} />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function HomeContent({
|
|
searchQuery,
|
|
setSearchQuery,
|
|
selectedCategory,
|
|
setSelectedCategory,
|
|
filters,
|
|
setFilters,
|
|
articles,
|
|
allArticles,
|
|
authors,
|
|
allAuthors,
|
|
loading,
|
|
error,
|
|
onUnlock,
|
|
unlockedArticles,
|
|
}: HomeViewProps): React.ReactElement {
|
|
const shouldShowFilters = !loading && allArticles.length > 0
|
|
const shouldShowAuthors = selectedCategory !== null && selectedCategory !== 'all'
|
|
|
|
// At startup, we don't know yet if we're loading articles or authors
|
|
// Use a generic loading message until we have content
|
|
const isInitialLoad = loading && allArticles.length === 0 && allAuthors.length === 0
|
|
const articlesListProps = {
|
|
articles,
|
|
allArticles,
|
|
loading: loading && !isInitialLoad, // Don't show loading if it's the initial generic state
|
|
error,
|
|
onUnlock,
|
|
unlockedArticles
|
|
}
|
|
const authorsListProps = { authors, allAuthors, loading: loading && !isInitialLoad, error }
|
|
|
|
return (
|
|
<div className="w-full px-4 py-8">
|
|
<ArticlesHero
|
|
searchQuery={searchQuery}
|
|
setSearchQuery={setSearchQuery}
|
|
selectedCategory={selectedCategory}
|
|
setSelectedCategory={setSelectedCategory}
|
|
/>
|
|
|
|
{shouldShowFilters && !shouldShowAuthors && (
|
|
<ArticleFiltersComponent filters={filters} onFiltersChange={setFilters} articles={allArticles} />
|
|
)}
|
|
|
|
{isInitialLoad ? (
|
|
<div className="text-center py-12">
|
|
<p className="text-cyber-accent/70">{t('common.loading')}</p>
|
|
</div>
|
|
) : shouldShowAuthors ? (
|
|
<AuthorsList {...authorsListProps} />
|
|
) : (
|
|
<ArticlesList {...articlesListProps} />
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function HomeView(props: HomeViewProps): React.ReactElement {
|
|
return (
|
|
<>
|
|
<HomeHead />
|
|
<main className="min-h-screen bg-cyber-darker">
|
|
<PageHeader />
|
|
<HomeContent {...props} />
|
|
<Footer />
|
|
</main>
|
|
</>
|
|
)
|
|
}
|