diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 76683e6..e6534ea 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -1,3 +1,4 @@
+import { useEffect, useState } from "react";
import { Navigate, Route, Routes } from "react-router-dom";
import { Toaster } from "sonner";
@@ -13,6 +14,23 @@ import { AuthCallbackPage } from "./pages/AuthCallbackPage";
* can wrap `` with a `MemoryRouter` if needed.
*/
export default function App() {
+ const [isMobile, setIsMobile] = useState(false);
+
+ useEffect(() => {
+ const mediaQuery = window.matchMedia("(max-width: 640px)");
+
+ const updateViewport = () => {
+ setIsMobile(mediaQuery.matches);
+ };
+
+ updateViewport();
+ mediaQuery.addEventListener("change", updateViewport);
+
+ return () => {
+ mediaQuery.removeEventListener("change", updateViewport);
+ };
+ }, []);
+
return (
@@ -24,20 +42,24 @@ export default function App() {
diff --git a/frontend/src/components/dashboard/SyncButton.jsx b/frontend/src/components/dashboard/SyncButton.jsx
index bca8420..0a95dd2 100644
--- a/frontend/src/components/dashboard/SyncButton.jsx
+++ b/frontend/src/components/dashboard/SyncButton.jsx
@@ -5,9 +5,15 @@ import { Spinner } from "../ui/Spinner";
* Primary action button on the dashboard. Swaps icon + colour scheme
* depending on the sync lifecycle (idle → loading → success flash).
*/
-export function SyncButton({ onClick, status = "idle", className = "" }) {
+export function SyncButton({
+ onClick,
+ status = "idle",
+ disabled = false,
+ className = "",
+}) {
const isLoading = status === "loading";
const isSuccess = status === "success";
+ const isDisabled = disabled || isLoading || isSuccess;
const palette = isSuccess
? "bg-orcid-green-soft text-orcid-green-text border border-orcid-green-border"
@@ -19,7 +25,7 @@ export function SyncButton({ onClick, status = "idle", className = "" }) {