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:
@@ -1,18 +0,0 @@
|
||||
import { Space, Title } from "@mantine/core";
|
||||
import AdminConfigTable from "../../components/admin/configuration/AdminConfigTable";
|
||||
import Meta from "../../components/Meta";
|
||||
|
||||
const AdminConfig = () => {
|
||||
return (
|
||||
<>
|
||||
<Meta title="Configuration" />
|
||||
<Title mb={30} order={3}>
|
||||
Configuration
|
||||
</Title>
|
||||
<AdminConfigTable />
|
||||
<Space h="xl" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminConfig;
|
||||
148
frontend/src/pages/admin/config/[category].tsx
Normal file
148
frontend/src/pages/admin/config/[category].tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import {
|
||||
AppShell,
|
||||
Box,
|
||||
Button,
|
||||
Container,
|
||||
Group,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
useMantineTheme,
|
||||
} from "@mantine/core";
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import AdminConfigInput from "../../../components/admin/configuration/AdminConfigInput";
|
||||
import ConfigurationHeader from "../../../components/admin/configuration/ConfigurationHeader";
|
||||
import ConfigurationNavBar from "../../../components/admin/configuration/ConfigurationNavBar";
|
||||
import TestEmailButton from "../../../components/admin/configuration/TestEmailButton";
|
||||
import CenterLoader from "../../../components/core/CenterLoader";
|
||||
import Meta from "../../../components/Meta";
|
||||
import useConfig from "../../../hooks/config.hook";
|
||||
import configService from "../../../services/config.service";
|
||||
import { AdminConfig, UpdateConfig } from "../../../types/config.type";
|
||||
import {
|
||||
capitalizeFirstLetter,
|
||||
configVariableToFriendlyName,
|
||||
} from "../../../utils/string.util";
|
||||
import toast from "../../../utils/toast.util";
|
||||
|
||||
export default function AppShellDemo() {
|
||||
const theme = useMantineTheme();
|
||||
const router = useRouter();
|
||||
|
||||
const [isMobileNavBarOpened, setIsMobileNavBarOpened] = useState(false);
|
||||
const isMobile = useMediaQuery("(max-width: 560px)");
|
||||
const config = useConfig();
|
||||
|
||||
const categoryId = router.query.category as string;
|
||||
|
||||
const [configVariables, setConfigVariables] = useState<AdminConfig[]>();
|
||||
const [updatedConfigVariables, setUpdatedConfigVariables] = useState<
|
||||
UpdateConfig[]
|
||||
>([]);
|
||||
|
||||
const saveConfigVariables = async () => {
|
||||
await configService
|
||||
.updateMany(updatedConfigVariables)
|
||||
.then(() => {
|
||||
setUpdatedConfigVariables([]);
|
||||
toast.success("Configurations updated successfully");
|
||||
})
|
||||
.catch(toast.axiosError);
|
||||
config.refresh();
|
||||
};
|
||||
|
||||
const updateConfigVariable = (configVariable: UpdateConfig) => {
|
||||
const index = updatedConfigVariables.findIndex(
|
||||
(item) => item.key === configVariable.key
|
||||
);
|
||||
if (index > -1) {
|
||||
updatedConfigVariables[index] = configVariable;
|
||||
} else {
|
||||
setUpdatedConfigVariables([...updatedConfigVariables, configVariable]);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
configService.getByCategory(categoryId).then((configVariables) => {
|
||||
setConfigVariables(configVariables);
|
||||
});
|
||||
}, [categoryId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Meta title="Configuration" />
|
||||
<AppShell
|
||||
styles={{
|
||||
main: {
|
||||
background:
|
||||
theme.colorScheme === "dark"
|
||||
? theme.colors.dark[8]
|
||||
: theme.colors.gray[0],
|
||||
},
|
||||
}}
|
||||
navbar={
|
||||
<ConfigurationNavBar
|
||||
categoryId={categoryId}
|
||||
isMobileNavBarOpened={isMobileNavBarOpened}
|
||||
setIsMobileNavBarOpened={setIsMobileNavBarOpened}
|
||||
/>
|
||||
}
|
||||
header={
|
||||
<ConfigurationHeader
|
||||
isMobileNavBarOpened={isMobileNavBarOpened}
|
||||
setIsMobileNavBarOpened={setIsMobileNavBarOpened}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Container size="lg">
|
||||
{!configVariables ? (
|
||||
<CenterLoader />
|
||||
) : (
|
||||
<>
|
||||
<Stack>
|
||||
<Title mb="md" order={3}>
|
||||
{capitalizeFirstLetter(categoryId)}
|
||||
</Title>
|
||||
{configVariables.map((configVariable) => (
|
||||
<Group key={configVariable.key} position="apart">
|
||||
<Stack
|
||||
style={{ maxWidth: isMobile ? "100%" : "40%" }}
|
||||
spacing={0}
|
||||
>
|
||||
<Title order={6}>
|
||||
{configVariableToFriendlyName(configVariable.name)}
|
||||
</Title>
|
||||
<Text color="dimmed" size="sm" mb="xs">
|
||||
{configVariable.description}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack></Stack>
|
||||
<Box style={{ width: isMobile ? "100%" : "50%" }}>
|
||||
<AdminConfigInput
|
||||
key={configVariable.key}
|
||||
configVariable={configVariable}
|
||||
updateConfigVariable={updateConfigVariable}
|
||||
/>
|
||||
</Box>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
<Group mt="lg" position="right">
|
||||
{categoryId == "smtp" && (
|
||||
<TestEmailButton
|
||||
configVariablesChanged={updatedConfigVariables.length != 0}
|
||||
saveConfigVariables={saveConfigVariables}
|
||||
/>
|
||||
)}
|
||||
<Button onClick={saveConfigVariables}>Save</Button>
|
||||
</Group>
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
</AppShell>
|
||||
</>
|
||||
);
|
||||
}
|
||||
15
frontend/src/pages/admin/config/index.tsx
Normal file
15
frontend/src/pages/admin/config/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
export function getServerSideProps() {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: "/admin/config/general",
|
||||
},
|
||||
props: {},
|
||||
};
|
||||
}
|
||||
|
||||
const Config = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export default Config;
|
||||
59
frontend/src/pages/admin/intro.tsx
Normal file
59
frontend/src/pages/admin/intro.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
Anchor,
|
||||
Button,
|
||||
Center,
|
||||
Container,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import Link from "next/link";
|
||||
import Logo from "../../components/Logo";
|
||||
import Meta from "../../components/Meta";
|
||||
|
||||
const Intro = () => {
|
||||
return (
|
||||
<>
|
||||
<Meta title="Intro" />
|
||||
<Container size="xs">
|
||||
<Stack>
|
||||
<Center>
|
||||
<Logo height={80} width={80} />
|
||||
</Center>
|
||||
<Center>
|
||||
<Title order={2}>Welcome to Pingvin Share</Title>
|
||||
</Center>
|
||||
<Text>
|
||||
If you enjoy Pingvin Share please ⭐️ it on{" "}
|
||||
<Anchor
|
||||
target="_blank"
|
||||
href="https://github.com/stonith404/pingvin-share"
|
||||
>
|
||||
GitHub
|
||||
</Anchor>{" "}
|
||||
or{" "}
|
||||
<Anchor
|
||||
target="_blank"
|
||||
href="https://github.com/sponsors/stonith404"
|
||||
>
|
||||
buy me a coffee
|
||||
</Anchor>{" "}
|
||||
if you want to support my work.
|
||||
</Text>
|
||||
<Text>Enough talked, have fun with Pingvin Share!</Text>
|
||||
<Text mt="lg">How to you want to continue?</Text>
|
||||
<Stack>
|
||||
<Button href="/admin/config" component={Link}>
|
||||
Customize configuration
|
||||
</Button>
|
||||
<Button href="/" component={Link} variant="light">
|
||||
Explore Pingvin Share
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Intro;
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Box, Stack, Text, Title } from "@mantine/core";
|
||||
import AdminConfigTable from "../../components/admin/configuration/AdminConfigTable";
|
||||
|
||||
import Logo from "../../components/Logo";
|
||||
import Meta from "../../components/Meta";
|
||||
|
||||
const Setup = () => {
|
||||
return (
|
||||
<>
|
||||
<Meta title="Setup" />
|
||||
<Stack align="center">
|
||||
<Logo height={80} width={80} />
|
||||
<Title order={2}>Welcome to Pingvin Share</Title>
|
||||
<Text>Let's customize Pingvin Share for you! </Text>
|
||||
<Box style={{ width: "100%" }}>
|
||||
<AdminConfigTable />
|
||||
</Box>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Setup;
|
||||
@@ -58,7 +58,7 @@ const Users = () => {
|
||||
</Title>
|
||||
<Button
|
||||
onClick={() =>
|
||||
showCreateUserModal(modals, config.get("SMTP_ENABLED"), getUsers)
|
||||
showCreateUserModal(modals, config.get("smtp.enabled"), getUsers)
|
||||
}
|
||||
leftIcon={<TbPlus size={20} />}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user