diff --git a/packages/backend/src/controllers/api/v1/admin/config/update.ee.js b/packages/backend/src/controllers/api/v1/admin/config/update.ee.js index 50c76beb..263d9f6b 100644 --- a/packages/backend/src/controllers/api/v1/admin/config/update.ee.js +++ b/packages/backend/src/controllers/api/v1/admin/config/update.ee.js @@ -11,6 +11,15 @@ export default async (request, response) => { const configParams = (request) => { const { + enableFooter, + footerBackgroundColor, + footerCopyrightText, + footerDocsUrl, + footerImprintUrl, + footerLogoSvgData, + footerPrivacyPolicyUrl, + footerTextColor, + footerTosUrl, logoSvgData, palettePrimaryDark, palettePrimaryLight, @@ -19,6 +28,15 @@ const configParams = (request) => { } = request.body; return { + enableFooter, + footerBackgroundColor, + footerCopyrightText, + footerDocsUrl, + footerImprintUrl, + footerLogoSvgData, + footerPrivacyPolicyUrl, + footerTextColor, + footerTosUrl, logoSvgData, palettePrimaryDark, palettePrimaryLight, diff --git a/packages/backend/src/controllers/api/v1/admin/config/update.ee.test.js b/packages/backend/src/controllers/api/v1/admin/config/update.ee.test.js index 5984cdbb..7f81e799 100644 --- a/packages/backend/src/controllers/api/v1/admin/config/update.ee.test.js +++ b/packages/backend/src/controllers/api/v1/admin/config/update.ee.test.js @@ -25,6 +25,18 @@ describe('PATCH /api/v1/admin/config', () => { const palettePrimaryMain = '#00adef'; const palettePrimaryDark = '#222222'; 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 = + 'Sample Footer Logo'; + const logoSvgData = 'A'; @@ -34,6 +46,15 @@ describe('PATCH /api/v1/admin/config', () => { palettePrimaryDark: palettePrimaryDark, palettePrimaryLight: palettePrimaryLight, logoSvgData: logoSvgData, + enableFooter, + footerCopyrightText, + footerBackgroundColor, + footerTextColor, + footerDocsUrl, + footerTosUrl, + footerPrivacyPolicyUrl, + footerImprintUrl, + footerLogoSvgData, }; await updateConfig(appConfig); diff --git a/packages/backend/src/controllers/api/v1/automatisch/config.ee.test.js b/packages/backend/src/controllers/api/v1/automatisch/config.ee.test.js index 01ca3365..d3e870d7 100644 --- a/packages/backend/src/controllers/api/v1/automatisch/config.ee.test.js +++ b/packages/backend/src/controllers/api/v1/automatisch/config.ee.test.js @@ -28,6 +28,15 @@ describe('GET /api/v1/automatisch/config', () => { palettePrimaryMain: '#0059F7', title: 'Sample Title', enableTemplates: true, + enableFooter: true, + footerLogoSvgData: 'Sample Footer Logo', + 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) @@ -41,6 +50,16 @@ describe('GET /api/v1/automatisch/config', () => { additionalDrawerLink: 'link', additionalDrawerLinkIcon: 'icon', additionalDrawerLinkText: 'text', + enableTemplates: true, + enableFooter: true, + footerLogoSvgData: 'Sample Footer Logo', + 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); diff --git a/packages/backend/src/db/migrations/20250304095143_add_footer_columns_in_config.js b/packages/backend/src/db/migrations/20250304095143_add_footer_columns_in_config.js new file mode 100644 index 00000000..bc9ae626 --- /dev/null +++ b/packages/backend/src/db/migrations/20250304095143_add_footer_columns_in_config.js @@ -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'); + }); +} diff --git a/packages/backend/src/models/__snapshots__/config.test.js.snap b/packages/backend/src/models/__snapshots__/config.test.js.snap index b3650d4a..9576bb0d 100644 --- a/packages/backend/src/models/__snapshots__/config.test.js.snap +++ b/packages/backend/src/models/__snapshots__/config.test.js.snap @@ -6,12 +6,63 @@ exports[`Config model > jsonSchema should have correct validations 1`] = ` "createdAt": { "type": "string", }, + "enableFooter": { + "type": "boolean", + }, "enableTemplates": { "type": [ "boolean", "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": { "format": "uuid", "type": "string", diff --git a/packages/backend/src/models/config.js b/packages/backend/src/models/config.js index 7fda0860..3cab32a0 100644 --- a/packages/backend/src/models/config.js +++ b/packages/backend/src/models/config.js @@ -16,6 +16,15 @@ class Config extends Base { palettePrimaryMain: { type: ['string', 'null'] }, title: { type: ['string', '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' }, updatedAt: { type: 'string' }, }, diff --git a/packages/backend/src/serializers/config.js b/packages/backend/src/serializers/config.js index 44308938..774d0ef7 100644 --- a/packages/backend/src/serializers/config.js +++ b/packages/backend/src/serializers/config.js @@ -15,6 +15,15 @@ const configSerializer = (config) => { palettePrimaryLight: config.palettePrimaryLight, installationCompleted: config.installationCompleted, 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, }; }; diff --git a/packages/backend/src/serializers/config.test.js b/packages/backend/src/serializers/config.test.js index 025f286a..3a58406e 100644 --- a/packages/backend/src/serializers/config.test.js +++ b/packages/backend/src/serializers/config.test.js @@ -24,6 +24,15 @@ describe('configSerializer', () => { additionalDrawerLink: config.additionalDrawerLink, additionalDrawerLinkIcon: config.additionalDrawerLinkIcon, 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(), updatedAt: config.updatedAt.getTime(), }; diff --git a/packages/backend/test/mocks/rest/api/v1/automatisch/config.js b/packages/backend/test/mocks/rest/api/v1/automatisch/config.js index eae9202c..cb0a47a6 100644 --- a/packages/backend/test/mocks/rest/api/v1/automatisch/config.js +++ b/packages/backend/test/mocks/rest/api/v1/automatisch/config.js @@ -16,6 +16,15 @@ const configMock = (config) => { installationCompleted: config.installationCompleted || false, title: config.title, 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: { count: 1, diff --git a/packages/web/public/index.html b/packages/web/public/index.html index 26c342fd..3c6345fb 100644 --- a/packages/web/public/index.html +++ b/packages/web/public/index.html @@ -1,4 +1,4 @@ - + @@ -25,6 +25,7 @@ crossorigin type="font/ttf" /> + + + diff --git a/packages/web/src/components/AdminSettingsLayout/Footer/index.jsx b/packages/web/src/components/AdminSettingsLayout/Footer/index.jsx index 8bfe8bc7..4022ba53 100644 --- a/packages/web/src/components/AdminSettingsLayout/Footer/index.jsx +++ b/packages/web/src/components/AdminSettingsLayout/Footer/index.jsx @@ -5,13 +5,13 @@ import Typography from '@mui/material/Typography'; import useFormatMessage from 'hooks/useFormatMessage'; import useVersion from 'hooks/useVersion'; -const Footer = () => { +const AdminSettingsLayoutFooter = () => { const version = useVersion(); const formatMessage = useFormatMessage(); return ( typeof version?.version === 'string' && ( - + { ); }; -export default Footer; +export default AdminSettingsLayoutFooter; diff --git a/packages/web/src/components/CustomLogo/style.ee.js b/packages/web/src/components/CustomLogo/style.ee.js index e38a9691..fd2d15be 100644 --- a/packages/web/src/components/CustomLogo/style.ee.js +++ b/packages/web/src/components/CustomLogo/style.ee.js @@ -1,4 +1,5 @@ import styled from '@emotion/styled'; + export const LogoImage = styled('img')(() => ({ maxWidth: 200, maxHeight: 22, diff --git a/packages/web/src/components/Layout/Footer/index.jsx b/packages/web/src/components/Layout/Footer/index.jsx new file mode 100644 index 00000000..759f199c --- /dev/null +++ b/packages/web/src/components/Layout/Footer/index.jsx @@ -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 ( + + + + + theme.palette.footer.main, + color: (theme) => theme.palette.footer.text, + }} + > +
+ {config.data.footerLogoSvgData && ( + <> + + + )} +
+ +
+ {config.data.footerCopyrightText && ( + + {config.data.footerCopyrightText} + + )} +
+ + + {links + .filter((link) => link.show) + .map((link) => ( + + {link.text} + + ))} + +
+
+
+ ); +}; + +export default LayoutFooter; diff --git a/packages/web/src/components/Layout/index.jsx b/packages/web/src/components/Layout/index.jsx index 3e96442b..e9542f9e 100644 --- a/packages/web/src/components/Layout/index.jsx +++ b/packages/web/src/components/Layout/index.jsx @@ -19,6 +19,8 @@ import AppBar from 'components/AppBar'; import Drawer from 'components/Drawer'; import useAutomatischConfig from 'hooks/useAutomatischConfig'; +import Footer from './Footer'; + const additionalDrawerLinkIcons = { Security: SecurityIcon, ArrowBackIosNew: ArrowBackIosNewIcon, @@ -141,6 +143,7 @@ function PublicLayout({ children }) { {children} +