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
12 changes: 3 additions & 9 deletions src/app/_components/DownloadedScriptsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { FilterBar, type FilterState } from './FilterBar';
import { ViewToggle } from './ViewToggle';
import { Button } from './ui/button';
import type { ScriptCard as ScriptCardType } from '~/types/script';
import { getDefaultFilters, mergeFiltersWithDefaults } from './filterUtils';

interface DownloadedScriptsTabProps {
onInstallScript?: (
Expand All @@ -25,14 +26,7 @@ export function DownloadedScriptsTab({ onInstallScript }: DownloadedScriptsTabPr
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [viewMode, setViewMode] = useState<'card' | 'list'>('card');
const [filters, setFilters] = useState<FilterState>({
searchQuery: '',
showUpdatable: null,
selectedTypes: [],
selectedRepositories: [],
sortBy: 'name',
sortOrder: 'asc',
});
const [filters, setFilters] = useState<FilterState>(getDefaultFilters());
const [saveFiltersEnabled, setSaveFiltersEnabled] = useState(false);
const [isLoadingFilters, setIsLoadingFilters] = useState(true);
const gridRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -63,7 +57,7 @@ export function DownloadedScriptsTab({ onInstallScript }: DownloadedScriptsTabPr
if (filtersResponse.ok) {
const filtersData = await filtersResponse.json();
if (filtersData.filters) {
setFilters(filtersData.filters as FilterState);
setFilters(mergeFiltersWithDefaults(filtersData.filters));
}
}
}
Expand Down
10 changes: 2 additions & 8 deletions src/app/_components/FilterBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Button } from "./ui/button";
import { ContextualHelpIcon } from "./ContextualHelpIcon";
import { Package, Monitor, Wrench, Server, FileText, Calendar, RefreshCw, Filter, GitBranch } from "lucide-react";
import { api } from "~/trpc/react";
import { getDefaultFilters } from "./filterUtils";

export interface FilterState {
searchQuery: string;
Expand Down Expand Up @@ -67,14 +68,7 @@ export function FilterBar({
};

const clearAllFilters = () => {
onFiltersChange({
searchQuery: "",
showUpdatable: null,
selectedTypes: [],
selectedRepositories: [],
sortBy: "name",
sortOrder: "asc",
});
onFiltersChange(getDefaultFilters());
};

const hasActiveFilters =
Expand Down
12 changes: 3 additions & 9 deletions src/app/_components/ScriptsGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ViewToggle } from './ViewToggle';
import { Button } from './ui/button';
import { Clock } from 'lucide-react';
import type { ScriptCard as ScriptCardType } from '~/types/script';
import { getDefaultFilters, mergeFiltersWithDefaults } from './filterUtils';


interface ScriptsGridProps {
Expand All @@ -25,14 +26,7 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
const [viewMode, setViewMode] = useState<'card' | 'list'>('card');
const [selectedSlugs, setSelectedSlugs] = useState<Set<string>>(new Set());
const [downloadProgress, setDownloadProgress] = useState<{ current: number; total: number; currentScript: string; failed: Array<{ slug: string; error: string }> } | null>(null);
const [filters, setFilters] = useState<FilterState>({
searchQuery: '',
showUpdatable: null,
selectedTypes: [],
selectedRepositories: [],
sortBy: 'name',
sortOrder: 'asc',
});
const [filters, setFilters] = useState<FilterState>(getDefaultFilters());
const [saveFiltersEnabled, setSaveFiltersEnabled] = useState(false);
const [isLoadingFilters, setIsLoadingFilters] = useState(true);
const [isNewestMinimized, setIsNewestMinimized] = useState(false);
Expand Down Expand Up @@ -67,7 +61,7 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
if (filtersResponse.ok) {
const filtersData = await filtersResponse.json();
if (filtersData.filters) {
setFilters(filtersData.filters as FilterState);
setFilters(mergeFiltersWithDefaults(filtersData.filters));
}
}
}
Expand Down
44 changes: 44 additions & 0 deletions src/app/_components/filterUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { FilterState } from "./FilterBar";

/**
* Returns the default FilterState with all properties initialized.
* This serves as the single source of truth for default filter values.
*/
export function getDefaultFilters(): FilterState {
return {
searchQuery: "",
showUpdatable: null,
selectedTypes: [],
selectedRepositories: [],
sortBy: "name",
sortOrder: "asc",
};
}

/**
* Merges saved filters with defaults, ensuring all FilterState properties exist.
* This prevents crashes when loading old saved filters that are missing new properties.
*
* @param savedFilters - Partial or undefined saved filters from storage
* @returns Complete FilterState with all properties guaranteed to exist
*/
export function mergeFiltersWithDefaults(
savedFilters: Partial<FilterState> | undefined
): FilterState {
const defaults = getDefaultFilters();

if (!savedFilters) {
return defaults;
}

// Merge saved filters with defaults, ensuring all properties exist
return {
searchQuery: savedFilters.searchQuery ?? defaults.searchQuery,
showUpdatable: savedFilters.showUpdatable ?? defaults.showUpdatable,
selectedTypes: savedFilters.selectedTypes ?? defaults.selectedTypes,
selectedRepositories: savedFilters.selectedRepositories ?? defaults.selectedRepositories,
sortBy: savedFilters.sortBy ?? defaults.sortBy,
sortOrder: savedFilters.sortOrder ?? defaults.sortOrder,
};
}

Loading