114 lines
5.1 KiB
TypeScript
114 lines
5.1 KiB
TypeScript
import React, { useEffect, useMemo, useState } from "react";
|
||
import {
|
||
View, Text, ScrollView,
|
||
ActivityIndicator, useColorScheme, TouchableOpacity,
|
||
} from "react-native";
|
||
import { SafeAreaView } from "react-native-safe-area-context";
|
||
import SearchBar from "../components/SearchBar";
|
||
import CollapsibleCategory, { CollapsibleCategoryProps } from "../components/CollapsibleCategory";
|
||
import { entriesApi } from "../services/api";
|
||
|
||
// Cycles through as many groups as the API returns
|
||
const PALETTE: Pick<CollapsibleCategoryProps, "icon" | "color" | "darkColor">[] = [
|
||
{ icon: "book-outline", color: "#3B7DD8", darkColor: "#5E9EF4" },
|
||
{ icon: "book-outline", color: "#2E9E6B", darkColor: "#4EC992" },
|
||
{ icon: "book-outline", color: "#9B4FB8", darkColor: "#C47CE0" },
|
||
{ icon: "book-outline", color: "#C4622D", darkColor: "#E07B45" },
|
||
{ icon: "book-outline", color: "#B8834F", darkColor: "#D4A574" },
|
||
{ icon: "book-outline", color: "#2E7D9E", darkColor: "#4EA8C9" },
|
||
];
|
||
|
||
export default function AllFeed() {
|
||
const dark = useColorScheme() === "dark";
|
||
|
||
const [groups, setGroups] = useState<string[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState("");
|
||
const [query, setQuery] = useState("");
|
||
|
||
const loadGroups = () => {
|
||
setLoading(true);
|
||
setError("");
|
||
entriesApi.getGroups()
|
||
.then(setGroups)
|
||
.catch(() => setError("Couldn't load groups."))
|
||
.finally(() => setLoading(false));
|
||
};
|
||
|
||
useEffect(() => { loadGroups(); }, []);
|
||
|
||
const visibleGroups = useMemo(() => {
|
||
if (!query.trim()) return groups;
|
||
const lower = query.toLowerCase();
|
||
return groups.filter((g) => g.toLowerCase().includes(lower));
|
||
}, [query, groups]);
|
||
|
||
return (
|
||
<SafeAreaView className={`flex-1 ${dark ? "bg-obsidian-200" : "bg-parchment-50"}`}>
|
||
{/* Header */}
|
||
<View className={`border-b ${dark ? "border-border-dark" : "border-border-light"}`}>
|
||
<View className="px-4 pt-2 pb-1">
|
||
<Text className={`text-2xl font-display font-bold ${dark ? "text-ink-dark" : "text-ink-light"}`}>
|
||
Discover
|
||
</Text>
|
||
<Text className={`text-xs mt-0.5 font-sans ${dark ? "text-muted-dark" : "text-muted-light"}`}>
|
||
{loading ? "Loading…" : `${groups.length} groups`}
|
||
</Text>
|
||
</View>
|
||
<SearchBar placeholder="Search groups…" onSearch={setQuery} />
|
||
</View>
|
||
|
||
{/* States */}
|
||
{loading ? (
|
||
<View className="flex-1 items-center justify-center">
|
||
<ActivityIndicator color={dark ? "#E07B45" : "#C4622D"} />
|
||
</View>
|
||
|
||
) : error ? (
|
||
<View className="flex-1 items-center justify-center px-8">
|
||
<Text className="text-4xl mb-3">⚠️</Text>
|
||
<Text className={`text-sm font-sans text-center mb-4 ${dark ? "text-muted-dark" : "text-muted-light"}`}>
|
||
{error}
|
||
</Text>
|
||
<TouchableOpacity
|
||
onPress={loadGroups}
|
||
className="px-5 py-2.5 rounded-xl"
|
||
style={{ backgroundColor: dark ? "#E07B45" : "#C4622D" }}
|
||
>
|
||
<Text className="text-sm font-sans font-semibold text-white">Retry</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
|
||
) : (
|
||
<ScrollView
|
||
contentContainerStyle={{ paddingTop: 16, paddingBottom: 40 }}
|
||
showsVerticalScrollIndicator={false}
|
||
keyboardDismissMode="on-drag"
|
||
>
|
||
{visibleGroups.length === 0 ? (
|
||
<View className="items-center mt-16 px-8">
|
||
<Text className="text-4xl mb-3">🔍</Text>
|
||
<Text className={`text-base font-sans font-semibold text-center ${dark ? "text-ink-dark" : "text-ink-light"}`}>
|
||
No groups match "{query}"
|
||
</Text>
|
||
<Text className={`text-sm font-sans text-center mt-1 ${dark ? "text-muted-dark" : "text-muted-light"}`}>
|
||
Try a different search term.
|
||
</Text>
|
||
</View>
|
||
) : (
|
||
visibleGroups.map((groupName, index) => (
|
||
<CollapsibleCategory
|
||
key={groupName}
|
||
id={groupName}
|
||
label={groupName}
|
||
groupName={groupName}
|
||
defaultOpen={index === 0}
|
||
{...PALETTE[index % PALETTE.length]}
|
||
/>
|
||
))
|
||
)}
|
||
</ScrollView>
|
||
)}
|
||
</SafeAreaView>
|
||
);
|
||
} |