mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
feat: auth pages layout (#2042)
* auth pages layout * exclude home route from redirect * fix margin
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,6 +60,7 @@ export default function VerifyEmail() {
|
|||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
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,10 +71,12 @@ export default function VerifyEmail() {
|
|||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Container>
|
</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,6 +63,7 @@ export function LoginForm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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">
|
||||||
@@ -109,5 +111,6 @@ export function LoginForm() {
|
|||||||
)}
|
)}
|
||||||
</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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,9 +74,13 @@ 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;
|
||||||
|
if (redirectTo === APP_ROUTE.HOME) {
|
||||||
|
window.location.href = APP_ROUTE.AUTH.LOGIN;
|
||||||
|
} else {
|
||||||
const params = new URLSearchParams({ redirect: redirectTo });
|
const params = new URLSearchParams({ redirect: redirectTo });
|
||||||
window.location.href = `${APP_ROUTE.AUTH.LOGIN}?${params.toString()}`;
|
window.location.href = `${APP_ROUTE.AUTH.LOGIN}?${params.toString()}`;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default api;
|
export default api;
|
||||||
|
|||||||
Reference in New Issue
Block a user