Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 236 additions & 0 deletions frontend/src/components/RecentSubmissions.tsx
Original file line number Diff line number Diff line change
@@ -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<StatusFilterType>(statusFilter);
const [localSortOrder, setLocalSortOrder] =
useState<SortOrderType>(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<HTMLInputElement>) => {
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 (
<div className={`flex flex-col gap-6 w-full ${className}`}>
<div className="flex md:flex-row flex-col justify-between items-center gap-6">
<h1 className="text-[24px] leading-[63px] font-normal">{title}</h1>
<div className="flex gap-3 items-center">
<div className="relative flex-grow">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none max-h-[40px]">
<Search className="h-4 w-4 text-gray-400" />
</div>
<Input
type="text"
placeholder="Search"
className="pl-9"
value={searchQuery}
onChange={handleSearchChange}
/>
</div>
<Button
variant={"outline"}
className="min-h-[40px]"
onClick={toggleFilters}
>
<Filter className="mr-2 h-4 w-4" />
Filters
</Button>
</div>
</div>

{debouncedSearchQuery.trim() !== "" && (
<p className="text-sm text-gray-600">
Showing results for "{debouncedSearchQuery}" ({filteredItems.length}{" "}
{filteredItems.length === 1 ? "item" : "items"})
</p>
)}

{showFilters && (
<div className="p-4 border rounded-md w-full gap-3 flex flex-col">
<div className="flex md:flex-row flex-col w-full justify-between items-center gap-6">
<div className="w-full">
<p className="text-sm font-medium">Sort By</p>
<Select
value={localSortOrder}
onValueChange={(val) => setLocalSortOrder(val as SortOrderType)}
>
<SelectTrigger className="bg-white">
<SelectValue placeholder="Most Recent" />
</SelectTrigger>
<SelectContent>
<SelectItem value="newest">Most Recent</SelectItem>
<SelectItem value="oldest">Oldest</SelectItem>
</SelectContent>
</Select>
</div>

<div className="w-full">
<p className="text-sm font-medium">Platform</p>
<Select value={localPlatform} onValueChange={setLocalPlatform}>
<SelectTrigger className="bg-white">
<SelectValue placeholder="Twitter" />
</SelectTrigger>
<SelectContent>
<SelectItem value="twitter">Twitter</SelectItem>
<SelectItem value="instagram">Instagram</SelectItem>
<SelectItem value="facebook">Facebook</SelectItem>
</SelectContent>
</Select>
</div>

<div className="w-full">
<p className="text-sm font-medium">Status</p>
<Select
value={localStatusFilter}
onValueChange={(val) =>
setLocalStatusFilter(val as StatusFilterType)
}
>
<SelectTrigger className="bg-white">
<SelectValue placeholder="All" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All</SelectItem>
<SelectItem value="pending">Pending</SelectItem>
<SelectItem value="approved">Approved</SelectItem>
<SelectItem value="rejected">Rejected</SelectItem>
</SelectContent>
</Select>
</div>
</div>

<div className="flex justify-end">
<Button
className="bg-black text-white hover:bg-gray-800"
onClick={applyFilters}
>
Apply Filters
</Button>
</div>
</div>
)}

<InfiniteFeed
items={filteredItems}
fetchNextPage={fetchNextPage}
hasNextPage={hasNextPage && debouncedSearchQuery.trim() === ""}
isFetchingNextPage={isFetchingNextPage}
status={status}
loadingMessage="Loading more submissions..."
noMoreItemsMessage="No more submissions to load"
initialLoadingMessage="Loading submissions..."
renderItems={(items) => (
<SubmissionList
items={items}
statusFilter={statusFilter}
botId={botId}
feedId={currentFeedId}
/>
)}
/>
</div>
);
}
89 changes: 89 additions & 0 deletions frontend/src/components/feed/FeedTabs.tsx
Original file line number Diff line number Diff line change
@@ -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
defaultValue={"content"}
className="w-full justify-between space-y-[30px]"
>
<TabsList className="overflow-x-auto w-full justify-between">
{TABS.map(({ value, label, icon: Icon }) => (
<TabsTrigger key={value} value={value}>
<Icon strokeWidth={1} size={24} />
{label}
</TabsTrigger>
))}
</TabsList>

{TABS.map(({ value, component: Component }) => (
<TabsContent key={value} value={value} className="w-full">
<Component />
</TabsContent>
))}
</Tabs>
</>
);
}
9 changes: 9 additions & 0 deletions frontend/src/components/feed/content/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import RecentSubmissions from "../../RecentSubmissions";

export default function FeedContent() {
return (
<div>
<RecentSubmissions title="Recent Content" />
</div>
);
}
9 changes: 9 additions & 0 deletions frontend/src/components/feed/curation/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import RecentSubmissions from "../../RecentSubmissions";

export default function FeedCuration() {
return (
<div>
<RecentSubmissions title="Recent Curation" />
</div>
);
}
3 changes: 3 additions & 0 deletions frontend/src/components/feed/members/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function FeedMembers() {
return <div>Members</div>;
}
3 changes: 3 additions & 0 deletions frontend/src/components/feed/points/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function FeedPoints() {
return <div>Points</div>;
}
3 changes: 3 additions & 0 deletions frontend/src/components/feed/proposals/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function FeedProposals() {
return <div>Proposals</div>;
}
12 changes: 12 additions & 0 deletions frontend/src/components/feed/settings/connected/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Button } from "../../../ui/button";

export default function ConnectedAccounts() {
return (
<div>
<div className="flex justify-between items-center w-full">
<h3 className="text-2xl font-light">Connected Accounts</h3>
<Button>Connect Account</Button>
</div>
</div>
);
}
3 changes: 3 additions & 0 deletions frontend/src/components/feed/settings/general/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function GeneralSettings() {
return <div>General Settings</div>;
}
Loading
Loading