diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4b8c57a..016b702 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -3,13 +3,10 @@ import HomePage from "./pages/homePage/homePage"; import CategoryPage from "./pages/categoryPage/categoryPage"; import PcBuilderPage from "./pages/pcBuilderPage/pcBuilderPage"; import CommunityPage from "./pages/communityPage/communityPage"; -import NavBar from "./components/pageSections/Header"; -import Footer from "./components/pageSections/Footer"; -import BackToTopButton from "./components/buttons/BackToTopButton"; +import Layout from "./components/Layout"; import LoginPage from "./pages/loginPage/loginPage"; import AccountPage from "./pages/accountPage/accountPage"; import { Routes, Route, useLocation, useParams } from "react-router"; -import Notification from "./components/Notification"; import { useSelector } from "react-redux"; import type { RootState } from "./state/store"; @@ -28,29 +25,21 @@ function App() { (state: RootState) => state.auth.user?.uid ?? "guest", ); return ( -
-
- -
-
- - } /> - } /> - } - /> - } /> - } /> - } /> - } /> - 404 - Page not found

} /> -
-
-
+ + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + 404 - Page not found

} + /> +
+
); } export default App; diff --git a/frontend/src/assets/dualListIcon.tsx b/frontend/src/assets/dualListIcon.tsx new file mode 100644 index 0000000..fc721b3 --- /dev/null +++ b/frontend/src/assets/dualListIcon.tsx @@ -0,0 +1,28 @@ +import type { SVGProps } from "react"; + +export function DualListIcon(props: SVGProps) { + return ( + + {/* Left column */} + + + + + {/* Right column */} + + + + + ); +} \ No newline at end of file diff --git a/frontend/src/components/AllPartsPreview.tsx b/frontend/src/components/AllPartsPreview.tsx index 21b531d..c87bac5 100644 --- a/frontend/src/components/AllPartsPreview.tsx +++ b/frontend/src/components/AllPartsPreview.tsx @@ -178,16 +178,19 @@ function PartsResults({ }, [featured, handleLoadMore, loading, error, pageInfo?.hasNextPage]); return ( -
- {viewenabled && ( - - )} +
- {!featured && ( -

- Showing {visibleParts.length} of {totalCount} components -

- )} +
+ {!featured && ( +

+ Showing {visibleParts.length} of {totalCount} components +

+ )} + + {viewenabled && ( + + )} +
+

diff --git a/frontend/src/components/FilterComponent.tsx b/frontend/src/components/FilterComponent.tsx index 8efd99a..6f6ac83 100644 --- a/frontend/src/components/FilterComponent.tsx +++ b/frontend/src/components/FilterComponent.tsx @@ -48,7 +48,7 @@ export default function FilterComponent({ return (
{isDesktopSize ? ( diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx new file mode 100644 index 0000000..8613810 --- /dev/null +++ b/frontend/src/components/Layout.tsx @@ -0,0 +1,27 @@ +import type { ReactNode } from "react"; +import NavBar from "./pageSections/Header"; +import Footer from "./pageSections/Footer"; +import BackToTopButton from "./buttons/BackToTopButton"; +import Notification from "./Notification"; + +const contentWidth = "mx-auto w-full max-w-[100rem] px-4 sm:px-6"; + +export default function Layout({ children }: { children: ReactNode }) { + return ( +
+
+
+ +
+
+
{children}
+
+
+
+
+
+ + +
+ ); +} diff --git a/frontend/src/components/Notification.tsx b/frontend/src/components/Notification.tsx index 4edc7b4..dde2e37 100644 --- a/frontend/src/components/Notification.tsx +++ b/frontend/src/components/Notification.tsx @@ -66,10 +66,14 @@ export default function Notification() { return; } + const exitTransform = window.matchMedia("(min-width: 48rem)").matches + ? "translateX(calc(100% + 1rem))" + : "translateY(calc(-100% - 6rem))"; + exitAnimation = notification.animate( [ - { transform: "translateX(0)", opacity: 1 }, - { transform: "translateX(calc(100% + 1rem))", opacity: 0 }, + { transform: "translate(0, 0)", opacity: 1 }, + { transform: exitTransform, opacity: 0 }, ], { duration: 250, easing: "ease-in", fill: "forwards" }, ); @@ -120,7 +124,7 @@ export default function Notification() { }} aria-labelledby={titleId} aria-describedby={descriptionId} - className="fixed right-4 left-auto top-20 bottom-auto m-0 h-fit max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-sm overflow-y-auto rounded-2xl border border-surface bg-background p-0 text-white shadow-lg shadow-black/20 backdrop:bg-transparent motion-safe:animate-[feedback-slide-in_250ms_ease-out]" + className="fixed right-4 left-4 top-20 bottom-auto my-0 mx-auto md:left-auto md:mx-0 h-fit max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-sm overflow-y-auto rounded-2xl border border-surface bg-background p-0 text-white shadow-lg shadow-black/20 backdrop:bg-transparent motion-safe:animate-[notification-slide-down_250ms_ease-out] md:motion-safe:animate-[feedback-slide-in_250ms_ease-out]" > - } error={detailError &&

{detailError}

} buildAction={ } /> diff --git a/frontend/src/components/PcPartCardLayout.tsx b/frontend/src/components/PcPartCardLayout.tsx index 70fd031..6e4d42c 100644 --- a/frontend/src/components/PcPartCardLayout.tsx +++ b/frontend/src/components/PcPartCardLayout.tsx @@ -1,24 +1,24 @@ import type { ReactNode } from "react"; type PcPartCardLayoutProps = { + onShowDetails?: () => void; image: ReactNode; name: ReactNode; nameTitle?: string; category: ReactNode; price: ReactNode; - detailsAction: ReactNode; buildAction: ReactNode; error?: ReactNode; skeleton?: boolean; }; //Layot for the PcPartCard and the PcPartCardSkeleton. export default function PcPartCardLayout({ + onShowDetails, image, name, nameTitle, category, price, - detailsAction, buildAction, error, skeleton = false, @@ -26,25 +26,61 @@ export default function PcPartCardLayout({ return (
{ + if (!skeleton) onShowDetails?.(); + }} > -
{ + if (skeleton || event.target !== event.currentTarget) return; + if (event.key === "Enter" || event.key === " "){ + event.preventDefault(); + onShowDetails?.() + } + }} >
{image}
-
-

- {name} -

-

{category}

-

{price}

-
{detailsAction}
- {error} - {buildAction} + +
+
+

+ {name} +

+ +

+ {category} +

+ + {error} +
+ +

+ {price} +

+ +
event.stopPropagation()} + > + {buildAction} +
diff --git a/frontend/src/components/PcPartCardSkeleton.tsx b/frontend/src/components/PcPartCardSkeleton.tsx index 5844db6..b2d8cdb 100644 --- a/frontend/src/components/PcPartCardSkeleton.tsx +++ b/frontend/src/components/PcPartCardSkeleton.tsx @@ -14,7 +14,6 @@ export default function PcPartCardSkeleton() { name={} category={} price={} - detailsAction={actionPlaceholder} buildAction={actionPlaceholder} /> ); diff --git a/frontend/src/components/ViewSelector.tsx b/frontend/src/components/ViewSelector.tsx index aad5bf8..30df8d4 100644 --- a/frontend/src/components/ViewSelector.tsx +++ b/frontend/src/components/ViewSelector.tsx @@ -1,13 +1,14 @@ -import { LayoutGrid, StretchHorizontal, Columns2 } from "lucide-react"; +import { Grip, List } from "lucide-react"; +import { DualListIcon } from "@/assets/dualListIcon"; const viewOptions = ["list-view", "2-grid-view", "4-grid-view"] as const; export type ViewType = (typeof viewOptions)[number]; const viewIcons = { - "list-view": StretchHorizontal, - "2-grid-view": Columns2, - "4-grid-view": LayoutGrid, + "list-view": List, + "2-grid-view": DualListIcon, + "4-grid-view": Grip, }; const viewLabels = { diff --git a/frontend/src/components/pageSections/Footer.tsx b/frontend/src/components/pageSections/Footer.tsx index acc6f9c..408c7c1 100644 --- a/frontend/src/components/pageSections/Footer.tsx +++ b/frontend/src/components/pageSections/Footer.tsx @@ -1,16 +1,14 @@ export default function Footer() { return ( -
-
-
-

Full Circuit

-

- Explore PC components and plan your build. -

-
- -

© 2026 Team 15, IT2810

+
+
+

Full Circuit

+

+ Explore PC components and plan your build. +

-
+ +

© 2026 Team 15, IT2810

+

); } diff --git a/frontend/src/components/pageSections/Header.tsx b/frontend/src/components/pageSections/Header.tsx index 02f633e..22aa0cb 100644 --- a/frontend/src/components/pageSections/Header.tsx +++ b/frontend/src/components/pageSections/Header.tsx @@ -32,7 +32,7 @@ export default function NavBar() { return (