feat: custom branding (#112)
* add first concept * remove setup status * split config page in multiple components * add custom branding docs * add test email button * fix invalid email from header * add migration * mount images to host * update docs * remove unused endpoint * run formatter
This commit is contained in:
219
frontend/src/components/header/Header.tsx
Normal file
219
frontend/src/components/header/Header.tsx
Normal file
@@ -0,0 +1,219 @@
|
||||
import {
|
||||
Box,
|
||||
Burger,
|
||||
Container,
|
||||
createStyles,
|
||||
Group,
|
||||
Header as MantineHeader,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
Transition,
|
||||
} from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
import useConfig from "../../hooks/config.hook";
|
||||
import useUser from "../../hooks/user.hook";
|
||||
import Logo from "../Logo";
|
||||
import ActionAvatar from "./ActionAvatar";
|
||||
import NavbarShareMenu from "./NavbarShareMenu";
|
||||
|
||||
const HEADER_HEIGHT = 60;
|
||||
|
||||
type NavLink = {
|
||||
link?: string;
|
||||
label?: string;
|
||||
component?: ReactNode;
|
||||
action?: () => Promise<void>;
|
||||
};
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
root: {
|
||||
position: "relative",
|
||||
zIndex: 1,
|
||||
},
|
||||
|
||||
dropdown: {
|
||||
position: "absolute",
|
||||
top: HEADER_HEIGHT,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 0,
|
||||
borderTopRightRadius: 0,
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopWidth: 0,
|
||||
overflow: "hidden",
|
||||
|
||||
[theme.fn.largerThan("sm")]: {
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
|
||||
header: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
},
|
||||
|
||||
links: {
|
||||
[theme.fn.smallerThan("sm")]: {
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
|
||||
burger: {
|
||||
[theme.fn.largerThan("sm")]: {
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
|
||||
link: {
|
||||
display: "block",
|
||||
lineHeight: 1,
|
||||
padding: "8px 12px",
|
||||
borderRadius: theme.radius.sm,
|
||||
textDecoration: "none",
|
||||
color:
|
||||
theme.colorScheme === "dark"
|
||||
? theme.colors.dark[0]
|
||||
: theme.colors.gray[7],
|
||||
fontSize: theme.fontSizes.sm,
|
||||
fontWeight: 500,
|
||||
|
||||
"&:hover": {
|
||||
backgroundColor:
|
||||
theme.colorScheme === "dark"
|
||||
? theme.colors.dark[6]
|
||||
: theme.colors.gray[0],
|
||||
},
|
||||
|
||||
[theme.fn.smallerThan("sm")]: {
|
||||
borderRadius: 0,
|
||||
padding: theme.spacing.md,
|
||||
},
|
||||
},
|
||||
|
||||
linkActive: {
|
||||
"&, &:hover": {
|
||||
backgroundColor:
|
||||
theme.colorScheme === "dark"
|
||||
? theme.fn.rgba(theme.colors[theme.primaryColor][9], 0.25)
|
||||
: theme.colors[theme.primaryColor][0],
|
||||
color:
|
||||
theme.colors[theme.primaryColor][theme.colorScheme === "dark" ? 3 : 7],
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const Header = () => {
|
||||
const { user } = useUser();
|
||||
const router = useRouter();
|
||||
const config = useConfig();
|
||||
|
||||
const [opened, toggleOpened] = useDisclosure(false);
|
||||
|
||||
const [currentRoute, setCurrentRoute] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentRoute(router.pathname);
|
||||
}, [router.pathname]);
|
||||
|
||||
const authenticatedLinks: NavLink[] = [
|
||||
{
|
||||
link: "/upload",
|
||||
label: "Upload",
|
||||
},
|
||||
{
|
||||
component: <NavbarShareMenu />,
|
||||
},
|
||||
{
|
||||
component: <ActionAvatar />,
|
||||
},
|
||||
];
|
||||
|
||||
let unauthenticatedLinks: NavLink[] = [
|
||||
{
|
||||
link: "/auth/signIn",
|
||||
label: "Sign in",
|
||||
},
|
||||
];
|
||||
|
||||
if (config.get("share.allowUnauthenticatedShares")) {
|
||||
unauthenticatedLinks.unshift({
|
||||
link: "/upload",
|
||||
label: "Upload",
|
||||
});
|
||||
}
|
||||
|
||||
if (config.get("general.showHomePage"))
|
||||
unauthenticatedLinks.unshift({
|
||||
link: "/",
|
||||
label: "Home",
|
||||
});
|
||||
|
||||
if (config.get("share.allowRegistration"))
|
||||
unauthenticatedLinks.push({
|
||||
link: "/auth/signUp",
|
||||
label: "Sign up",
|
||||
});
|
||||
|
||||
const { classes, cx } = useStyles();
|
||||
const items = (
|
||||
<>
|
||||
{(user ? authenticatedLinks : unauthenticatedLinks).map((link, i) => {
|
||||
if (link.component) {
|
||||
return (
|
||||
<Box pl={5} py={15} key={i}>
|
||||
{link.component}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Link
|
||||
key={link.label}
|
||||
href={link.link ?? ""}
|
||||
onClick={() => toggleOpened.toggle()}
|
||||
className={cx(classes.link, {
|
||||
[classes.linkActive]: currentRoute == link.link,
|
||||
})}
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<MantineHeader height={HEADER_HEIGHT} mb={40} className={classes.root}>
|
||||
<Container className={classes.header}>
|
||||
<Link href="/" passHref>
|
||||
<Group>
|
||||
<Logo height={35} width={35} />
|
||||
<Text weight={600}>{config.get("general.appName")}</Text>
|
||||
</Group>
|
||||
</Link>
|
||||
<Group spacing={5} className={classes.links}>
|
||||
<Group>{items} </Group>
|
||||
</Group>
|
||||
<Burger
|
||||
opened={opened}
|
||||
onClick={() => toggleOpened.toggle()}
|
||||
className={classes.burger}
|
||||
size="sm"
|
||||
/>
|
||||
<Transition transition="pop-top-right" duration={200} mounted={opened}>
|
||||
{(styles) => (
|
||||
<Paper className={classes.dropdown} withBorder style={styles}>
|
||||
<Stack spacing={0}> {items}</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
</Transition>
|
||||
</Container>
|
||||
</MantineHeader>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
Reference in New Issue
Block a user