From 937b96877a93206ac424cd1ad99b865676e1c567 Mon Sep 17 00:00:00 2001
From: Kristian Gjertsen
Date: Sat, 14 Nov 2026 16:33:41 +0100
Subject: [PATCH 01/12] standardize AddToBuildButton component to include
optional icon display
---
.../components/DesktopComponentDetailPage.tsx | 17 +++--------------
.../src/components/buttons/AddToBuildButton.tsx | 16 ++++++++++++----
2 files changed, 15 insertions(+), 18 deletions(-)
diff --git a/frontend/src/components/DesktopComponentDetailPage.tsx b/frontend/src/components/DesktopComponentDetailPage.tsx
index 716032a..dd04b50 100644
--- a/frontend/src/components/DesktopComponentDetailPage.tsx
+++ b/frontend/src/components/DesktopComponentDetailPage.tsx
@@ -1,4 +1,4 @@
-import { Trash2, X } from "lucide-react";
+import { X } from "lucide-react";
import { useId, useState } from "react";
import { Link } from "react-router";
import type { PcPart } from "@/types/types";
@@ -8,14 +8,11 @@ import {
getSpecifications,
} from "@/utils/partSpecifications";
import AddToBuildButton from "./buttons/AddToBuildButton";
-import { useSelector } from "react-redux";
-import { selectIsPartInBuild } from "@/state/selectedPartsSlice";
export default function DesktopComponentDetailPage({ part }: { part: PcPart }) {
const [expanded, setExpanded] = useState(false);
const [failedImage, setFailedImage] = useState("");
const specificationsId = useId();
- const inBuild = useSelector((state) => selectIsPartInBuild(state, part));
const specifications = getSpecifications(part);
const category =
categories.find((item) => item.value === part.category)?.label ??
@@ -133,16 +130,8 @@ export default function DesktopComponentDetailPage({ part }: { part: PcPart }) {
- {inBuild ? (
-
- ) : (
-
- +
-
- )}
- {inBuild ? "Remove from build" : "Add to build"}
-
+ showIcon
+ />
diff --git a/frontend/src/components/buttons/AddToBuildButton.tsx b/frontend/src/components/buttons/AddToBuildButton.tsx
index c4f1e18..ac8c76e 100644
--- a/frontend/src/components/buttons/AddToBuildButton.tsx
+++ b/frontend/src/components/buttons/AddToBuildButton.tsx
@@ -1,4 +1,4 @@
-import type { ReactNode } from "react";
+import { Trash2 } from "lucide-react";
import { useRef, useState } from "react";
import type { PcPart } from "@/types/types";
import { twMerge } from "tailwind-merge";
@@ -17,13 +17,13 @@ import { showNotification } from "@/state/notificationSlice";
type AddToBuildButtonProps = {
part: PcPart;
- children?: ReactNode;
+ showIcon?: boolean;
className?: string;
};
export default function AddToBuildButton({
part,
- children,
+ showIcon = false,
className,
}: AddToBuildButtonProps) {
const [showFeedback, setShowFeedback] = useState(false);
@@ -102,7 +102,15 @@ export default function AddToBuildButton({
)}
onClick={handleClick}
>
- {children ?? (inBuild ? "Remove from build" : "Add to build")}
+ {showIcon &&
+ (inBuild ? (
+
+ ) : (
+
+ +
+
+ ))}
+ {inBuild ? "Remove from build" : "Add to build"}
{showFeedback && (stage !== "confirm" || !isCategoryEmpty) && (
From 4615c2bfbf5733dc73782fcf6420f91101ad3fd0 Mon Sep 17 00:00:00 2001
From: Kristian Gjertsen
Date: Sat, 14 Nov 2026 16:36:36 +0100
Subject: [PATCH 02/12] replace native button with custom Button component in
BackToTopButton
---
frontend/src/components/buttons/BackToTopButton.tsx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/frontend/src/components/buttons/BackToTopButton.tsx b/frontend/src/components/buttons/BackToTopButton.tsx
index a515e84..c0f26a7 100644
--- a/frontend/src/components/buttons/BackToTopButton.tsx
+++ b/frontend/src/components/buttons/BackToTopButton.tsx
@@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
import { ArrowUp } from "lucide-react";
+import Button from "./Button";
// Show the button once the user has scrolled about one screen down.
const SHOW_AFTER_PX = 600;
@@ -15,9 +16,8 @@ export default function BackToTopButton() {
}, []);
return (
-
+
);
}
From 86e4347e495deba179a7b27b579f31fb97ab8639 Mon Sep 17 00:00:00 2001
From: Kristian Gjertsen
Date: Sat, 14 Nov 2026 16:37:25 +0100
Subject: [PATCH 03/12] update BackToTopButton styling for improved appearance
---
frontend/src/components/buttons/BackToTopButton.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/components/buttons/BackToTopButton.tsx b/frontend/src/components/buttons/BackToTopButton.tsx
index c0f26a7..5d88680 100644
--- a/frontend/src/components/buttons/BackToTopButton.tsx
+++ b/frontend/src/components/buttons/BackToTopButton.tsx
@@ -17,7 +17,7 @@ export default function BackToTopButton() {
return (
diff --git a/frontend/src/components/MobileComponentDetailPage.tsx b/frontend/src/components/MobileComponentDetailPage.tsx
index 048f4a6..e0afdd2 100644
--- a/frontend/src/components/MobileComponentDetailPage.tsx
+++ b/frontend/src/components/MobileComponentDetailPage.tsx
@@ -123,7 +123,7 @@ export default function ComponentDetailPage({ part }: { part: PcPart }) {
>
{expanded ? "Minimize" : "View all specifications"}
-
+
);
diff --git a/frontend/src/components/PcPartCard.tsx b/frontend/src/components/PcPartCard.tsx
index c795996..b39e5d0 100644
--- a/frontend/src/components/PcPartCard.tsx
+++ b/frontend/src/components/PcPartCard.tsx
@@ -88,7 +88,7 @@ export default function PcPartCard({
buildAction={
}
/>
diff --git a/frontend/src/components/buttons/AddToBuildButton.tsx b/frontend/src/components/buttons/AddToBuildButton.tsx
index ac8c76e..8177964 100644
--- a/frontend/src/components/buttons/AddToBuildButton.tsx
+++ b/frontend/src/components/buttons/AddToBuildButton.tsx
@@ -95,10 +95,10 @@ export default function AddToBuildButton({
return (
<>
{logoutError && (
@@ -148,14 +149,14 @@ function AccountContent() {
- resetBuild()}
- className={`${buttonStyle} bg-accent text-black hover:bg-orange-300 motion-safe:hover:scale-[1.02]`}
+ className="h-11 w-auto gap-2 px-4 py-2 text-sm font-medium focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-accent"
>
Create a build
-
+
{notice && (
@@ -179,17 +180,17 @@ function AccountContent() {
{error}
- {
setLoading(true);
setError("");
setRetry((value) => value + 1);
}}
- className={`${buttonStyle} mt-4 bg-accent text-black hover:bg-orange-300`}
+ className="mt-4 h-11 w-auto px-4 py-2"
>
Try again
-
+
)}
@@ -265,22 +266,24 @@ function AccountContent() {
{offset > 0 && (
- changePage(offset - 20)}
- className={`${buttonStyle} border border-gray-500 hover:bg-surface`}
+ variant="secondary"
+ className="h-11 w-auto rounded-lg px-4 py-2 text-sm font-medium"
>
Previous
-
+
)}
{builds.length > 20 && (
- changePage(offset + 20)}
- className={`${buttonStyle} ml-auto border border-gray-500 hover:bg-surface`}
+ variant="secondary"
+ className="ml-auto h-11 w-auto rounded-lg px-4 py-2 text-sm font-medium"
>
Next
-
+
)}
>
diff --git a/frontend/src/pages/accountPage/components/DeleteBuildButton.tsx b/frontend/src/pages/accountPage/components/DeleteBuildButton.tsx
index 9092622..37975c8 100644
--- a/frontend/src/pages/accountPage/components/DeleteBuildButton.tsx
+++ b/frontend/src/pages/accountPage/components/DeleteBuildButton.tsx
@@ -1,3 +1,4 @@
+import Button from "@/components/buttons/Button";
import { useId, useRef, useState } from "react";
import { LoaderCircle, Trash2 } from "lucide-react";
import { deleteBuild } from "@/api/builds";
@@ -39,7 +40,7 @@ export default function DeleteBuildButton({ id, title, onDeleted }: Props) {
return (
<>
- {
@@ -47,11 +48,12 @@ export default function DeleteBuildButton({ id, title, onDeleted }: Props) {
dialogRef.current?.showModal();
cancelRef.current?.focus();
}}
- className="relative z-10 inline-flex cursor-pointer items-center gap-2 rounded-lg px-3 py-2 text-sm text-red-200 transition-colors hover:bg-red-950/40 hover:text-red-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
+ variant="danger"
+ className="relative z-10 inline-flex h-11 w-auto items-center justify-center gap-2 px-3 py-2"
>
Delete
-
+
>
diff --git a/frontend/src/pages/loginPage/loginPage.tsx b/frontend/src/pages/loginPage/loginPage.tsx
index 6363c1f..e9773bb 100644
--- a/frontend/src/pages/loginPage/loginPage.tsx
+++ b/frontend/src/pages/loginPage/loginPage.tsx
@@ -1,3 +1,4 @@
+import Button from "@/components/buttons/Button";
import { Link, useNavigate } from "react-router";
import { useState } from "react";
import { FirebaseError } from "firebase/app";
@@ -88,7 +89,10 @@ export default function LoginPage() {
return (
-
+
{registering ? "Create account" : "Welcome back"}
@@ -135,10 +139,10 @@ export default function LoginPage() {
/>
-
{pending && (
+
diff --git a/frontend/src/pages/pcBuilderPage/components/PublishBuild.tsx b/frontend/src/pages/pcBuilderPage/components/PublishBuild.tsx
index 5614300..73a0ca1 100644
--- a/frontend/src/pages/pcBuilderPage/components/PublishBuild.tsx
+++ b/frontend/src/pages/pcBuilderPage/components/PublishBuild.tsx
@@ -29,11 +29,8 @@ export default function PublishBuild({
className="shrink-0 disabled:opacity-50"
>
{
setPublished(false);
setError("");
@@ -155,12 +152,17 @@ export default function PublishBuild({
)}
dialogRef.current?.close()}
>
Cancel
-
+
{saving
? "Saving..."
: privately
From d2b9573367fb92979282a596e7f7dd57939f5b31 Mon Sep 17 00:00:00 2001
From: Kristian Gjertsen
Date: Sat, 14 Nov 2026 17:42:18 +0100
Subject: [PATCH 09/12] standardize button usage by replacing button elements
with TextButton component across multiple pages
---
.../components/DesktopComponentDetailPage.tsx | 19 ++++++++-----------
frontend/src/components/FilterComponent.tsx | 12 ++++++------
.../components/MobileComponentDetailPage.tsx | 7 ++++---
frontend/src/components/buttons/Button.tsx | 1 +
.../src/components/buttons/TextButton.tsx | 12 ++++++++++++
frontend/src/pages/loginPage/loginPage.tsx | 6 +++---
.../pcBuilderPage/components/ClearBuild.tsx | 7 ++++---
7 files changed, 38 insertions(+), 26 deletions(-)
create mode 100644 frontend/src/components/buttons/TextButton.tsx
diff --git a/frontend/src/components/DesktopComponentDetailPage.tsx b/frontend/src/components/DesktopComponentDetailPage.tsx
index 1ad2ec3..bcb43a1 100644
--- a/frontend/src/components/DesktopComponentDetailPage.tsx
+++ b/frontend/src/components/DesktopComponentDetailPage.tsx
@@ -1,3 +1,4 @@
+import TextButton from "@/components/buttons/TextButton";
import { X } from "lucide-react";
import { useId, useState } from "react";
import { Link } from "react-router";
@@ -44,12 +45,12 @@ export default function DesktopComponentDetailPage({ part }: { part: PcPart }) {
/
@@ -127,11 +128,7 @@ export default function DesktopComponentDetailPage({ part }: { part: PcPart }) {
? "Price unavailable"
: `€${part.price_eur.toFixed(2)}`}
-
+
@@ -187,12 +184,12 @@ export default function DesktopComponentDetailPage({ part }: { part: PcPart }) {
{specifications.length > 4 && (
-
setExpanded((previous) => !previous)}
- className="mt-4 flex cursor-pointer items-center gap-3 text-xs text-accent hover:underline"
+ className="mt-4 flex items-center gap-3 text-xs"
>
{expanded
? "Show fewer specifications"
@@ -203,7 +200,7 @@ export default function DesktopComponentDetailPage({ part }: { part: PcPart }) {
>
↗
-
+
)}
diff --git a/frontend/src/components/FilterComponent.tsx b/frontend/src/components/FilterComponent.tsx
index 6f6ac83..2465fcc 100644
--- a/frontend/src/components/FilterComponent.tsx
+++ b/frontend/src/components/FilterComponent.tsx
@@ -1,3 +1,4 @@
+import TextButton from "@/components/buttons/TextButton";
import { useEffect, useRef, useState } from "react";
import type { Dispatch, SetStateAction } from "react";
import type { PartsOptions } from "@/api/parts";
@@ -104,14 +105,13 @@ export default function FilterComponent({
- setFiltOptions({ category: defaultCategory })}
>
Reset
-
+
>
) : (
@@ -203,14 +203,14 @@ export default function FilterComponent({
-
setDraft({ category: defaultCategory })}
>
Reset
-
+
-
{expanded ? "Minimize" : "View all specifications"}
-
+
diff --git a/frontend/src/components/buttons/Button.tsx b/frontend/src/components/buttons/Button.tsx
index 9a7315c..1108ceb 100644
--- a/frontend/src/components/buttons/Button.tsx
+++ b/frontend/src/components/buttons/Button.tsx
@@ -3,6 +3,7 @@ import { Link, type LinkProps } from "react-router";
import { twMerge } from "tailwind-merge";
const variantStyles = {
+ text: "w-auto rounded border-0 bg-transparent p-0 font-normal text-accent underline decoration-1 underline-offset-4 transition-colors enabled:hover:text-white",
danger: "border-red-600 bg-red-600 text-white hover:border-red-300",
default: "border-orange-400 bg-orange-400 text-black hover:border-black",
dark: "border-black bg-black text-orange-400 hover:border-orange-400",
diff --git a/frontend/src/components/buttons/TextButton.tsx b/frontend/src/components/buttons/TextButton.tsx
new file mode 100644
index 0000000..f667d58
--- /dev/null
+++ b/frontend/src/components/buttons/TextButton.tsx
@@ -0,0 +1,12 @@
+import type { ComponentPropsWithRef } from "react";
+import Button from "./Button";
+
+type TextButtonProps = ComponentPropsWithRef<"button">;
+
+export default function TextButton(props: TextButtonProps) {
+ return (
+
+ {props.children}
+
+ );
+}
diff --git a/frontend/src/pages/loginPage/loginPage.tsx b/frontend/src/pages/loginPage/loginPage.tsx
index e9773bb..a7856cb 100644
--- a/frontend/src/pages/loginPage/loginPage.tsx
+++ b/frontend/src/pages/loginPage/loginPage.tsx
@@ -1,3 +1,4 @@
+import TextButton from "@/components/buttons/TextButton";
import Button from "@/components/buttons/Button";
import { Link, useNavigate } from "react-router";
import { useState } from "react";
@@ -198,7 +199,7 @@ export default function LoginPage() {
{registering ? "Already have an account? " : "Need an account? "}
- {
@@ -206,10 +207,9 @@ export default function LoginPage() {
setPassword("");
setError("");
}}
- className="cursor-pointer rounded font-medium text-accent underline-offset-4 transition-colors hover:text-white hover:underline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-accent disabled:cursor-wait disabled:opacity-50"
>
{registering ? "Sign in" : "Register"}
-
+
{(pending || googlePending) && (
diff --git a/frontend/src/pages/pcBuilderPage/components/ClearBuild.tsx b/frontend/src/pages/pcBuilderPage/components/ClearBuild.tsx
index 89df7e4..a63d58f 100644
--- a/frontend/src/pages/pcBuilderPage/components/ClearBuild.tsx
+++ b/frontend/src/pages/pcBuilderPage/components/ClearBuild.tsx
@@ -1,3 +1,4 @@
+import TextButton from "@/components/buttons/TextButton";
import { useRef } from "react";
import Button from "@/components/buttons/Button";
import { resetBuild } from "@/state/selectedPartsSlice";
@@ -12,16 +13,16 @@ export default function ClearBuild() {
//Small popup component that ask if user wants to delete build
return (
<>
- {
if (Object.keys(parts).length === 0) return;
dialogRef.current?.showModal();
}}
>
Clear build
-
+