diff --git a/src/app/(app)/stls/_components/creator-filter.tsx b/src/app/(app)/stls/_components/creator-filter.tsx
new file mode 100644
index 0000000..e744bc3
--- /dev/null
+++ b/src/app/(app)/stls/_components/creator-filter.tsx
@@ -0,0 +1,82 @@
+"use client";
+
+import { useState } from "react";
+import { Check, ChevronsUpDown } from "lucide-react";
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/components/ui/command";
+import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
+
+interface CreatorFilterProps {
+ creators: string[];
+ value: string; // active creator, "" when none
+ onChange: (creator: string) => void; // "" clears the filter
+}
+
+export function CreatorFilter({ creators, value, onChange }: CreatorFilterProps) {
+ const [open, setOpen] = useState(false);
+
+ return (
+
+
+
+
+
+
+
+
+ No creators found.
+
+ {
+ onChange("");
+ setOpen(false);
+ }}
+ >
+
+ All Creators
+
+ {creators.map((creator) => (
+ {
+ onChange(creator);
+ setOpen(false);
+ }}
+ >
+
+ {creator}
+
+ ))}
+
+
+
+
+
+ );
+}
diff --git a/src/app/(app)/stls/_components/stl-table.tsx b/src/app/(app)/stls/_components/stl-table.tsx
index 9235e4d..d914bb0 100644
--- a/src/app/(app)/stls/_components/stl-table.tsx
+++ b/src/app/(app)/stls/_components/stl-table.tsx
@@ -3,7 +3,7 @@
import { useState, useCallback, useTransition, useMemo, useRef } from "react";
import { useRouter, usePathname, useSearchParams } from "next/navigation";
import { toast } from "sonner";
-import { Search, Layers, Upload } from "lucide-react";
+import { Search, Layers, Upload, Send } from "lucide-react";
import { UploadDialog } from "./upload-dialog";
import { useDataTable } from "@/hooks/use-data-table";
import {
@@ -16,6 +16,7 @@ import {
import { PackageFilesDrawer } from "./package-files-drawer";
import { IngestionStatus } from "./ingestion-status";
import { SkippedPackagesTab } from "./skipped-packages-tab";
+import { CreatorFilter } from "./creator-filter";
import { DataTable } from "@/components/shared/data-table";
import { DataTablePagination } from "@/components/shared/data-table-pagination";
import { DataTableViewOptions } from "@/components/shared/data-table-view-options";
@@ -49,6 +50,7 @@ import {
createGroupAction,
removeFromGroupAction,
sendAllInGroupAction,
+ sendAllFromCreatorAction,
updateGroupPreviewAction,
mergeGroupsAction,
} from "../actions";
@@ -59,6 +61,7 @@ interface StlTableProps {
totalCount: number;
ingestionStatus: IngestionAccountStatus[];
availableTags: string[];
+ availableCreators: string[];
searchTerm: string;
skippedData: SkippedRow[];
skippedPageCount: number;
@@ -74,6 +77,7 @@ export function StlTable({
totalCount,
ingestionStatus,
availableTags,
+ availableCreators,
searchTerm,
skippedData,
skippedPageCount,
@@ -85,6 +89,7 @@ export function StlTable({
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
+ const activeCreator = searchParams.get("creator") ?? "";
const [searchValue, setSearchValue] = useState(searchParams.get("search") ?? "");
const [viewPkg, setViewPkg] = useState(null);
@@ -207,6 +212,20 @@ export function StlTable({
[router, pathname, searchParams]
);
+ const updateCreatorFilter = useCallback(
+ (value: string) => {
+ const params = new URLSearchParams(searchParams.toString());
+ if (value) {
+ params.set("creator", value);
+ params.set("page", "1");
+ } else {
+ params.delete("creator");
+ }
+ router.push(`${pathname}?${params.toString()}`, { scroll: false });
+ },
+ [router, pathname, searchParams]
+ );
+
const activeTab = searchParams.get("tab") ?? "packages";
const updateTab = useCallback(
@@ -277,6 +296,23 @@ export function StlTable({
[router]
);
+ const handleSendAllFromCreator = useCallback(() => {
+ if (!confirm(`Send all packages from "${activeCreator}" to your Telegram?`)) return;
+ startTransition(async () => {
+ const result = await sendAllFromCreatorAction(activeCreator);
+ if (result.success) {
+ const { queued, skipped } = result.data;
+ toast.success(
+ `Queued ${queued} package${queued === 1 ? "" : "s"} from ${activeCreator}` +
+ (skipped ? ` (${skipped} already queued)` : "")
+ );
+ router.refresh();
+ } else {
+ toast.error(result.error);
+ }
+ });
+ }, [activeCreator, router]);
+
const handleRemoveFromGroup = useCallback(
(packageId: string) => {
startTransition(async () => {
@@ -500,11 +536,29 @@ export function StlTable({
)}
+ {availableCreators.length > 0 && (
+
+ )}
+ {activeCreator && (
+
+ )}
{selectedPackages.size >= 2 && (