From 4998fe4c94c0c75e2b6770a641d0cf09dfcc2596 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Mon, 20 Apr 2026 13:50:36 -0400 Subject: [PATCH 1/4] fix: hide register tip on login and redirect to setup when no super admin (#3556) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Changes - Removed the "Register here" link from the login page — it misled users into thinking public registration was open. - Added redirect from /login to /register when no super admin exists (initial setup). - Added an info alert on the register page clarifying the page is only for initial setup; additional users are invited from team settings. Hidden for the invite-token flow. - Extracted shared useSuperAdminRedirect hook to dedupe identical fetch/redirect logic across Login and Register. ## Benefits - Clearer UX: login page no longer suggests self-registration is available post-setup. - Correct routing on fresh installs: users land on the registration page instead of a login page they can't use. --- client/src/Hooks/useSuperAdminRedirect.ts | 24 +++++++++++++++++++++++ client/src/Pages/Auth/Login/index.tsx | 14 +++++++------ client/src/Pages/Auth/Register/index.tsx | 21 ++++++++++---------- client/src/locales/en.json | 1 + 4 files changed, 44 insertions(+), 16 deletions(-) create mode 100644 client/src/Hooks/useSuperAdminRedirect.ts diff --git a/client/src/Hooks/useSuperAdminRedirect.ts b/client/src/Hooks/useSuperAdminRedirect.ts new file mode 100644 index 000000000..341208ac3 --- /dev/null +++ b/client/src/Hooks/useSuperAdminRedirect.ts @@ -0,0 +1,24 @@ +import { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { useGet } from "@/Hooks/UseApi"; + +type Options = { + redirectTo: string; + redirectWhen: boolean; + skip?: boolean; +}; + +export const useSuperAdminRedirect = ({ redirectTo, redirectWhen, skip }: Options) => { + const navigate = useNavigate(); + const { data: superAdminExists, isLoading } = useGet( + skip ? null : "/auth/users/superadmin" + ); + + useEffect(() => { + if (!skip && superAdminExists === redirectWhen) { + navigate(redirectTo, { replace: true }); + } + }, [superAdminExists, redirectWhen, redirectTo, skip, navigate]); + + return { superAdminExists, isLoading }; +}; diff --git a/client/src/Pages/Auth/Login/index.tsx b/client/src/Pages/Auth/Login/index.tsx index ec95a0ce3..fd0eff130 100644 --- a/client/src/Pages/Auth/Login/index.tsx +++ b/client/src/Pages/Auth/Login/index.tsx @@ -10,6 +10,7 @@ import { useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; import { setAuthState } from "@/Features/Auth/authSlice"; import { usePost } from "@/Hooks/UseApi"; +import { useSuperAdminRedirect } from "@/Hooks/useSuperAdminRedirect"; const LoginPage = () => { const { t } = useTranslation(); @@ -17,6 +18,11 @@ const LoginPage = () => { const navigate = useNavigate(); const { post, loading } = usePost(); + const { isLoading: isCheckingAdmin } = useSuperAdminRedirect({ + redirectTo: "/register", + redirectWhen: false, + }); + const { schema, defaults } = useLoginForm(); const { control, handleSubmit } = useForm({ @@ -24,6 +30,8 @@ const LoginPage = () => { defaultValues: defaults, }); + if (isCheckingAdmin) return null; + const onSubmit = async (data: LoginFormData) => { if (loading) return; @@ -82,12 +90,6 @@ const LoginPage = () => { linkText={t("pages.auth.login.links.forgotPassword.linkText")} href="/forgot-password" /> - ); }; diff --git a/client/src/Pages/Auth/Register/index.tsx b/client/src/Pages/Auth/Register/index.tsx index a2a439023..787f7da0e 100644 --- a/client/src/Pages/Auth/Register/index.tsx +++ b/client/src/Pages/Auth/Register/index.tsx @@ -1,11 +1,13 @@ import { BaseAuthPage } from "@/Components/design-elements"; import { Button, TextField } from "@/Components/inputs"; +import { Alert } from "@mui/material"; import { useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod/dist/zod.js"; import { useRegisterForm } from "@/Hooks/useRegisterForm"; import type { RegisterFormData } from "@/Validation/register"; import { useTranslation } from "react-i18next"; -import { usePost, useGet } from "@/Hooks/UseApi"; +import { usePost } from "@/Hooks/UseApi"; +import { useSuperAdminRedirect } from "@/Hooks/useSuperAdminRedirect"; import { setAuthState } from "@/Features/Auth/authSlice"; import { useDispatch } from "react-redux"; import { useNavigate, useParams } from "react-router-dom"; @@ -32,21 +34,17 @@ const RegisterPage = () => { const { post: verifyToken } = usePost<{ token: string }, InviteVerifyResponse>(); const hasVerified = useRef(false); - const { data: superAdminExists, isLoading: isCheckingAdmin } = useGet( - token ? null : "/auth/users/superadmin" - ); + const { isLoading: isCheckingAdmin } = useSuperAdminRedirect({ + redirectTo: "/login", + redirectWhen: true, + skip: !!token, + }); const { control, handleSubmit, setError, reset } = useForm({ resolver: zodResolver(schema), defaultValues: defaults, }); - useEffect(() => { - if (superAdminExists === true) { - navigate("/login", { replace: true }); - } - }, [superAdminExists, navigate]); - useEffect(() => { if (!token || hasVerified.current) return; hasVerified.current = true; @@ -92,6 +90,9 @@ const RegisterPage = () => { title={t("pages.auth.register.title")} subtitle={t("pages.auth.register.subtitle")} > + {!token && ( + {t("pages.auth.register.setupNotice")} + )} Date: Mon, 20 Apr 2026 13:53:50 -0400 Subject: [PATCH 2/4] style: apply prettier formatting --- client/src/Pages/Auth/Register/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/src/Pages/Auth/Register/index.tsx b/client/src/Pages/Auth/Register/index.tsx index 787f7da0e..09cc0e206 100644 --- a/client/src/Pages/Auth/Register/index.tsx +++ b/client/src/Pages/Auth/Register/index.tsx @@ -90,9 +90,7 @@ const RegisterPage = () => { title={t("pages.auth.register.title")} subtitle={t("pages.auth.register.subtitle")} > - {!token && ( - {t("pages.auth.register.setupNotice")} - )} + {!token && {t("pages.auth.register.setupNotice")}} Date: Mon, 20 Apr 2026 11:47:43 -0700 Subject: [PATCH 3/4] use lazy get --- client/src/Hooks/useSuperAdminRedirect.ts | 24 ----------------------- client/src/Pages/Auth/Login/index.tsx | 18 +++++++++++------ client/src/Pages/Auth/Register/index.tsx | 21 ++++++++++++-------- 3 files changed, 25 insertions(+), 38 deletions(-) delete mode 100644 client/src/Hooks/useSuperAdminRedirect.ts diff --git a/client/src/Hooks/useSuperAdminRedirect.ts b/client/src/Hooks/useSuperAdminRedirect.ts deleted file mode 100644 index 341208ac3..000000000 --- a/client/src/Hooks/useSuperAdminRedirect.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useEffect } from "react"; -import { useNavigate } from "react-router-dom"; -import { useGet } from "@/Hooks/UseApi"; - -type Options = { - redirectTo: string; - redirectWhen: boolean; - skip?: boolean; -}; - -export const useSuperAdminRedirect = ({ redirectTo, redirectWhen, skip }: Options) => { - const navigate = useNavigate(); - const { data: superAdminExists, isLoading } = useGet( - skip ? null : "/auth/users/superadmin" - ); - - useEffect(() => { - if (!skip && superAdminExists === redirectWhen) { - navigate(redirectTo, { replace: true }); - } - }, [superAdminExists, redirectWhen, redirectTo, skip, navigate]); - - return { superAdminExists, isLoading }; -}; diff --git a/client/src/Pages/Auth/Login/index.tsx b/client/src/Pages/Auth/Login/index.tsx index fd0eff130..9de5f1df5 100644 --- a/client/src/Pages/Auth/Login/index.tsx +++ b/client/src/Pages/Auth/Login/index.tsx @@ -1,6 +1,7 @@ import { BaseAuthPage, TextLink } from "@/Components/design-elements"; import { Button, TextField } from "@/Components/inputs"; +import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod/dist/zod.js"; @@ -9,8 +10,7 @@ import type { LoginFormData } from "@/Validation/login"; import { useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; import { setAuthState } from "@/Features/Auth/authSlice"; -import { usePost } from "@/Hooks/UseApi"; -import { useSuperAdminRedirect } from "@/Hooks/useSuperAdminRedirect"; +import { useLazyGet, usePost } from "@/Hooks/UseApi"; const LoginPage = () => { const { t } = useTranslation(); @@ -18,10 +18,16 @@ const LoginPage = () => { const navigate = useNavigate(); const { post, loading } = usePost(); - const { isLoading: isCheckingAdmin } = useSuperAdminRedirect({ - redirectTo: "/register", - redirectWhen: false, - }); + const [isCheckingAdmin, setIsCheckingAdmin] = useState(true); + const { get } = useLazyGet(); + + useEffect(() => { + get("/auth/users/superadmin").then((res) => { + if (res?.data === false) navigate("/register", { replace: true }); + else setIsCheckingAdmin(false); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const { schema, defaults } = useLoginForm(); diff --git a/client/src/Pages/Auth/Register/index.tsx b/client/src/Pages/Auth/Register/index.tsx index 09cc0e206..9c7bae3ee 100644 --- a/client/src/Pages/Auth/Register/index.tsx +++ b/client/src/Pages/Auth/Register/index.tsx @@ -6,13 +6,12 @@ import { zodResolver } from "@hookform/resolvers/zod/dist/zod.js"; import { useRegisterForm } from "@/Hooks/useRegisterForm"; import type { RegisterFormData } from "@/Validation/register"; import { useTranslation } from "react-i18next"; -import { usePost } from "@/Hooks/UseApi"; -import { useSuperAdminRedirect } from "@/Hooks/useSuperAdminRedirect"; +import { useLazyGet, usePost } from "@/Hooks/UseApi"; import { setAuthState } from "@/Features/Auth/authSlice"; import { useDispatch } from "react-redux"; import { useNavigate, useParams } from "react-router-dom"; import type { AuthResponse } from "@/Types/User"; -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useState } from "react"; interface RegisterPayload { user: Omit; @@ -34,11 +33,17 @@ const RegisterPage = () => { const { post: verifyToken } = usePost<{ token: string }, InviteVerifyResponse>(); const hasVerified = useRef(false); - const { isLoading: isCheckingAdmin } = useSuperAdminRedirect({ - redirectTo: "/login", - redirectWhen: true, - skip: !!token, - }); + const [isCheckingAdmin, setIsCheckingAdmin] = useState(!token); + const { get } = useLazyGet(); + + useEffect(() => { + if (token) return; + get("/auth/users/superadmin").then((res) => { + if (res?.data === true) navigate("/login", { replace: true }); + else setIsCheckingAdmin(false); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const { control, handleSubmit, setError, reset } = useForm({ resolver: zodResolver(schema), From 5f12459bfd5a348a2563ddcb1e4a46d008664693 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 20 Apr 2026 11:48:48 -0700 Subject: [PATCH 4/4] explicit import --- client/src/Pages/Auth/Register/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Pages/Auth/Register/index.tsx b/client/src/Pages/Auth/Register/index.tsx index 9c7bae3ee..71603058f 100644 --- a/client/src/Pages/Auth/Register/index.tsx +++ b/client/src/Pages/Auth/Register/index.tsx @@ -1,6 +1,6 @@ import { BaseAuthPage } from "@/Components/design-elements"; import { Button, TextField } from "@/Components/inputs"; -import { Alert } from "@mui/material"; +import Alert from "@mui/material/Alert"; import { useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod/dist/zod.js"; import { useRegisterForm } from "@/Hooks/useRegisterForm";