Skip to content

Commit 5eaafbd

Browse files
feat: Add filter persistence with settings integration (#78)
* feat: Add settings modal with GitHub PAT and filter toggle - Add GeneralSettingsModal with General and GitHub tabs - Create GitHub PAT input field that saves to .env as GITHUB_TOKEN - Add animated toggle component for SAVE_FILTER setting - Create API endpoints for settings management - Add Input and Toggle UI components - Implement smooth animations for toggle interactions - Add proper error handling and user feedback * feat: Add filter persistence with settings integration - Add filter persistence system that saves user filter preferences to .env - Create FILTERS variable in .env to store complete filter state as JSON - Add SAVE_FILTER toggle in settings to enable/disable persistence - Implement auto-save functionality with 500ms debounce - Add loading states and visual feedback for filter restoration - Create API endpoints for managing saved filters - Add filter management UI in settings modal - Support for search query, type filters, sort order, and updatable status - Seamless integration across all script tabs (Available, Downloaded, Installed) - Auto-clear saved filters when persistence is disabled
1 parent 92f78c7 commit 5eaafbd

File tree

15 files changed

+895
-92
lines changed

15 files changed

+895
-92
lines changed

.env.example

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,8 @@ ALLOWED_SCRIPT_PATHS="scripts/"
1616

1717
# WebSocket Configuration
1818
WEBSOCKET_PORT="3001"
19-
GITHUB_TOKEN=your_github_token_here
19+
20+
# User settings
21+
GITHUB_TOKEN=
22+
SAVE_FILTER=false
23+
FILTERS=

public/favicon.png

1.57 KB
Loading

src/app/_components/DownloadedScriptsTab.tsx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export function DownloadedScriptsTab() {
2020
sortBy: 'name',
2121
sortOrder: 'asc',
2222
});
23+
const [saveFiltersEnabled, setSaveFiltersEnabled] = useState(false);
24+
const [isLoadingFilters, setIsLoadingFilters] = useState(true);
2325
const gridRef = useRef<HTMLDivElement>(null);
2426

2527
const { data: scriptCardsData, isLoading: githubLoading, error: githubError, refetch } = api.scripts.getScriptCardsWithCategories.useQuery();
@@ -29,6 +31,62 @@ export function DownloadedScriptsTab() {
2931
{ enabled: !!selectedSlug }
3032
);
3133

34+
// Load SAVE_FILTER setting and saved filters on component mount
35+
useEffect(() => {
36+
const loadSettings = async () => {
37+
try {
38+
// Load SAVE_FILTER setting
39+
const saveFilterResponse = await fetch('/api/settings/save-filter');
40+
let saveFilterEnabled = false;
41+
if (saveFilterResponse.ok) {
42+
const saveFilterData = await saveFilterResponse.json();
43+
saveFilterEnabled = saveFilterData.enabled ?? false;
44+
setSaveFiltersEnabled(saveFilterEnabled);
45+
}
46+
47+
// Load saved filters if SAVE_FILTER is enabled
48+
if (saveFilterEnabled) {
49+
const filtersResponse = await fetch('/api/settings/filters');
50+
if (filtersResponse.ok) {
51+
const filtersData = await filtersResponse.json();
52+
if (filtersData.filters) {
53+
setFilters(filtersData.filters as FilterState);
54+
}
55+
}
56+
}
57+
} catch (error) {
58+
console.error('Error loading settings:', error);
59+
} finally {
60+
setIsLoadingFilters(false);
61+
}
62+
};
63+
64+
void loadSettings();
65+
}, []);
66+
67+
// Save filters when they change (if SAVE_FILTER is enabled)
68+
useEffect(() => {
69+
if (!saveFiltersEnabled || isLoadingFilters) return;
70+
71+
const saveFilters = async () => {
72+
try {
73+
await fetch('/api/settings/filters', {
74+
method: 'POST',
75+
headers: {
76+
'Content-Type': 'application/json',
77+
},
78+
body: JSON.stringify({ filters }),
79+
});
80+
} catch (error) {
81+
console.error('Error saving filters:', error);
82+
}
83+
};
84+
85+
// Debounce the save operation
86+
const timeoutId = setTimeout(() => void saveFilters(), 500);
87+
return () => clearTimeout(timeoutId);
88+
}, [filters, saveFiltersEnabled, isLoadingFilters]);
89+
3290
// Extract categories from metadata
3391
const categories = React.useMemo((): string[] => {
3492
if (!scriptCardsData?.success || !scriptCardsData.metadata?.categories) return [];
@@ -341,6 +399,8 @@ export function DownloadedScriptsTab() {
341399
totalScripts={downloadedScripts.length}
342400
filteredCount={filteredScripts.length}
343401
updatableCount={filterCounts.updatableCount}
402+
saveFiltersEnabled={saveFiltersEnabled}
403+
isLoadingFilters={isLoadingFilters}
344404
/>
345405

346406
{/* Scripts Grid */}

src/app/_components/FilterBar.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ interface FilterBarProps {
1818
totalScripts: number;
1919
filteredCount: number;
2020
updatableCount?: number;
21+
saveFiltersEnabled?: boolean;
22+
isLoadingFilters?: boolean;
2123
}
2224

2325
const SCRIPT_TYPES = [
@@ -33,6 +35,8 @@ export function FilterBar({
3335
totalScripts,
3436
filteredCount,
3537
updatableCount = 0,
38+
saveFiltersEnabled = false,
39+
isLoadingFilters = false,
3640
}: FilterBarProps) {
3741
const [isTypeDropdownOpen, setIsTypeDropdownOpen] = useState(false);
3842
const [isSortDropdownOpen, setIsSortDropdownOpen] = useState(false);
@@ -78,6 +82,28 @@ export function FilterBar({
7882

7983
return (
8084
<div className="mb-6 rounded-lg border border-border bg-card p-4 sm:p-6 shadow-sm">
85+
{/* Loading State */}
86+
{isLoadingFilters && (
87+
<div className="mb-4 flex items-center justify-center py-2">
88+
<div className="flex items-center space-x-2 text-sm text-muted-foreground">
89+
<div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
90+
<span>Loading saved filters...</span>
91+
</div>
92+
</div>
93+
)}
94+
95+
{/* Filter Persistence Status */}
96+
{!isLoadingFilters && saveFiltersEnabled && (
97+
<div className="mb-4 flex items-center justify-center py-1">
98+
<div className="flex items-center space-x-2 text-xs text-green-600">
99+
<svg className="h-3 w-3" fill="currentColor" viewBox="0 0 20 20">
100+
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
101+
</svg>
102+
<span>Filters are being saved automatically</span>
103+
</div>
104+
</div>
105+
)}
106+
81107
{/* Search Bar */}
82108
<div className="mb-4">
83109
<div className="relative max-w-md w-full">

0 commit comments

Comments
 (0)