diff --git a/frontend/src/components/RecentSubmissions.tsx b/frontend/src/components/RecentSubmissions.tsx new file mode 100644 index 00000000..3eca417b --- /dev/null +++ b/frontend/src/components/RecentSubmissions.tsx @@ -0,0 +1,236 @@ +import { useState, useEffect } from "react"; +import { useAllSubmissions, useFeedItems } from "../lib/api"; +import { useBotId } from "../lib/config"; +import InfiniteFeed from "../components/InfiniteFeed"; +import SubmissionList from "../components/SubmissionList"; +import { Input } from "../components/ui/input"; +import { Button } from "../components/ui/button"; +import { Filter, Search } from "lucide-react"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "../components/ui/select"; +import { + useFeedFilterStore, + StatusFilterType, + SortOrderType, +} from "../store/useFeedFilterStore"; +import { useParams } from "@tanstack/react-router"; + +interface SubmissionsComponentProps { + className?: string; + feedId?: string; + title?: string; +} + +export default function RecentSubmissions({ + feedId, + className, + title, +}: SubmissionsComponentProps) { + const botId = useBotId(); + + const params = useParams({ from: "/submissions/$feedId" }); + + const currentFeedId = feedId || params.feedId; + + // Get global filter state from Zustand + const { statusFilter, sortOrder, setStatusFilter } = useFeedFilterStore(); + + // Local filter state (before applying) + const [localStatusFilter, setLocalStatusFilter] = + useState(statusFilter); + const [localSortOrder, setLocalSortOrder] = + useState(sortOrder); + const [localPlatform, setLocalPlatform] = useState("twitter"); + + const [showFilters, setShowFilters] = useState(false); + const [searchQuery, setSearchQuery] = useState(""); + const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(""); + + // Debounce search query to avoid excessive filtering + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedSearchQuery(searchQuery); + }, 300); + + return () => clearTimeout(timer); + }, [searchQuery]); + + // Fetch submissions with infinite scroll + // const ITEMS_PER_PAGE = 20; + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } = + useFeedItems(feedId); + + // const { data: items = [] } = useFeedItems(feedId); + + // Get the items from the transformed data + const items = data?.items || []; + + // Sort items based on sort order + const sortedItems = [...items].sort((a, b) => { + const dateA = new Date(a.submittedAt || 0).getTime(); + const dateB = new Date(b.submittedAt || 0).getTime(); + return sortOrder === "newest" ? dateB - dateA : dateA - dateB; + }); + + // Filter items based on search query + const filteredItems = + debouncedSearchQuery.trim() !== "" + ? sortedItems.filter( + (item) => + item.content + ?.toLowerCase() + .includes(debouncedSearchQuery.toLowerCase()) || + item.curatorUsername + ?.toLowerCase() + .includes(debouncedSearchQuery.toLowerCase()), + ) + : sortedItems; + + // Handle search input changes + const handleSearchChange = (e: React.ChangeEvent) => { + setSearchQuery(e.target.value); + }; + + // Toggle filter visibility + const toggleFilters = () => { + setShowFilters(!showFilters); + }; + + // Apply filters + const applyFilters = () => { + // Update global filter state + setStatusFilter(localStatusFilter); + // Update other global filters (would need to add these to the store) + // setSortOrder(localSortOrder); + // setPlatform(localPlatform); + + // Close the filter panel + setShowFilters(false); + }; + + return ( +
+
+

{title}

+
+
+
+ +
+ +
+ +
+
+ + {debouncedSearchQuery.trim() !== "" && ( +

+ Showing results for "{debouncedSearchQuery}" ({filteredItems.length}{" "} + {filteredItems.length === 1 ? "item" : "items"}) +

+ )} + + {showFilters && ( +
+
+
+

Sort By

+ +
+ +
+

Platform

+ +
+ +
+

Status

+ +
+
+ +
+ +
+
+ )} + + ( + + )} + /> +
+ ); +} diff --git a/frontend/src/components/feed/FeedTabs.tsx b/frontend/src/components/feed/FeedTabs.tsx new file mode 100644 index 00000000..ab174492 --- /dev/null +++ b/frontend/src/components/feed/FeedTabs.tsx @@ -0,0 +1,89 @@ +import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/profile-tabs"; +import { + Award, + Coins, + ListFilter, + Newspaper, + Settings2, + UsersRound, + Vote, +} from "lucide-react"; +import FeedContent from "./content"; +import FeedCuration from "./curation"; +import FeedProposals from "./proposals"; +import FeedToken from "./token"; +import FeedPoints from "./points"; +import FeedMembers from "./members"; +import FeedSettings from "./settings"; + +// Tab data structure +const TABS = [ + { + value: "content", + label: "Content", + icon: Newspaper, + component: FeedContent, + }, + { + value: "curation", + label: "Curation", + icon: ListFilter, + component: FeedCuration, + }, + { + value: "proposals", + label: "Proposals", + icon: Vote, + component: FeedProposals, + }, + { + value: "token", + label: "Token", + icon: Coins, + component: FeedToken, + }, + { + value: "points", + label: "Points", + icon: Award, + component: FeedPoints, + }, + { + value: "members", + label: "Members", + icon: UsersRound, + component: FeedMembers, + }, + { + value: "settings", + label: "Settings", + icon: Settings2, + component: FeedSettings, + }, +]; + +export function FeedTabs() { + return ( + <> + + + {TABS.map(({ value, label, icon: Icon }) => ( + + + {label} + + ))} + + + {TABS.map(({ value, component: Component }) => ( + + + + ))} + + + ); +} diff --git a/frontend/src/components/feed/content/index.tsx b/frontend/src/components/feed/content/index.tsx new file mode 100644 index 00000000..1c64168f --- /dev/null +++ b/frontend/src/components/feed/content/index.tsx @@ -0,0 +1,9 @@ +import RecentSubmissions from "../../RecentSubmissions"; + +export default function FeedContent() { + return ( +
+ +
+ ); +} diff --git a/frontend/src/components/feed/curation/index.tsx b/frontend/src/components/feed/curation/index.tsx new file mode 100644 index 00000000..8ec429c5 --- /dev/null +++ b/frontend/src/components/feed/curation/index.tsx @@ -0,0 +1,9 @@ +import RecentSubmissions from "../../RecentSubmissions"; + +export default function FeedCuration() { + return ( +
+ +
+ ); +} diff --git a/frontend/src/components/feed/members/index.tsx b/frontend/src/components/feed/members/index.tsx new file mode 100644 index 00000000..6455d90b --- /dev/null +++ b/frontend/src/components/feed/members/index.tsx @@ -0,0 +1,3 @@ +export default function FeedMembers() { + return
Members
; +} diff --git a/frontend/src/components/feed/points/index.tsx b/frontend/src/components/feed/points/index.tsx new file mode 100644 index 00000000..a54fc0f3 --- /dev/null +++ b/frontend/src/components/feed/points/index.tsx @@ -0,0 +1,3 @@ +export default function FeedPoints() { + return
Points
; +} diff --git a/frontend/src/components/feed/proposals/index.tsx b/frontend/src/components/feed/proposals/index.tsx new file mode 100644 index 00000000..6bff0cb8 --- /dev/null +++ b/frontend/src/components/feed/proposals/index.tsx @@ -0,0 +1,3 @@ +export default function FeedProposals() { + return
Proposals
; +} diff --git a/frontend/src/components/feed/settings/connected/index.tsx b/frontend/src/components/feed/settings/connected/index.tsx new file mode 100644 index 00000000..8d5ada4f --- /dev/null +++ b/frontend/src/components/feed/settings/connected/index.tsx @@ -0,0 +1,12 @@ +import { Button } from "../../../ui/button"; + +export default function ConnectedAccounts() { + return ( +
+
+

Connected Accounts

+ +
+
+ ); +} diff --git a/frontend/src/components/feed/settings/general/index.tsx b/frontend/src/components/feed/settings/general/index.tsx new file mode 100644 index 00000000..8d2641a2 --- /dev/null +++ b/frontend/src/components/feed/settings/general/index.tsx @@ -0,0 +1,3 @@ +export default function GeneralSettings() { + return
General Settings
; +} diff --git a/frontend/src/components/feed/settings/index.tsx b/frontend/src/components/feed/settings/index.tsx new file mode 100644 index 00000000..d7db9122 --- /dev/null +++ b/frontend/src/components/feed/settings/index.tsx @@ -0,0 +1,49 @@ +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "../../ui/profile-tabs"; +import ConnectedAccounts from "./connected"; +import GeneralSettings from "./general"; + +const TABS = [ + { + value: "general", + label: "General", + component: GeneralSettings, + }, + { + value: "connected", + label: "Connected Accounts", + component: ConnectedAccounts, + }, +]; +export default function FeedSettings() { + return ( +
+ + + {TABS.map(({ value, label }) => ( + + {label} + + ))} + + + {TABS.map(({ value, component: Component }) => ( + + + + ))} + +
+ ); +} diff --git a/frontend/src/components/feed/token/index.tsx b/frontend/src/components/feed/token/index.tsx new file mode 100644 index 00000000..78ec918b --- /dev/null +++ b/frontend/src/components/feed/token/index.tsx @@ -0,0 +1,3 @@ +export default function FeedToken() { + return
Token
; +} diff --git a/frontend/src/routes/submissions/$feedId.tsx b/frontend/src/routes/submissions/$feedId.tsx index ede6f6f8..0e1b20cb 100644 --- a/frontend/src/routes/submissions/$feedId.tsx +++ b/frontend/src/routes/submissions/$feedId.tsx @@ -7,6 +7,7 @@ import Header from "../../components/Header"; import { useFilterStore } from "../../store/useFilterStore"; import FeedLayout from "../../components/FeedLayout"; import { ProfileTabs } from "../../components/profile/ProfileTabs"; +import { FeedTabs } from "../../components/feed/FeedTabs"; export const Route = createFileRoute("/submissions/$feedId")({ component: FeedDetailsPage, @@ -66,8 +67,8 @@ function FeedDetailsPage() { Twitter -
-

Posting to:

+
+

Posting to:

- +