Merge pull request #2388 from automatisch/introduce-customizable-footer
feat: introduce customizable footer
This commit is contained in:
@@ -11,6 +11,15 @@ export default async (request, response) => {
|
|||||||
|
|
||||||
const configParams = (request) => {
|
const configParams = (request) => {
|
||||||
const {
|
const {
|
||||||
|
enableFooter,
|
||||||
|
footerBackgroundColor,
|
||||||
|
footerCopyrightText,
|
||||||
|
footerDocsUrl,
|
||||||
|
footerImprintUrl,
|
||||||
|
footerLogoSvgData,
|
||||||
|
footerPrivacyPolicyUrl,
|
||||||
|
footerTextColor,
|
||||||
|
footerTosUrl,
|
||||||
logoSvgData,
|
logoSvgData,
|
||||||
palettePrimaryDark,
|
palettePrimaryDark,
|
||||||
palettePrimaryLight,
|
palettePrimaryLight,
|
||||||
@@ -19,6 +28,15 @@ const configParams = (request) => {
|
|||||||
} = request.body;
|
} = request.body;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
enableFooter,
|
||||||
|
footerBackgroundColor,
|
||||||
|
footerCopyrightText,
|
||||||
|
footerDocsUrl,
|
||||||
|
footerImprintUrl,
|
||||||
|
footerLogoSvgData,
|
||||||
|
footerPrivacyPolicyUrl,
|
||||||
|
footerTextColor,
|
||||||
|
footerTosUrl,
|
||||||
logoSvgData,
|
logoSvgData,
|
||||||
palettePrimaryDark,
|
palettePrimaryDark,
|
||||||
palettePrimaryLight,
|
palettePrimaryLight,
|
||||||
|
|||||||
@@ -25,6 +25,18 @@ describe('PATCH /api/v1/admin/config', () => {
|
|||||||
const palettePrimaryMain = '#00adef';
|
const palettePrimaryMain = '#00adef';
|
||||||
const palettePrimaryDark = '#222222';
|
const palettePrimaryDark = '#222222';
|
||||||
const palettePrimaryLight = '#f90707';
|
const palettePrimaryLight = '#f90707';
|
||||||
|
const enableFooter = true;
|
||||||
|
const footerCopyrightText = '© AB Software GmbH';
|
||||||
|
const footerBackgroundColor = '#FFFFFF';
|
||||||
|
const footerTextColor = '#000000';
|
||||||
|
const footerDocsUrl = 'https://automatisch.io/docs';
|
||||||
|
const footerTosUrl = 'https://automatisch.io/terms';
|
||||||
|
const footerPrivacyPolicyUrl = 'https://automatisch.io/privacy';
|
||||||
|
const footerImprintUrl = 'https://automatisch.io/imprint';
|
||||||
|
|
||||||
|
const footerLogoSvgData =
|
||||||
|
'<svg width="25" height="25" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100"><rect width="100%" height="100%" fill="white" /><text x="10" y="40" font-family="Arial" font-size="40" fill="black">Sample Footer Logo</text></svg>';
|
||||||
|
|
||||||
const logoSvgData =
|
const logoSvgData =
|
||||||
'<svg width="25" height="25" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100"><rect width="100%" height="100%" fill="white" /><text x="10" y="40" font-family="Arial" font-size="40" fill="black">A</text></svg>';
|
'<svg width="25" height="25" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100"><rect width="100%" height="100%" fill="white" /><text x="10" y="40" font-family="Arial" font-size="40" fill="black">A</text></svg>';
|
||||||
|
|
||||||
@@ -34,6 +46,15 @@ describe('PATCH /api/v1/admin/config', () => {
|
|||||||
palettePrimaryDark: palettePrimaryDark,
|
palettePrimaryDark: palettePrimaryDark,
|
||||||
palettePrimaryLight: palettePrimaryLight,
|
palettePrimaryLight: palettePrimaryLight,
|
||||||
logoSvgData: logoSvgData,
|
logoSvgData: logoSvgData,
|
||||||
|
enableFooter,
|
||||||
|
footerCopyrightText,
|
||||||
|
footerBackgroundColor,
|
||||||
|
footerTextColor,
|
||||||
|
footerDocsUrl,
|
||||||
|
footerTosUrl,
|
||||||
|
footerPrivacyPolicyUrl,
|
||||||
|
footerImprintUrl,
|
||||||
|
footerLogoSvgData,
|
||||||
};
|
};
|
||||||
|
|
||||||
await updateConfig(appConfig);
|
await updateConfig(appConfig);
|
||||||
|
|||||||
@@ -28,6 +28,15 @@ describe('GET /api/v1/automatisch/config', () => {
|
|||||||
palettePrimaryMain: '#0059F7',
|
palettePrimaryMain: '#0059F7',
|
||||||
title: 'Sample Title',
|
title: 'Sample Title',
|
||||||
enableTemplates: true,
|
enableTemplates: true,
|
||||||
|
enableFooter: true,
|
||||||
|
footerLogoSvgData: '<svg>Sample Footer Logo</svg>',
|
||||||
|
footerCopyrightText: '© AB Software GmbH',
|
||||||
|
footerBackgroundColor: '#FFFFFF',
|
||||||
|
footerTextColor: '#000000',
|
||||||
|
footerDocsUrl: 'https://automatisch.io/docs',
|
||||||
|
footerTosUrl: 'https://automatisch.io/terms',
|
||||||
|
footerPrivacyPolicyUrl: 'https://automatisch.io/privacy',
|
||||||
|
footerImprintUrl: 'https://automatisch.io/imprint',
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
@@ -41,6 +50,16 @@ describe('GET /api/v1/automatisch/config', () => {
|
|||||||
additionalDrawerLink: 'link',
|
additionalDrawerLink: 'link',
|
||||||
additionalDrawerLinkIcon: 'icon',
|
additionalDrawerLinkIcon: 'icon',
|
||||||
additionalDrawerLinkText: 'text',
|
additionalDrawerLinkText: 'text',
|
||||||
|
enableTemplates: true,
|
||||||
|
enableFooter: true,
|
||||||
|
footerLogoSvgData: '<svg>Sample Footer Logo</svg>',
|
||||||
|
footerCopyrightText: '© AB Software GmbH',
|
||||||
|
footerBackgroundColor: '#FFFFFF',
|
||||||
|
footerTextColor: '#000000',
|
||||||
|
footerDocsUrl: 'https://automatisch.io/docs',
|
||||||
|
footerTosUrl: 'https://automatisch.io/terms',
|
||||||
|
footerPrivacyPolicyUrl: 'https://automatisch.io/privacy',
|
||||||
|
footerImprintUrl: 'https://automatisch.io/imprint',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.body).toStrictEqual(expectedPayload);
|
expect(response.body).toStrictEqual(expectedPayload);
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
export async function up(knex) {
|
||||||
|
await knex.schema.table('config', (table) => {
|
||||||
|
table.boolean('enable_footer').defaultTo(false);
|
||||||
|
table.text('footer_logo_svg_data');
|
||||||
|
table.string('footer_copyright_text');
|
||||||
|
table.string('footer_background_color');
|
||||||
|
table.string('footer_text_color');
|
||||||
|
table.string('footer_docs_url');
|
||||||
|
table.string('footer_tos_url');
|
||||||
|
table.string('footer_privacy_policy_url');
|
||||||
|
table.string('footer_imprint_url');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex) {
|
||||||
|
await knex.schema.table('config', (table) => {
|
||||||
|
table.dropColumn('enable_footer');
|
||||||
|
table.dropColumn('footer_copyright_text');
|
||||||
|
table.dropColumn('footer_logo_svg_data');
|
||||||
|
table.dropColumn('footer_background_color');
|
||||||
|
table.dropColumn('footer_text_color');
|
||||||
|
table.dropColumn('footer_docs_url');
|
||||||
|
table.dropColumn('footer_tos_url');
|
||||||
|
table.dropColumn('footer_privacy_policy_url');
|
||||||
|
table.dropColumn('footer_imprint_url');
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -6,12 +6,63 @@ exports[`Config model > jsonSchema should have correct validations 1`] = `
|
|||||||
"createdAt": {
|
"createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
|
"enableFooter": {
|
||||||
|
"type": "boolean",
|
||||||
|
},
|
||||||
"enableTemplates": {
|
"enableTemplates": {
|
||||||
"type": [
|
"type": [
|
||||||
"boolean",
|
"boolean",
|
||||||
"null",
|
"null",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
"footerBackgroundColor": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"footerCopyrightText": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"footerDocsUrl": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"footerImprintUrl": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"footerLogoSvgData": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"footerPrivacyPolicyUrl": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"footerTextColor": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"footerTosUrl": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null",
|
||||||
|
],
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"format": "uuid",
|
"format": "uuid",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@@ -16,6 +16,15 @@ class Config extends Base {
|
|||||||
palettePrimaryMain: { type: ['string', 'null'] },
|
palettePrimaryMain: { type: ['string', 'null'] },
|
||||||
title: { type: ['string', 'null'] },
|
title: { type: ['string', 'null'] },
|
||||||
enableTemplates: { type: ['boolean', 'null'] },
|
enableTemplates: { type: ['boolean', 'null'] },
|
||||||
|
enableFooter: { type: 'boolean' },
|
||||||
|
footerLogoSvgData: { type: ['string', 'null'] },
|
||||||
|
footerCopyrightText: { type: ['string', 'null'] },
|
||||||
|
footerBackgroundColor: { type: ['string', 'null'] },
|
||||||
|
footerTextColor: { type: ['string', 'null'] },
|
||||||
|
footerDocsUrl: { type: ['string', 'null'] },
|
||||||
|
footerTosUrl: { type: ['string', 'null'] },
|
||||||
|
footerPrivacyPolicyUrl: { type: ['string', 'null'] },
|
||||||
|
footerImprintUrl: { type: ['string', 'null'] },
|
||||||
createdAt: { type: 'string' },
|
createdAt: { type: 'string' },
|
||||||
updatedAt: { type: 'string' },
|
updatedAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,6 +15,15 @@ const configSerializer = (config) => {
|
|||||||
palettePrimaryLight: config.palettePrimaryLight,
|
palettePrimaryLight: config.palettePrimaryLight,
|
||||||
installationCompleted: config.installationCompleted,
|
installationCompleted: config.installationCompleted,
|
||||||
title: config.title,
|
title: config.title,
|
||||||
|
enableFooter: config.enableFooter,
|
||||||
|
footerLogoSvgData: config.footerLogoSvgData,
|
||||||
|
footerCopyrightText: config.footerCopyrightText,
|
||||||
|
footerBackgroundColor: config.footerBackgroundColor,
|
||||||
|
footerTextColor: config.footerTextColor,
|
||||||
|
footerDocsUrl: config.footerDocsUrl,
|
||||||
|
footerTosUrl: config.footerTosUrl,
|
||||||
|
footerPrivacyPolicyUrl: config.footerPrivacyPolicyUrl,
|
||||||
|
footerImprintUrl: config.footerImprintUrl,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,15 @@ describe('configSerializer', () => {
|
|||||||
additionalDrawerLink: config.additionalDrawerLink,
|
additionalDrawerLink: config.additionalDrawerLink,
|
||||||
additionalDrawerLinkIcon: config.additionalDrawerLinkIcon,
|
additionalDrawerLinkIcon: config.additionalDrawerLinkIcon,
|
||||||
additionalDrawerLinkText: config.additionalDrawerLinkText,
|
additionalDrawerLinkText: config.additionalDrawerLinkText,
|
||||||
|
enableFooter: config.enableFooter,
|
||||||
|
footerBackgroundColor: config.footerBackgroundColor,
|
||||||
|
footerCopyrightText: config.footerCopyrightText,
|
||||||
|
footerDocsUrl: config.footerDocsUrl,
|
||||||
|
footerImprintUrl: config.footerImprintUrl,
|
||||||
|
footerLogoSvgData: config.footerLogoSvgData,
|
||||||
|
footerPrivacyPolicyUrl: config.footerPrivacyPolicyUrl,
|
||||||
|
footerTextColor: config.footerTextColor,
|
||||||
|
footerTosUrl: config.footerTosUrl,
|
||||||
createdAt: config.createdAt.getTime(),
|
createdAt: config.createdAt.getTime(),
|
||||||
updatedAt: config.updatedAt.getTime(),
|
updatedAt: config.updatedAt.getTime(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,6 +16,15 @@ const configMock = (config) => {
|
|||||||
installationCompleted: config.installationCompleted || false,
|
installationCompleted: config.installationCompleted || false,
|
||||||
title: config.title,
|
title: config.title,
|
||||||
enableTemplates: config.enableTemplates,
|
enableTemplates: config.enableTemplates,
|
||||||
|
enableFooter: config.enableFooter || false,
|
||||||
|
footerLogoSvgData: config.footerLogoSvgData,
|
||||||
|
footerCopyrightText: config.footerCopyrightText,
|
||||||
|
footerBackgroundColor: config.footerBackgroundColor,
|
||||||
|
footerTextColor: config.footerTextColor,
|
||||||
|
footerDocsUrl: config.footerDocsUrl,
|
||||||
|
footerTosUrl: config.footerTosUrl,
|
||||||
|
footerPrivacyPolicyUrl: config.footerPrivacyPolicyUrl,
|
||||||
|
footerImprintUrl: config.footerImprintUrl,
|
||||||
},
|
},
|
||||||
meta: {
|
meta: {
|
||||||
count: 1,
|
count: 1,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
crossorigin
|
crossorigin
|
||||||
type="font/ttf"
|
type="font/ttf"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
href="/fonts/Inter-Medium.ttf"
|
href="/fonts/Inter-Medium.ttf"
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
crossorigin
|
crossorigin
|
||||||
type="font/ttf"
|
type="font/ttf"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
href="/fonts/Inter-Bold.ttf"
|
href="/fonts/Inter-Bold.ttf"
|
||||||
@@ -39,6 +41,7 @@
|
|||||||
crossorigin
|
crossorigin
|
||||||
type="font/ttf"
|
type="font/ttf"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Inter';
|
font-family: 'Inter';
|
||||||
@@ -63,6 +66,10 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tsqd-parent-container .tsqd-open-btn-container {
|
||||||
|
transform: translateY(-40px);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ import Typography from '@mui/material/Typography';
|
|||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import useVersion from 'hooks/useVersion';
|
import useVersion from 'hooks/useVersion';
|
||||||
|
|
||||||
const Footer = () => {
|
const AdminSettingsLayoutFooter = () => {
|
||||||
const version = useVersion();
|
const version = useVersion();
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
typeof version?.version === 'string' && (
|
typeof version?.version === 'string' && (
|
||||||
<Box mt="auto" position="sticky" bottom={0}>
|
<Box mt="auto" position="sticky" bottom={0} zIndex={99}>
|
||||||
<Box bgcolor="common.white" mt={4}>
|
<Box bgcolor="common.white" mt={4}>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Typography
|
<Typography
|
||||||
@@ -32,4 +32,4 @@ const Footer = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Footer;
|
export default AdminSettingsLayoutFooter;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
export const LogoImage = styled('img')(() => ({
|
export const LogoImage = styled('img')(() => ({
|
||||||
maxWidth: 200,
|
maxWidth: 200,
|
||||||
maxHeight: 22,
|
maxHeight: 22,
|
||||||
|
|||||||
126
packages/web/src/components/Layout/Footer/index.jsx
Normal file
126
packages/web/src/components/Layout/Footer/index.jsx
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Divider from '@mui/material/Divider';
|
||||||
|
import Link from '@mui/material/Link';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import SvgIcon from '@mui/material/SvgIcon';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
|
||||||
|
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
||||||
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
import useVersion from 'hooks/useVersion';
|
||||||
|
|
||||||
|
const LogoImage = styled('img')(() => ({
|
||||||
|
maxWidth: 'auto',
|
||||||
|
height: 22,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const LayoutFooter = () => {
|
||||||
|
const { data: config, isPending: isConfigPending } = useAutomatischConfig();
|
||||||
|
const formatMessage = useFormatMessage();
|
||||||
|
|
||||||
|
const links = [
|
||||||
|
{
|
||||||
|
key: 'docs',
|
||||||
|
show: !!config.data.footerDocsUrl,
|
||||||
|
href: config.data.footerDocsUrl,
|
||||||
|
text: formatMessage('footer.docsLinkText'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'terms-of-services',
|
||||||
|
show: !!config.data.footerTosUrl,
|
||||||
|
href: config.data.footerTosUrl,
|
||||||
|
text: formatMessage('footer.tosLinkText'),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
key: 'privacy-policy',
|
||||||
|
show: !!config.data.footerPrivacyPolicyUrl,
|
||||||
|
href: config.data.footerPrivacyPolicyUrl,
|
||||||
|
text: formatMessage('footer.privacyPolicyLinkText'),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
key: 'imprint',
|
||||||
|
show: !!config.data.footerImprintUrl,
|
||||||
|
href: config.data.footerImprintUrl,
|
||||||
|
text: formatMessage('footer.imprintLinkText'),
|
||||||
|
},
|
||||||
|
,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (config.data.enableFooter !== true) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box mt="auto" position="sticky" bottom={0}>
|
||||||
|
<Box bgcolor="common.white" mt={4}>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: { xs: 'column', md: 'row' },
|
||||||
|
gap: 1,
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '10px 20px',
|
||||||
|
bgcolor: (theme) => theme.palette.footer.main,
|
||||||
|
color: (theme) => theme.palette.footer.text,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{config.data.footerLogoSvgData && (
|
||||||
|
<>
|
||||||
|
<LogoImage
|
||||||
|
data-test="footer-logo"
|
||||||
|
src={`data:image/svg+xml;utf8,${encodeURIComponent(config.data.footerLogoSvgData)}`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{config.data.footerCopyrightText && (
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
sx={{ flexGrow: 1, textAlign: 'center' }}
|
||||||
|
>
|
||||||
|
{config.data.footerCopyrightText}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: '15px',
|
||||||
|
flexWrap: { xs: 'wrap', sm: 'unset' },
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{links
|
||||||
|
.filter((link) => link.show)
|
||||||
|
.map((link) => (
|
||||||
|
<Link
|
||||||
|
key={link.key}
|
||||||
|
href={link.href}
|
||||||
|
color="inherit"
|
||||||
|
variant="body1"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
sx={{
|
||||||
|
textDecoration: 'none',
|
||||||
|
'&:hover': { textDecoration: 'underline' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{link.text}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LayoutFooter;
|
||||||
@@ -19,6 +19,8 @@ import AppBar from 'components/AppBar';
|
|||||||
import Drawer from 'components/Drawer';
|
import Drawer from 'components/Drawer';
|
||||||
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
||||||
|
|
||||||
|
import Footer from './Footer';
|
||||||
|
|
||||||
const additionalDrawerLinkIcons = {
|
const additionalDrawerLinkIcons = {
|
||||||
Security: SecurityIcon,
|
Security: SecurityIcon,
|
||||||
ArrowBackIosNew: ArrowBackIosNewIcon,
|
ArrowBackIosNew: ArrowBackIosNewIcon,
|
||||||
@@ -141,6 +143,7 @@ function PublicLayout({ children }) {
|
|||||||
<Stack flex={1}>
|
<Stack flex={1}>
|
||||||
<Toolbar />
|
<Toolbar />
|
||||||
{children}
|
{children}
|
||||||
|
<Footer />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -37,6 +37,18 @@ const customizeTheme = (theme, config) => {
|
|||||||
config.palettePrimaryDark,
|
config.palettePrimaryDark,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
overrideIfGiven(
|
||||||
|
shallowDefaultTheme,
|
||||||
|
'palette.footer.main',
|
||||||
|
config.footerBackgroundColor,
|
||||||
|
);
|
||||||
|
|
||||||
|
overrideIfGiven(
|
||||||
|
shallowDefaultTheme,
|
||||||
|
'palette.footer.text',
|
||||||
|
config.footerTextColor,
|
||||||
|
);
|
||||||
|
|
||||||
return shallowDefaultTheme;
|
return shallowDefaultTheme;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -273,12 +273,23 @@
|
|||||||
"permissionSettings.title": "Conditions",
|
"permissionSettings.title": "Conditions",
|
||||||
"appOAuthClientsDialog.title": "Choose your authentication client",
|
"appOAuthClientsDialog.title": "Choose your authentication client",
|
||||||
"userInterfacePage.title": "User Interface",
|
"userInterfacePage.title": "User Interface",
|
||||||
|
"userInterfacePage.generalTitle": "General",
|
||||||
|
"userInterfacePage.footerTitle": "Footer",
|
||||||
"userInterfacePage.successfullyUpdated": "User interface has been updated.",
|
"userInterfacePage.successfullyUpdated": "User interface has been updated.",
|
||||||
"userInterfacePage.titleFieldLabel": "Title",
|
"userInterfacePage.titleFieldLabel": "Title",
|
||||||
"userInterfacePage.primaryMainColorFieldLabel": "Primary main color",
|
"userInterfacePage.primaryMainColorFieldLabel": "Primary main color",
|
||||||
"userInterfacePage.primaryDarkColorFieldLabel": "Primary dark color",
|
"userInterfacePage.primaryDarkColorFieldLabel": "Primary dark color",
|
||||||
"userInterfacePage.primaryLightColorFieldLabel": "Primary light color",
|
"userInterfacePage.primaryLightColorFieldLabel": "Primary light color",
|
||||||
"userInterfacePage.svgDataFieldLabel": "Logo SVG code",
|
"userInterfacePage.svgDataFieldLabel": "Logo SVG code",
|
||||||
|
"userInterfacePage.footerLogoSvgDataFieldLabel": "Footer logo SVG code",
|
||||||
|
"userInterfacePage.footerCopyrightTextFieldLabel": "Copyright text",
|
||||||
|
"userInterfacePage.enableFooterLabel": "Enable footer",
|
||||||
|
"userInterfacePage.footerTextColorLabel": "Text color",
|
||||||
|
"userInterfacePage.footerBackgroundColorLabel": "Background color",
|
||||||
|
"userInterfacePage.footerDocsUrlLabel": "Documentation link",
|
||||||
|
"userInterfacePage.footerTosUrlLabel": "Terms of services link",
|
||||||
|
"userInterfacePage.footerPrivacyPolicyUrlLabel": "Privacy policy link",
|
||||||
|
"userInterfacePage.footerImprintUrlLabel": "Imprint link",
|
||||||
"userInterfacePage.submit": "Update",
|
"userInterfacePage.submit": "Update",
|
||||||
"authenticationPage.title": "Single Sign-On with SAML",
|
"authenticationPage.title": "Single Sign-On with SAML",
|
||||||
"authenticationForm.active": "Active",
|
"authenticationForm.active": "Active",
|
||||||
@@ -355,5 +366,9 @@
|
|||||||
"flowFolderChangeDialog.confirm": "Move",
|
"flowFolderChangeDialog.confirm": "Move",
|
||||||
"flowFolderChangeDialog.uncategorizedFolder": "Uncategorized",
|
"flowFolderChangeDialog.uncategorizedFolder": "Uncategorized",
|
||||||
"flowFolderChangeDialog.successfullyUpdatedFolder": "The flow has been successfully moved to the new folder!",
|
"flowFolderChangeDialog.successfullyUpdatedFolder": "The flow has been successfully moved to the new folder!",
|
||||||
"flowFolder.uncategorized": "Uncategorized"
|
"flowFolder.uncategorized": "Uncategorized",
|
||||||
|
"footer.docsLinkText": "Documentation",
|
||||||
|
"footer.tosLinkText": "Terms of Service",
|
||||||
|
"footer.privacyPolicyLinkText": "Privacy",
|
||||||
|
"footer.imprintLinkText": "Imprint"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
import Skeleton from '@mui/material/Skeleton';
|
import Skeleton from '@mui/material/Skeleton';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import mergeWith from 'lodash/mergeWith';
|
import mergeWith from 'lodash/mergeWith';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import Switch from 'components/Switch';
|
||||||
import ColorInput from 'components/ColorInput';
|
import ColorInput from 'components/ColorInput';
|
||||||
import Container from 'components/Container';
|
import Container from 'components/Container';
|
||||||
import Form from 'components/Form';
|
import Form from 'components/Form';
|
||||||
@@ -29,6 +31,16 @@ const defaultValues = {
|
|||||||
palettePrimaryMain: primaryMainColor,
|
palettePrimaryMain: primaryMainColor,
|
||||||
palettePrimaryDark: primaryDarkColor,
|
palettePrimaryDark: primaryDarkColor,
|
||||||
palettePrimaryLight: primaryLightColor,
|
palettePrimaryLight: primaryLightColor,
|
||||||
|
logoSvgData: '',
|
||||||
|
enableFooter: false,
|
||||||
|
footerLogoSvgData: '',
|
||||||
|
footerCopyrightText: '',
|
||||||
|
footerBackgroundColor: '#FFFFFF',
|
||||||
|
footerTextColor: '#000000',
|
||||||
|
footerDocsUrl: '',
|
||||||
|
footerTosUrl: '',
|
||||||
|
footerPrivacyPolicyUrl: '',
|
||||||
|
footerImprintUrl: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const mergeIfGiven = (oldValue, newValue) => {
|
const mergeIfGiven = (oldValue, newValue) => {
|
||||||
@@ -51,13 +63,24 @@ export default function UserInterface() {
|
|||||||
const handleUserInterfaceUpdate = async (uiData) => {
|
const handleUserInterfaceUpdate = async (uiData) => {
|
||||||
try {
|
try {
|
||||||
const input = {
|
const input = {
|
||||||
title: uiData.title,
|
enableFooter: uiData.enableFooter,
|
||||||
palettePrimaryMain: getPrimaryMainColor(uiData.palettePrimaryMain),
|
footerBackgroundColor: uiData.footerBackgroundColor,
|
||||||
|
footerCopyrightText: uiData.footerCopyrightText,
|
||||||
|
footerDocsUrl: uiData.footerDocsUrl,
|
||||||
|
footerImprintUrl: uiData.footerImprintUrl,
|
||||||
|
footerLogoSvgData: uiData.footerLogoSvgData,
|
||||||
|
footerPrivacyPolicyUrl: uiData.footerPrivacyPolicyUrl,
|
||||||
|
footerTextColor: uiData.footerTextColor,
|
||||||
|
footerTosUrl: uiData.footerTosUrl,
|
||||||
|
logoSvgData: uiData.logoSvgData,
|
||||||
palettePrimaryDark: getPrimaryDarkColor(uiData.palettePrimaryDark),
|
palettePrimaryDark: getPrimaryDarkColor(uiData.palettePrimaryDark),
|
||||||
palettePrimaryLight: getPrimaryLightColor(uiData.palettePrimaryLight),
|
palettePrimaryLight: getPrimaryLightColor(uiData.palettePrimaryLight),
|
||||||
logoSvgData: uiData.logoSvgData,
|
palettePrimaryMain: getPrimaryMainColor(uiData.palettePrimaryMain),
|
||||||
|
title: uiData.title,
|
||||||
};
|
};
|
||||||
|
|
||||||
await updateConfig(input);
|
await updateConfig(input);
|
||||||
|
|
||||||
enqueueSnackbar(formatMessage('userInterfacePage.successfullyUpdated'), {
|
enqueueSnackbar(formatMessage('userInterfacePage.successfullyUpdated'), {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
SnackbarProps: {
|
SnackbarProps: {
|
||||||
@@ -92,6 +115,10 @@ export default function UserInterface() {
|
|||||||
defaultValues={configWithDefaults}
|
defaultValues={configWithDefaults}
|
||||||
>
|
>
|
||||||
<Stack direction="column" gap={2}>
|
<Stack direction="column" gap={2}>
|
||||||
|
<Typography variant="h4" gutterBottom={true}>
|
||||||
|
{formatMessage('userInterfacePage.generalTitle')}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
name="title"
|
name="title"
|
||||||
label={formatMessage('userInterfacePage.titleFieldLabel')}
|
label={formatMessage('userInterfacePage.titleFieldLabel')}
|
||||||
@@ -133,6 +160,89 @@ export default function UserInterface() {
|
|||||||
data-test="logo-svg-data-text-field"
|
data-test="logo-svg-data-text-field"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Typography variant="h4" gutterBottom={true} mt={3}>
|
||||||
|
{formatMessage('userInterfacePage.footerTitle')}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
name="enableFooter"
|
||||||
|
label={formatMessage('userInterfacePage.enableFooterLabel')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
name="footerLogoSvgData"
|
||||||
|
label={formatMessage(
|
||||||
|
'userInterfacePage.footerLogoSvgDataFieldLabel',
|
||||||
|
)}
|
||||||
|
multiline
|
||||||
|
fullWidth
|
||||||
|
data-test="footer-logo-svg-data-text-field"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
name="footerCopyrightText"
|
||||||
|
label={formatMessage(
|
||||||
|
'userInterfacePage.footerCopyrightTextFieldLabel',
|
||||||
|
)}
|
||||||
|
multiline
|
||||||
|
fullWidth
|
||||||
|
data-test="footer-copyright-text-field"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ColorInput
|
||||||
|
name="footerBackgroundColor"
|
||||||
|
label={formatMessage(
|
||||||
|
'userInterfacePage.footerBackgroundColorLabel',
|
||||||
|
)}
|
||||||
|
fullWidth
|
||||||
|
data-test="footer-background-color-input"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ColorInput
|
||||||
|
name="footerTextColor"
|
||||||
|
label={formatMessage(
|
||||||
|
'userInterfacePage.footerTextColorLabel',
|
||||||
|
)}
|
||||||
|
fullWidth
|
||||||
|
data-test="footer-text-color-input"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
name="footerDocsUrl"
|
||||||
|
label={formatMessage('userInterfacePage.footerDocsUrlLabel')}
|
||||||
|
multiline
|
||||||
|
fullWidth
|
||||||
|
data-test="logo-docs-text-field"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
name="footerTosUrl"
|
||||||
|
label={formatMessage('userInterfacePage.footerTosUrlLabel')}
|
||||||
|
multiline
|
||||||
|
fullWidth
|
||||||
|
data-test="logo-tos-url-text-field"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
name="footerPrivacyPolicyUrl"
|
||||||
|
label={formatMessage(
|
||||||
|
'userInterfacePage.footerPrivacyPolicyUrlLabel',
|
||||||
|
)}
|
||||||
|
multiline
|
||||||
|
fullWidth
|
||||||
|
data-test="logo-privacy-policy-url-text-field"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
name="footerImprintUrl"
|
||||||
|
label={formatMessage(
|
||||||
|
'userInterfacePage.footerImprintUrlLabel',
|
||||||
|
)}
|
||||||
|
multiline
|
||||||
|
fullWidth
|
||||||
|
data-test="logo-imprint-url-text-field"
|
||||||
|
/>
|
||||||
|
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { deepmerge } from '@mui/utils';
|
import { deepmerge } from '@mui/utils';
|
||||||
import { createTheme, alpha } from '@mui/material/styles';
|
import { createTheme, alpha } from '@mui/material/styles';
|
||||||
import { cardActionAreaClasses } from '@mui/material/CardActionArea';
|
import { cardActionAreaClasses } from '@mui/material/CardActionArea';
|
||||||
|
|
||||||
const referenceTheme = createTheme();
|
const referenceTheme = createTheme();
|
||||||
|
|
||||||
export const primaryMainColor = '#0059F7';
|
export const primaryMainColor = '#0059F7';
|
||||||
export const primaryLightColor = '#4286FF';
|
export const primaryLightColor = '#4286FF';
|
||||||
export const primaryDarkColor = '#001F52';
|
export const primaryDarkColor = '#001F52';
|
||||||
|
|
||||||
export const defaultTheme = createTheme({
|
export const defaultTheme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
primary: {
|
primary: {
|
||||||
@@ -57,6 +60,10 @@ export const defaultTheme = createTheme({
|
|||||||
paper: '#fff',
|
paper: '#fff',
|
||||||
default: '#FAFAFA',
|
default: '#FAFAFA',
|
||||||
},
|
},
|
||||||
|
footer: {
|
||||||
|
main: '#FFFFFF',
|
||||||
|
text: '#001F52',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
shape: {
|
shape: {
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
|
|||||||
Reference in New Issue
Block a user