feat: auth pages layout (#2042)

* auth pages layout
* exclude home route from redirect
* fix margin
This commit is contained in:
Philip Okugbe
2026-03-22 16:40:50 +00:00
committed by GitHub
parent 6683c515cf
commit 975b4dcaab
12 changed files with 124 additions and 61 deletions
@@ -21,6 +21,7 @@ import { useTranslation } from "react-i18next";
import JoinedWorkspaces from "@/ee/components/joined-workspaces.tsx"; import JoinedWorkspaces from "@/ee/components/joined-workspaces.tsx";
import { useJoinedWorkspacesQuery } from "@/ee/cloud/query/cloud-query.ts"; import { useJoinedWorkspacesQuery } from "@/ee/cloud/query/cloud-query.ts";
import { findWorkspacesByEmail } from "@/ee/cloud/service/cloud-service.ts"; import { findWorkspacesByEmail } from "@/ee/cloud/service/cloud-service.ts";
import { AuthLayout } from "@/features/auth/components/auth-layout.tsx";
const formSchema = z.object({ const formSchema = z.object({
hostname: z.string().min(1, { message: "subdomain is required" }), hostname: z.string().min(1, { message: "subdomain is required" }),
@@ -82,7 +83,7 @@ export function CloudLoginForm() {
} }
return ( return (
<div> <AuthLayout>
<Container size={420} className={classes.container}> <Container size={420} className={classes.container}>
<Box p="xl" className={classes.containerBox}> <Box p="xl" className={classes.containerBox}>
<Title order={2} ta="center" fw={500} mb="md"> <Title order={2} ta="center" fw={500} mb="md">
@@ -145,12 +146,12 @@ export function CloudLoginForm() {
</Box> </Box>
</Container> </Container>
<Text ta="center"> <Text ta="center" mb="xl">
{t("Don't have a workspace?")}{" "} {t("Don't have a workspace?")}{" "}
<Anchor component={Link} to={APP_ROUTE.AUTH.CREATE_WORKSPACE} fw={500}> <Anchor component={Link} to={APP_ROUTE.AUTH.CREATE_WORKSPACE} fw={500}>
{t("Create new workspace")} {t("Create new workspace")}
</Anchor> </Anchor>
</Text> </Text>
</div> </AuthLayout>
); );
} }
@@ -22,6 +22,7 @@ import APP_ROUTE, { getPostLoginRedirect } from "@/lib/app-route";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { z } from "zod/v4"; import { z } from "zod/v4";
import { MfaBackupCodeInput } from "./mfa-backup-code-input"; import { MfaBackupCodeInput } from "./mfa-backup-code-input";
import { AuthLayout } from "@/features/auth/components/auth-layout.tsx";
const formSchema = z.object({ const formSchema = z.object({
code: z code: z
@@ -66,6 +67,7 @@ export function MfaChallenge() {
}; };
return ( return (
<AuthLayout>
<Container size={420} className={classes.container}> <Container size={420} className={classes.container}>
<Paper radius="lg" p={40} className={classes.paper}> <Paper radius="lg" p={40} className={classes.paper}>
<Stack align="center" gap="xl"> <Stack align="center" gap="xl">
@@ -157,5 +159,6 @@ export function MfaChallenge() {
</Stack> </Stack>
</Paper> </Paper>
</Container> </Container>
</AuthLayout>
); );
} }
@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
import { MfaSetupModal } from "@/ee/mfa"; import { MfaSetupModal } from "@/ee/mfa";
import APP_ROUTE, { getPostLoginRedirect } from "@/lib/app-route.ts"; import APP_ROUTE, { getPostLoginRedirect } from "@/lib/app-route.ts";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { AuthLayout } from "@/features/auth/components/auth-layout.tsx";
export default function MfaSetupRequired() { export default function MfaSetupRequired() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -15,6 +16,7 @@ export default function MfaSetupRequired() {
}; };
return ( return (
<AuthLayout>
<Container size="sm" py="xl"> <Container size="sm" py="xl">
<Paper shadow="sm" p="xl" radius="md" withBorder> <Paper shadow="sm" p="xl" radius="md" withBorder>
<Stack> <Stack>
@@ -44,5 +46,6 @@ export default function MfaSetupRequired() {
</Stack> </Stack>
</Paper> </Paper>
</Container> </Container>
</AuthLayout>
); );
} }
+15 -10
View File
@@ -9,6 +9,7 @@ import {
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import APP_ROUTE from "@/lib/app-route.ts"; import APP_ROUTE from "@/lib/app-route.ts";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { AuthLayout } from "@/features/auth/components/auth-layout.tsx";
export default function VerifyEmail() { export default function VerifyEmail() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -59,20 +60,23 @@ export default function VerifyEmail() {
if (token) { if (token) {
return ( return (
<Container size={420} className={classes.container}> <AuthLayout>
<Box p="xl" className={classes.containerBox}> <Container size={420} className={classes.container}>
<Title order={2} ta="center" fw={500} mb="md"> <Box p="xl" className={classes.containerBox}>
{t("Verifying your email")} <Title order={2} ta="center" fw={500} mb="md">
</Title> {t("Verifying your email")}
<Text ta="center" c="dimmed"> </Title>
{t("Please wait...")} <Text ta="center" c="dimmed">
</Text> {t("Please wait...")}
</Box> </Text>
</Container> </Box>
</Container>
</AuthLayout>
); );
} }
return ( return (
<AuthLayout>
<Container size={420} className={classes.container}> <Container size={420} className={classes.container}>
<Box p="xl" className={classes.containerBox}> <Box p="xl" className={classes.containerBox}>
<Title order={2} ta="center" fw={500} mb="md"> <Title order={2} ta="center" fw={500} mb="md">
@@ -103,5 +107,6 @@ export default function VerifyEmail() {
)} )}
</Box> </Box>
</Container> </Container>
</AuthLayout>
); );
} }
@@ -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}
</>
);
}
@@ -1,12 +1,20 @@
.logo {
margin-top: 80px;
@media (max-width: $mantine-breakpoint-sm) {
margin-top: 30px;
}
}
.container { .container {
box-shadow: rgba(0, 0, 0, 0.07) 0px 2px 45px 4px; box-shadow: rgba(0, 0, 0, 0.07) 0px 2px 45px 4px;
border-radius: 4px; border-radius: 4px;
background: light-dark(var(--mantine-color-body), rgba(0, 0, 0, 0.1)); background: light-dark(var(--mantine-color-body), rgba(0, 0, 0, 0.1));
margin-top: 150px; margin-top: 40px;
margin-bottom: 20px; margin-bottom: 20px;
@media (max-width: $mantine-breakpoint-sm) { @media (max-width: $mantine-breakpoint-sm) {
margin-top: 50px; margin-top: 20px;
margin-bottom: 20px; margin-bottom: 20px;
} }
} }
@@ -7,6 +7,7 @@ import { Box, Button, Container, Text, TextInput, Title } from "@mantine/core";
import classes from "./auth.module.css"; import classes from "./auth.module.css";
import { useRedirectIfAuthenticated } from "@/features/auth/hooks/use-redirect-if-authenticated.ts"; import { useRedirectIfAuthenticated } from "@/features/auth/hooks/use-redirect-if-authenticated.ts";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { AuthLayout } from "./auth-layout.tsx";
const formSchema = z.object({ const formSchema = z.object({
email: z email: z
@@ -35,6 +36,7 @@ export function ForgotPasswordForm() {
} }
return ( return (
<AuthLayout>
<Container size={420} className={classes.container}> <Container size={420} className={classes.container}>
<Box p="xl" className={classes.containerBox}> <Box p="xl" className={classes.containerBox}>
<Title order={2} ta="center" fw={500} mb="md"> <Title order={2} ta="center" fw={500} mb="md">
@@ -69,5 +71,6 @@ export function ForgotPasswordForm() {
</form> </form>
</Box> </Box>
</Container> </Container>
</AuthLayout>
); );
} }
@@ -19,6 +19,7 @@ import { useGetInvitationQuery } from "@/features/workspace/queries/workspace-qu
import { useRedirectIfAuthenticated } from "@/features/auth/hooks/use-redirect-if-authenticated.ts"; import { useRedirectIfAuthenticated } from "@/features/auth/hooks/use-redirect-if-authenticated.ts";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import SsoLogin from "@/ee/components/sso-login.tsx"; import SsoLogin from "@/ee/components/sso-login.tsx";
import { AuthLayout } from "./auth-layout.tsx";
const formSchema = z.object({ const formSchema = z.object({
name: z.string().trim().min(1), name: z.string().trim().min(1),
@@ -66,6 +67,7 @@ export function InviteSignUpForm() {
} }
return ( return (
<AuthLayout>
<Container size={420} className={classes.container}> <Container size={420} className={classes.container}>
<Box p="xl" className={classes.containerBox}> <Box p="xl" className={classes.containerBox}>
<Title order={2} ta="center" fw={500} mb="md"> <Title order={2} ta="center" fw={500} mb="md">
@@ -111,5 +113,6 @@ export function InviteSignUpForm() {
)} )}
</Box> </Box>
</Container> </Container>
</AuthLayout>
); );
} }
@@ -21,6 +21,7 @@ import SsoLogin from "@/ee/components/sso-login.tsx";
import { useWorkspacePublicDataQuery } from "@/features/workspace/queries/workspace-query.ts"; import { useWorkspacePublicDataQuery } from "@/features/workspace/queries/workspace-query.ts";
import { Error404 } from "@/components/ui/error-404.tsx"; import { Error404 } from "@/components/ui/error-404.tsx";
import React from "react"; import React from "react";
import { AuthLayout } from "./auth-layout.tsx";
const formSchema = z.object({ const formSchema = z.object({
email: z email: z
@@ -62,52 +63,54 @@ export function LoginForm() {
} }
return ( return (
<Container size={420} className={classes.container}> <AuthLayout>
<Box p="xl" className={classes.containerBox}> <Container size={420} className={classes.container}>
<Title order={2} ta="center" fw={500} mb="md"> <Box p="xl" className={classes.containerBox}>
{t("Login")} <Title order={2} ta="center" fw={500} mb="md">
</Title> {t("Login")}
</Title>
<SsoLogin /> <SsoLogin />
{!data?.enforceSso && ( {!data?.enforceSso && (
<> <>
<form onSubmit={form.onSubmit(onSubmit)}> <form onSubmit={form.onSubmit(onSubmit)}>
<TextInput <TextInput
id="email" id="email"
type="email" type="email"
label={t("Email")} label={t("Email")}
placeholder="email@example.com" placeholder="email@example.com"
variant="filled" variant="filled"
{...form.getInputProps("email")} {...form.getInputProps("email")}
/> />
<PasswordInput <PasswordInput
label={t("Password")} label={t("Password")}
placeholder={t("Your password")} placeholder={t("Your password")}
variant="filled" variant="filled"
mt="md" mt="md"
{...form.getInputProps("password")} {...form.getInputProps("password")}
/> />
<Group justify="flex-end" mt="sm"> <Group justify="flex-end" mt="sm">
<Anchor <Anchor
to={APP_ROUTE.AUTH.FORGOT_PASSWORD} to={APP_ROUTE.AUTH.FORGOT_PASSWORD}
component={Link} component={Link}
underline="never" underline="never"
size="sm" size="sm"
> >
{t("Forgot your password?")} {t("Forgot your password?")}
</Anchor> </Anchor>
</Group> </Group>
<Button type="submit" fullWidth mt="md" loading={isLoading}> <Button type="submit" fullWidth mt="md" loading={isLoading}>
{t("Sign In")} {t("Sign In")}
</Button> </Button>
</form> </form>
</> </>
)} )}
</Box> </Box>
</Container> </Container>
</AuthLayout>
); );
} }
@@ -6,6 +6,7 @@ import { Box, Button, Container, PasswordInput, Title } from "@mantine/core";
import classes from "./auth.module.css"; import classes from "./auth.module.css";
import { useRedirectIfAuthenticated } from "@/features/auth/hooks/use-redirect-if-authenticated.ts"; import { useRedirectIfAuthenticated } from "@/features/auth/hooks/use-redirect-if-authenticated.ts";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { AuthLayout } from "./auth-layout.tsx";
const formSchema = z.object({ const formSchema = z.object({
newPassword: z newPassword: z
@@ -38,6 +39,7 @@ export function PasswordResetForm({ resetToken }: PasswordResetFormProps) {
} }
return ( return (
<AuthLayout>
<Container size={420} className={classes.container}> <Container size={420} className={classes.container}>
<Box p="xl" className={classes.containerBox}> <Box p="xl" className={classes.containerBox}>
<Title order={2} ta="center" fw={500} mb="md"> <Title order={2} ta="center" fw={500} mb="md">
@@ -59,5 +61,6 @@ export function PasswordResetForm({ resetToken }: PasswordResetFormProps) {
</form> </form>
</Box> </Box>
</Container> </Container>
</AuthLayout>
); );
} }
@@ -19,6 +19,7 @@ import SsoCloudSignup from "@/ee/components/sso-cloud-signup.tsx";
import { isCloud } from "@/lib/config.ts"; import { isCloud } from "@/lib/config.ts";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import APP_ROUTE from "@/lib/app-route.ts"; import APP_ROUTE from "@/lib/app-route.ts";
import { AuthLayout } from "./auth-layout.tsx";
const formSchema = z.object({ const formSchema = z.object({
workspaceName: z.string().trim().max(50).optional(), workspaceName: z.string().trim().max(50).optional(),
@@ -50,7 +51,7 @@ export function SetupWorkspaceForm() {
} }
return ( return (
<div> <AuthLayout>
<Container size={420} className={classes.container}> <Container size={420} className={classes.container}>
<Box p="xl" className={classes.containerBox}> <Box p="xl" className={classes.containerBox}>
<Title order={2} ta="center" fw={500} mb="md"> <Title order={2} ta="center" fw={500} mb="md">
@@ -117,6 +118,6 @@ export function SetupWorkspaceForm() {
</Anchor> </Anchor>
</Text> </Text>
)} )}
</div> </AuthLayout>
); );
} }
+6 -2
View File
@@ -74,8 +74,12 @@ function redirectToLogin() {
]; ];
if (!exemptPaths.some((path) => window.location.pathname.startsWith(path))) { if (!exemptPaths.some((path) => window.location.pathname.startsWith(path))) {
const redirectTo = window.location.pathname; const redirectTo = window.location.pathname;
const params = new URLSearchParams({ redirect: redirectTo }); if (redirectTo === APP_ROUTE.HOME) {
window.location.href = `${APP_ROUTE.AUTH.LOGIN}?${params.toString()}`; window.location.href = APP_ROUTE.AUTH.LOGIN;
} else {
const params = new URLSearchParams({ redirect: redirectTo });
window.location.href = `${APP_ROUTE.AUTH.LOGIN}?${params.toString()}`;
}
} }
} }