diff --git a/frontend/src/api/parts.ts b/frontend/src/api/parts.ts index 8a91da0..4a1a0c4 100644 --- a/frontend/src/api/parts.ts +++ b/frontend/src/api/parts.ts @@ -15,7 +15,6 @@ type ApiPart = { export type PartsOptions = { search?: string; category?: PcPart["category"]; - manufacturers?: string[]; sortBy?: "NAME" | "PRICE" | "MANUFACTURER" | "CATEGORY"; sortDirection?: "ASC" | "DESC"; first?: number; @@ -35,7 +34,6 @@ export async function getParts( { search = "", category, - manufacturers, sortBy = "NAME", sortDirection = "ASC", first = 20, @@ -52,7 +50,6 @@ export async function getParts( query GetParts( $search: String $category: Category - $manufacturers: [String!] $sortBy: SortField! $sortDirection: SortDirection! $first: Int! @@ -63,7 +60,6 @@ export async function getParts( after: $after search: $search category: $category - manufacturers: $manufacturers sortBy: $sortBy sortDirection: $sortDirection @@ -89,7 +85,6 @@ export async function getParts( variables: { search: search.trim() || null, category: category ?? null, - manufacturers: manufacturers?.length ? manufacturers : null, sortBy, sortDirection, first, diff --git a/frontend/src/components/AllPartsPreview.tsx b/frontend/src/components/AllPartsPreview.tsx index b9ffd86..21b531d 100644 --- a/frontend/src/components/AllPartsPreview.tsx +++ b/frontend/src/components/AllPartsPreview.tsx @@ -1,5 +1,6 @@ import { useParts } from "@/hooks/useParts"; import PcPartCard from "@/components/PcPartCard"; +import PcPartCardSkeleton from "@/components/PcPartCardSkeleton"; import ViewSelector, { type ViewType } from "@/components/ViewSelector"; import type { PcPart } from "@/types/types"; import type { PartsOptions } from "@/api/parts"; @@ -114,6 +115,12 @@ function PartsResults({ const visibleParts = loading ? [...lastResult.allParts, ...lastResult.parts] : [...allParts, ...parts]; + const showSkeletons = + loading && (visibleParts.length === 0 || Boolean(pagination.after)); + const skeletonCount = Math.min( + first, + viewType === "4-grid-view" ? 8 : viewType === "2-grid-view" ? 4 : 2, + ); const handleLoadMore = useCallback(() => { const cursor = pageInfo?.endCursor; @@ -198,6 +205,10 @@ function PartsResults({ onShowDetails={handleShowDetails} /> ))} + {showSkeletons && + Array.from({ length: skeletonCount }, (_, index) => ( + + ))} - {loading &&

Loading parts...

} + {loading && ( +

+ Loading parts... +

+ )} {error &&

{error}

} {!loading && !error && visibleParts.length === 0 && ( diff --git a/frontend/src/components/CategorySelection.tsx b/frontend/src/components/CategorySelection.tsx new file mode 100644 index 0000000..1df2333 --- /dev/null +++ b/frontend/src/components/CategorySelection.tsx @@ -0,0 +1,79 @@ +import { Link } from "react-router"; +import { categories } from "@/types/categories"; +import { + CircuitBoard, + Cpu, + Fan, + Gpu, + HardDrive, + MemoryStick, + PcCase, + Plug, + type LucideIcon, +} from "lucide-react"; +import type { PcPart } from "@/types/types"; + +const categoryIcons: Record = { + GPU: Gpu, + CPU: Cpu, + RAM: MemoryStick, + Storage: HardDrive, + PSU: Plug, + PCCase: PcCase, + CPUCooler: Fan, + Motherboard: CircuitBoard, +}; + +type CategorySelectionProps = { + showViewAll?: boolean; +}; + +export default function CategorySelection({ + showViewAll = true, +}: CategorySelectionProps) { + return ( +
+
+
+

+ Explore components +

+

+ Find every part of your next build. +

+
+ + {showViewAll && ( + + View all components → + + )} +
+ +
    + {categories.map((category) => { + const Icon = categoryIcons[category.value]; + return ( +
  • + +
  • + ); + })} +
+
+ ); +} diff --git a/frontend/src/components/FilterComponent.tsx b/frontend/src/components/FilterComponent.tsx index 04deefb..8efd99a 100644 --- a/frontend/src/components/FilterComponent.tsx +++ b/frontend/src/components/FilterComponent.tsx @@ -3,8 +3,9 @@ import type { Dispatch, SetStateAction } from "react"; import type { PartsOptions } from "@/api/parts"; import { categories } from "@/types/categories"; import Button from "./buttons/Button"; +import SearchComponent from "./SearchComponent"; -// Sort and both filters sit inline only when there is room next to the search field. +// Sort and category controls sit inline when there is room next to the search field. const DESKTOP_FILTER_WIDTH = 1200; type FilterComponentProps = { @@ -49,14 +50,7 @@ export default function FilterComponent({ aria-label="Search and filter components" className={`w-[90%] max-w-6xl text-white flex justify-center ${isDesktopSize ? "border border-gray-600 rounded-2xl bg-surface-dark p-2" : ""} items-center h-fit gap-2`} > - setSearch(event.target.value)} - className="min-w-0 flex-1 rounded-xl bg-surface px-4 py-3 placeholder:text-gray-300" - /> + {isDesktopSize ? ( <>
- - -

- Filter +
+

+ Filters

-