From 975b4dcaab9292a77e8db157d934f18054999e73 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Sun, 22 Mar 2026 16:40:50 +0000 Subject: [PATCH] feat: auth pages layout (#2042) * auth pages layout * exclude home route from redirect * fix margin --- .../src/ee/components/cloud-login-form.tsx | 7 +- .../src/ee/mfa/components/mfa-challenge.tsx | 3 + .../ee/mfa/components/mfa-setup-required.tsx | 3 + apps/client/src/ee/pages/verify-email.tsx | 25 +++--- .../features/auth/components/auth-layout.tsx | 26 ++++++ .../features/auth/components/auth.module.css | 12 ++- .../auth/components/forgot-password-form.tsx | 3 + .../auth/components/invite-sign-up-form.tsx | 3 + .../features/auth/components/login-form.tsx | 87 ++++++++++--------- .../auth/components/password-reset-form.tsx | 3 + .../auth/components/setup-workspace-form.tsx | 5 +- apps/client/src/lib/api-client.ts | 8 +- 12 files changed, 124 insertions(+), 61 deletions(-) create mode 100644 apps/client/src/features/auth/components/auth-layout.tsx diff --git a/apps/client/src/ee/components/cloud-login-form.tsx b/apps/client/src/ee/components/cloud-login-form.tsx index 6ab7ebd9..2357f9bb 100644 --- a/apps/client/src/ee/components/cloud-login-form.tsx +++ b/apps/client/src/ee/components/cloud-login-form.tsx @@ -21,6 +21,7 @@ import { useTranslation } from "react-i18next"; import JoinedWorkspaces from "@/ee/components/joined-workspaces.tsx"; import { useJoinedWorkspacesQuery } from "@/ee/cloud/query/cloud-query.ts"; import { findWorkspacesByEmail } from "@/ee/cloud/service/cloud-service.ts"; +import { AuthLayout } from "@/features/auth/components/auth-layout.tsx"; const formSchema = z.object({ hostname: z.string().min(1, { message: "subdomain is required" }), @@ -82,7 +83,7 @@ export function CloudLoginForm() { } return ( -
+ @@ -145,12 +146,12 @@ export function CloudLoginForm() { </Box> </Container> - <Text ta="center"> + <Text ta="center" mb="xl"> {t("Don't have a workspace?")}{" "} <Anchor component={Link} to={APP_ROUTE.AUTH.CREATE_WORKSPACE} fw={500}> {t("Create new workspace")} </Anchor> </Text> - </div> + </AuthLayout> ); } diff --git a/apps/client/src/ee/mfa/components/mfa-challenge.tsx b/apps/client/src/ee/mfa/components/mfa-challenge.tsx index e1d15965..bfa16b22 100644 --- a/apps/client/src/ee/mfa/components/mfa-challenge.tsx +++ b/apps/client/src/ee/mfa/components/mfa-challenge.tsx @@ -22,6 +22,7 @@ import APP_ROUTE, { getPostLoginRedirect } from "@/lib/app-route"; import { useTranslation } from "react-i18next"; import { z } from "zod/v4"; import { MfaBackupCodeInput } from "./mfa-backup-code-input"; +import { AuthLayout } from "@/features/auth/components/auth-layout.tsx"; const formSchema = z.object({ code: z @@ -66,6 +67,7 @@ export function MfaChallenge() { }; return ( + <AuthLayout> <Container size={420} className={classes.container}> <Paper radius="lg" p={40} className={classes.paper}> <Stack align="center" gap="xl"> @@ -157,5 +159,6 @@ export function MfaChallenge() { </Stack> </Paper> </Container> + </AuthLayout> ); } diff --git a/apps/client/src/ee/mfa/components/mfa-setup-required.tsx b/apps/client/src/ee/mfa/components/mfa-setup-required.tsx index ab327c4d..880228a9 100644 --- a/apps/client/src/ee/mfa/components/mfa-setup-required.tsx +++ b/apps/client/src/ee/mfa/components/mfa-setup-required.tsx @@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next"; import { MfaSetupModal } from "@/ee/mfa"; import APP_ROUTE, { getPostLoginRedirect } from "@/lib/app-route.ts"; import { useNavigate } from "react-router-dom"; +import { AuthLayout } from "@/features/auth/components/auth-layout.tsx"; export default function MfaSetupRequired() { const { t } = useTranslation(); @@ -15,6 +16,7 @@ export default function MfaSetupRequired() { }; return ( + <AuthLayout> <Container size="sm" py="xl"> <Paper shadow="sm" p="xl" radius="md" withBorder> <Stack> @@ -44,5 +46,6 @@ export default function MfaSetupRequired() { </Stack> </Paper> </Container> + </AuthLayout> ); } diff --git a/apps/client/src/ee/pages/verify-email.tsx b/apps/client/src/ee/pages/verify-email.tsx index 623c3202..aef5711d 100644 --- a/apps/client/src/ee/pages/verify-email.tsx +++ b/apps/client/src/ee/pages/verify-email.tsx @@ -9,6 +9,7 @@ import { import { notifications } from "@mantine/notifications"; import APP_ROUTE from "@/lib/app-route.ts"; import { useTranslation } from "react-i18next"; +import { AuthLayout } from "@/features/auth/components/auth-layout.tsx"; export default function VerifyEmail() { const { t } = useTranslation(); @@ -59,20 +60,23 @@ export default function VerifyEmail() { if (token) { return ( - <Container size={420} className={classes.container}> - <Box p="xl" className={classes.containerBox}> - <Title order={2} ta="center" fw={500} mb="md"> - {t("Verifying your email")} - - - {t("Please wait...")} - - - + + + + + {t("Verifying your email")} + + + {t("Please wait...")} + + + + ); } return ( + @@ -103,5 +107,6 @@ export default function VerifyEmail() { )} </Box> </Container> + </AuthLayout> ); } diff --git a/apps/client/src/features/auth/components/auth-layout.tsx b/apps/client/src/features/auth/components/auth-layout.tsx new file mode 100644 index 00000000..d05ad7ec --- /dev/null +++ b/apps/client/src/features/auth/components/auth-layout.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { Group, Text } from "@mantine/core"; +import classes from "./auth.module.css"; + +type AuthLayoutProps = { + children: React.ReactNode; +}; + +export function AuthLayout({ children }: AuthLayoutProps) { + return ( + <> + <Group justify="center" gap={8} className={classes.logo}> + <img + src="/icons/favicon-32x32.png" + alt="Docmost" + width={22} + height={22} + /> + <Text size="28px" fw={700} style={{ userSelect: "none" }}> + Docmost + </Text> + </Group> + {children} + </> + ); +} diff --git a/apps/client/src/features/auth/components/auth.module.css b/apps/client/src/features/auth/components/auth.module.css index 1e7d09d6..9b99200d 100644 --- a/apps/client/src/features/auth/components/auth.module.css +++ b/apps/client/src/features/auth/components/auth.module.css @@ -1,12 +1,20 @@ +.logo { + margin-top: 80px; + + @media (max-width: $mantine-breakpoint-sm) { + margin-top: 30px; + } +} + .container { box-shadow: rgba(0, 0, 0, 0.07) 0px 2px 45px 4px; border-radius: 4px; background: light-dark(var(--mantine-color-body), rgba(0, 0, 0, 0.1)); - margin-top: 150px; + margin-top: 40px; margin-bottom: 20px; @media (max-width: $mantine-breakpoint-sm) { - margin-top: 50px; + margin-top: 20px; margin-bottom: 20px; } } diff --git a/apps/client/src/features/auth/components/forgot-password-form.tsx b/apps/client/src/features/auth/components/forgot-password-form.tsx index b5987b49..cdb249c9 100644 --- a/apps/client/src/features/auth/components/forgot-password-form.tsx +++ b/apps/client/src/features/auth/components/forgot-password-form.tsx @@ -7,6 +7,7 @@ import { Box, Button, Container, Text, TextInput, Title } from "@mantine/core"; import classes from "./auth.module.css"; import { useRedirectIfAuthenticated } from "@/features/auth/hooks/use-redirect-if-authenticated.ts"; import { useTranslation } from "react-i18next"; +import { AuthLayout } from "./auth-layout.tsx"; const formSchema = z.object({ email: z @@ -35,6 +36,7 @@ export function ForgotPasswordForm() { } return ( + <AuthLayout> <Container size={420} className={classes.container}> <Box p="xl" className={classes.containerBox}> <Title order={2} ta="center" fw={500} mb="md"> @@ -69,5 +71,6 @@ export function ForgotPasswordForm() { </form> </Box> </Container> + </AuthLayout> ); } diff --git a/apps/client/src/features/auth/components/invite-sign-up-form.tsx b/apps/client/src/features/auth/components/invite-sign-up-form.tsx index d06ef307..91d8e167 100644 --- a/apps/client/src/features/auth/components/invite-sign-up-form.tsx +++ b/apps/client/src/features/auth/components/invite-sign-up-form.tsx @@ -19,6 +19,7 @@ import { useGetInvitationQuery } from "@/features/workspace/queries/workspace-qu import { useRedirectIfAuthenticated } from "@/features/auth/hooks/use-redirect-if-authenticated.ts"; import { useTranslation } from "react-i18next"; import SsoLogin from "@/ee/components/sso-login.tsx"; +import { AuthLayout } from "./auth-layout.tsx"; const formSchema = z.object({ name: z.string().trim().min(1), @@ -66,6 +67,7 @@ export function InviteSignUpForm() { } return ( + <AuthLayout> <Container size={420} className={classes.container}> <Box p="xl" className={classes.containerBox}> <Title order={2} ta="center" fw={500} mb="md"> @@ -111,5 +113,6 @@ export function InviteSignUpForm() { )} </Box> </Container> + </AuthLayout> ); } diff --git a/apps/client/src/features/auth/components/login-form.tsx b/apps/client/src/features/auth/components/login-form.tsx index c07ebe02..78aaa94b 100644 --- a/apps/client/src/features/auth/components/login-form.tsx +++ b/apps/client/src/features/auth/components/login-form.tsx @@ -21,6 +21,7 @@ import SsoLogin from "@/ee/components/sso-login.tsx"; import { useWorkspacePublicDataQuery } from "@/features/workspace/queries/workspace-query.ts"; import { Error404 } from "@/components/ui/error-404.tsx"; import React from "react"; +import { AuthLayout } from "./auth-layout.tsx"; const formSchema = z.object({ email: z @@ -62,52 +63,54 @@ export function LoginForm() { } return ( - <Container size={420} className={classes.container}> - <Box p="xl" className={classes.containerBox}> - <Title order={2} ta="center" fw={500} mb="md"> - {t("Login")} - + + + + + {t("Login")} + - + - {!data?.enforceSso && ( - <> -
- + {!data?.enforceSso && ( + <> + + - + - - - {t("Forgot your password?")} - - + + + {t("Forgot your password?")} + + - - - - )} -
-
+ + + + )} +
+
+
); } diff --git a/apps/client/src/features/auth/components/password-reset-form.tsx b/apps/client/src/features/auth/components/password-reset-form.tsx index 4fe836ec..d603264f 100644 --- a/apps/client/src/features/auth/components/password-reset-form.tsx +++ b/apps/client/src/features/auth/components/password-reset-form.tsx @@ -6,6 +6,7 @@ import { Box, Button, Container, PasswordInput, Title } from "@mantine/core"; import classes from "./auth.module.css"; import { useRedirectIfAuthenticated } from "@/features/auth/hooks/use-redirect-if-authenticated.ts"; import { useTranslation } from "react-i18next"; +import { AuthLayout } from "./auth-layout.tsx"; const formSchema = z.object({ newPassword: z @@ -38,6 +39,7 @@ export function PasswordResetForm({ resetToken }: PasswordResetFormProps) { } return ( + @@ -59,5 +61,6 @@ export function PasswordResetForm({ resetToken }: PasswordResetFormProps) { </form> </Box> </Container> + </AuthLayout> ); } diff --git a/apps/client/src/features/auth/components/setup-workspace-form.tsx b/apps/client/src/features/auth/components/setup-workspace-form.tsx index 6eaf3d12..8aa55e99 100644 --- a/apps/client/src/features/auth/components/setup-workspace-form.tsx +++ b/apps/client/src/features/auth/components/setup-workspace-form.tsx @@ -19,6 +19,7 @@ import SsoCloudSignup from "@/ee/components/sso-cloud-signup.tsx"; import { isCloud } from "@/lib/config.ts"; import { Link } from "react-router-dom"; import APP_ROUTE from "@/lib/app-route.ts"; +import { AuthLayout } from "./auth-layout.tsx"; const formSchema = z.object({ workspaceName: z.string().trim().max(50).optional(), @@ -50,7 +51,7 @@ export function SetupWorkspaceForm() { } return ( - <div> + <AuthLayout> <Container size={420} className={classes.container}> <Box p="xl" className={classes.containerBox}> <Title order={2} ta="center" fw={500} mb="md"> @@ -117,6 +118,6 @@ export function SetupWorkspaceForm() { </Anchor> </Text> )} - </div> + </AuthLayout> ); } diff --git a/apps/client/src/lib/api-client.ts b/apps/client/src/lib/api-client.ts index 632811db..17f7e102 100644 --- a/apps/client/src/lib/api-client.ts +++ b/apps/client/src/lib/api-client.ts @@ -74,8 +74,12 @@ function redirectToLogin() { ]; if (!exemptPaths.some((path) => window.location.pathname.startsWith(path))) { const redirectTo = window.location.pathname; - const params = new URLSearchParams({ redirect: redirectTo }); - window.location.href = `${APP_ROUTE.AUTH.LOGIN}?${params.toString()}`; + if (redirectTo === APP_ROUTE.HOME) { + window.location.href = APP_ROUTE.AUTH.LOGIN; + } else { + const params = new URLSearchParams({ redirect: redirectTo }); + window.location.href = `${APP_ROUTE.AUTH.LOGIN}?${params.toString()}`; + } } }