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 9c3e7801..50c76beb 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 @@ -1,23 +1,28 @@ -import pick from 'lodash/pick.js'; import { renderObject } from '../../../../../helpers/renderer.js'; import Config from '../../../../../models/config.js'; export default async (request, response) => { - const config = configParams(request); - - await Config.batchUpdate(config); + const config = await Config.query().updateFirstOrInsert( + configParams(request) + ); renderObject(response, config); }; const configParams = (request) => { - const updatableConfigurationKeys = [ - 'logo.svgData', - 'palette.primary.dark', - 'palette.primary.light', - 'palette.primary.main', - 'title', - ]; + const { + logoSvgData, + palettePrimaryDark, + palettePrimaryLight, + palettePrimaryMain, + title, + } = request.body; - return pick(request.body, updatableConfigurationKeys); + return { + logoSvgData, + palettePrimaryDark, + palettePrimaryLight, + palettePrimaryMain, + title, + }; }; 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 465978de..bf4b103c 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 @@ -5,7 +5,7 @@ import app from '../../../../../app.js'; import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js'; import { createUser } from '../../../../../../test/factories/user.js'; import { createRole } from '../../../../../../test/factories/role.js'; -import { createBulkConfig } from '../../../../../../test/factories/config.js'; +import { updateConfig } from '../../../../../../test/factories/config.js'; import * as license from '../../../../../helpers/license.ee.js'; describe('PATCH /api/v1/admin/config', () => { @@ -30,13 +30,13 @@ describe('PATCH /api/v1/admin/config', () => { const appConfig = { title, - 'palette.primary.main': palettePrimaryMain, - 'palette.primary.dark': palettePrimaryDark, - 'palette.primary.light': palettePrimaryLight, - 'logo.svgData': logoSvgData, + palettePrimaryMain: palettePrimaryMain, + palettePrimaryDark: palettePrimaryDark, + palettePrimaryLight: palettePrimaryLight, + logoSvgData: logoSvgData, }; - await createBulkConfig(appConfig); + await updateConfig(appConfig); const newTitle = 'Updated title'; @@ -51,7 +51,7 @@ describe('PATCH /api/v1/admin/config', () => { .expect(200); expect(response.body.data.title).toEqual(newTitle); - expect(response.body.meta.type).toEqual('Object'); + expect(response.body.meta.type).toEqual('Config'); }); it('should return created config for unexisting config', async () => { @@ -68,7 +68,7 @@ describe('PATCH /api/v1/admin/config', () => { .expect(200); expect(response.body.data.title).toEqual(newTitle); - expect(response.body.meta.type).toEqual('Object'); + expect(response.body.meta.type).toEqual('Config'); }); it('should return null for deleted config entry', async () => { @@ -83,6 +83,6 @@ describe('PATCH /api/v1/admin/config', () => { .expect(200); expect(response.body.data.title).toBeNull(); - expect(response.body.meta.type).toEqual('Object'); + expect(response.body.meta.type).toEqual('Config'); }); }); diff --git a/packages/backend/src/controllers/api/v1/automatisch/config.ee.js b/packages/backend/src/controllers/api/v1/automatisch/config.ee.js index e8538cde..65f970e6 100644 --- a/packages/backend/src/controllers/api/v1/automatisch/config.ee.js +++ b/packages/backend/src/controllers/api/v1/automatisch/config.ee.js @@ -1,25 +1,8 @@ -import appConfig from '../../../../config/app.js'; import Config from '../../../../models/config.js'; import { renderObject } from '../../../../helpers/renderer.js'; export default async (request, response) => { - const defaultConfig = { - disableNotificationsPage: appConfig.disableNotificationsPage, - disableFavicon: appConfig.disableFavicon, - additionalDrawerLink: appConfig.additionalDrawerLink, - additionalDrawerLinkIcon: appConfig.additionalDrawerLinkIcon, - additionalDrawerLinkText: appConfig.additionalDrawerLinkText, - }; - - let config = await Config.query().orderBy('key', 'asc'); - - config = config.reduce((computedConfig, configEntry) => { - const { key, value } = configEntry; - - computedConfig[key] = value?.data; - - return computedConfig; - }, defaultConfig); + const config = await Config.get(); renderObject(response, config); }; 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 28b8bde3..effb2e9b 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 @@ -1,66 +1,47 @@ import { vi, expect, describe, it } from 'vitest'; import request from 'supertest'; -import { createConfig } from '../../../../../test/factories/config.js'; +import { updateConfig } from '../../../../../test/factories/config.js'; import app from '../../../../app.js'; import configMock from '../../../../../test/mocks/rest/api/v1/automatisch/config.js'; import * as license from '../../../../helpers/license.ee.js'; import appConfig from '../../../../config/app.js'; describe('GET /api/v1/automatisch/config', () => { - it('should return Automatisch config', async () => { + it('should return Automatisch config along with static config', async () => { vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); + vi.spyOn(appConfig, 'disableNotificationsPage', 'get').mockReturnValue( + true + ); + vi.spyOn(appConfig, 'disableFavicon', 'get').mockReturnValue(true); + vi.spyOn(appConfig, 'additionalDrawerLink', 'get').mockReturnValue('link'); + vi.spyOn(appConfig, 'additionalDrawerLinkIcon', 'get').mockReturnValue( + 'icon' + ); + vi.spyOn(appConfig, 'additionalDrawerLinkText', 'get').mockReturnValue( + 'text' + ); - const logoConfig = await createConfig({ - key: 'logo.svgData', - value: { data: 'Sample' }, - }); - - const primaryDarkConfig = await createConfig({ - key: 'palette.primary.dark', - value: { data: '#001F52' }, - }); - - const primaryLightConfig = await createConfig({ - key: 'palette.primary.light', - value: { data: '#4286FF' }, - }); - - const primaryMainConfig = await createConfig({ - key: 'palette.primary.main', - value: { data: '#0059F7' }, - }); - - const titleConfig = await createConfig({ - key: 'title', - value: { data: 'Sample Title' }, + const config = await updateConfig({ + logoSvgData: 'Sample', + palettePrimaryDark: '#001f52', + palettePrimaryLight: '#4286FF', + palettePrimaryMain: '#0059F7', + title: 'Sample Title', }); const response = await request(app) .get('/api/v1/automatisch/config') .expect(200); - const expectedPayload = configMock( - logoConfig, - primaryDarkConfig, - primaryLightConfig, - primaryMainConfig, - titleConfig - ); + const expectedPayload = configMock({ + ...config, + disableNotificationsPage: true, + disableFavicon: true, + additionalDrawerLink: 'link', + additionalDrawerLinkIcon: 'icon', + additionalDrawerLinkText: 'text', + }); - expect(response.body).toEqual(expectedPayload); - }); - - it('should return additional environment variables', async () => { - vi.spyOn(appConfig, 'disableNotificationsPage', 'get').mockReturnValue(true); - vi.spyOn(appConfig, 'disableFavicon', 'get').mockReturnValue(true); - vi.spyOn(appConfig, 'additionalDrawerLink', 'get').mockReturnValue('link'); - vi.spyOn(appConfig, 'additionalDrawerLinkIcon', 'get').mockReturnValue('icon'); - vi.spyOn(appConfig, 'additionalDrawerLinkText', 'get').mockReturnValue('text'); - - expect(appConfig.disableNotificationsPage).toEqual(true); - expect(appConfig.disableFavicon).toEqual(true); - expect(appConfig.additionalDrawerLink).toEqual('link'); - expect(appConfig.additionalDrawerLinkIcon).toEqual('icon'); - expect(appConfig.additionalDrawerLinkText).toEqual('text'); + expect(response.body).toStrictEqual(expectedPayload); }); }); diff --git a/packages/backend/src/controllers/api/v1/installation/users/create-user.test.js b/packages/backend/src/controllers/api/v1/installation/users/create-user.test.js index 9c4d9bc6..a94f4c61 100644 --- a/packages/backend/src/controllers/api/v1/installation/users/create-user.test.js +++ b/packages/backend/src/controllers/api/v1/installation/users/create-user.test.js @@ -5,7 +5,7 @@ import Config from '../../../../../models/config.js'; import User from '../../../../../models/user.js'; import { createRole } from '../../../../../../test/factories/role'; import { createUser } from '../../../../../../test/factories/user'; -import { createInstallationCompletedConfig } from '../../../../../../test/factories/config'; +import { markInstallationCompleted } from '../../../../../../test/factories/config'; describe('POST /api/v1/installation/users', () => { let adminRole; @@ -59,7 +59,7 @@ describe('POST /api/v1/installation/users', () => { describe('for completed installations', () => { beforeEach(async () => { - await createInstallationCompletedConfig(); + await markInstallationCompleted(); }); it('should respond with HTTP 403 when installation completed', async () => { diff --git a/packages/backend/src/db/migrations/20240919100138_make_config_single_record.js b/packages/backend/src/db/migrations/20240919100138_make_config_single_record.js new file mode 100644 index 00000000..552d9499 --- /dev/null +++ b/packages/backend/src/db/migrations/20240919100138_make_config_single_record.js @@ -0,0 +1,105 @@ +export async function up(knex) { + await knex.schema.alterTable('config', (table) => { + table.dropUnique('key'); + + table.string('key').nullable().alter(); + table.boolean('installation_completed').defaultTo(false); + table.text('logo_svg_data'); + table.text('palette_primary_dark'); + table.text('palette_primary_light'); + table.text('palette_primary_main'); + table.string('title'); + }); + + const config = await knex('config').select('key', 'value'); + + const newConfigData = { + logo_svg_data: getValueForKey(config, 'logo.svgData'), + palette_primary_dark: getValueForKey(config, 'palette.primary.dark'), + palette_primary_light: getValueForKey(config, 'palette.primary.light'), + palette_primary_main: getValueForKey(config, 'palette.primary.main'), + title: getValueForKey(config, 'title'), + installation_completed: getValueForKey(config, 'installation.completed'), + }; + + const [configEntry] = await knex('config') + .insert(newConfigData) + .select('id') + .returning('id'); + + await knex('config').where('id', '!=', configEntry.id).delete(); + + await knex.schema.alterTable('config', (table) => { + table.dropColumn('key'); + table.dropColumn('value'); + }); +} + +export async function down(knex) { + await knex.schema.alterTable('config', (table) => { + table.string('key'); + table.jsonb('value').notNullable().defaultTo({}); + }); + + const configRow = await knex('config').first(); + + const config = [ + { + key: 'logo.svgData', + value: { + data: configRow.logo_svg_data, + }, + }, + { + key: 'palette.primary.dark', + value: { + data: configRow.palette_primary_dark, + }, + }, + { + key: 'palette.primary.light', + value: { + data: configRow.palette_primary_light, + }, + }, + { + key: 'palette.primary.main', + value: { + data: configRow.palette_primary_main, + }, + }, + { + key: 'title', + value: { + data: configRow.title, + }, + }, + { + key: 'installation.completed', + value: { + data: configRow.installation_completed, + }, + }, + ]; + + await knex('config').insert(config).returning('id'); + + await knex('config').where('id', '=', configRow.id).delete(); + + await knex.schema.alterTable('config', (table) => { + table.dropColumn('installation_completed'); + table.dropColumn('logo_svg_data'); + table.dropColumn('palette_primary_dark'); + table.dropColumn('palette_primary_light'); + table.dropColumn('palette_primary_main'); + table.dropColumn('title'); + + table.string('key').unique().notNullable().alter(); + }); +} + +function getValueForKey(rows, key) { + const row = rows.find((row) => row.key === key); + + return row?.value?.data || null; +} diff --git a/packages/backend/src/models/config.js b/packages/backend/src/models/config.js index 71ee614a..f60e51bb 100644 --- a/packages/backend/src/models/config.js +++ b/packages/backend/src/models/config.js @@ -1,3 +1,4 @@ +import appConfig from '../config/app.js'; import Base from './base.js'; class Config extends Base { @@ -5,68 +6,79 @@ class Config extends Base { static jsonSchema = { type: 'object', - required: ['key', 'value'], properties: { id: { type: 'string', format: 'uuid' }, - key: { type: 'string', minLength: 1 }, - value: { type: 'object' }, + installationCompleted: { type: 'boolean' }, + logoSvgData: { type: ['string', 'null'] }, + palettePrimaryDark: { type: ['string', 'null'] }, + palettePrimaryLight: { type: ['string', 'null'] }, + palettePrimaryMain: { type: ['string', 'null'] }, + title: { type: ['string', 'null'] }, + createdAt: { type: 'string' }, + updatedAt: { type: 'string' }, }, }; + static get virtualAttributes() { + return [ + 'disableNotificationsPage', + 'disableFavicon', + 'additionalDrawerLink', + 'additionalDrawerLinkIcon', + 'additionalDrawerLinkText', + ]; + } + + get disableNotificationsPage() { + return appConfig.disableNotificationsPage; + } + + get disableFavicon() { + return appConfig.disableFavicon; + } + + get additionalDrawerLink() { + return appConfig.additionalDrawerLink; + } + + get additionalDrawerLinkIcon() { + return appConfig.additionalDrawerLinkIcon; + } + + get additionalDrawerLinkText() { + return appConfig.additionalDrawerLinkText; + } + + static async get() { + const existingConfig = await this.query().limit(1).first(); + + if (!existingConfig) { + return await this.query().insertAndFetch({}); + } + + return existingConfig; + } + + static async update(config) { + const configEntry = await this.get(); + + return await configEntry.$query().patchAndFetch(config); + } + static async isInstallationCompleted() { - const installationCompletedEntry = await this.query() - .where({ - key: 'installation.completed', - }) - .first(); + const config = await this.get(); - const installationCompleted = - installationCompletedEntry?.value?.data === true; - - return installationCompleted; + return config.installationCompleted; } static async markInstallationCompleted() { - return await this.query().insert({ - key: 'installation.completed', - value: { - data: true, - }, + const config = await this.get(); + + return await config.$query().patchAndFetch({ + installationCompleted: true, }); } - - static async batchUpdate(config) { - const configKeys = Object.keys(config); - const updates = []; - - for (const key of configKeys) { - const newValue = config[key]; - - if (newValue) { - const entryUpdate = Config.query() - .insert({ - key, - value: { - data: newValue, - }, - }) - .onConflict('key') - .merge({ - value: { - data: newValue, - }, - }); - - updates.push(entryUpdate); - } else { - const entryUpdate = Config.query().findOne({ key }).delete(); - updates.push(entryUpdate); - } - } - - return await Promise.all(updates); - } } export default Config; diff --git a/packages/backend/src/models/query-builder.js b/packages/backend/src/models/query-builder.js index d3e0a89e..7dd62c63 100644 --- a/packages/backend/src/models/query-builder.js +++ b/packages/backend/src/models/query-builder.js @@ -53,6 +53,18 @@ class ExtendedQueryBuilder extends Model.QueryBuilder { [DELETED_COLUMN_NAME]: null, }); } + + async updateFirstOrInsert(data = {}) { + let firstRow = await this.first(); + + if (firstRow) { + return firstRow.$query().patchAndFetch(data); + } + + const newInstance = this.insertAndFetch(data); + + return newInstance; + } } export default ExtendedQueryBuilder; diff --git a/packages/backend/src/serializers/config.js b/packages/backend/src/serializers/config.js new file mode 100644 index 00000000..6625a1c9 --- /dev/null +++ b/packages/backend/src/serializers/config.js @@ -0,0 +1,20 @@ +const configSerializer = (config) => { + return { + id: config.id, + updatedAt: config.updatedAt.getTime(), + createdAt: config.createdAt.getTime(), + disableFavicon: config.disableFavicon, + disableNotificationsPage: config.disableNotificationsPage, + additionalDrawerLink: config.additionalDrawerLink, + additionalDrawerLinkIcon: config.additionalDrawerLinkIcon, + additionalDrawerLinkText: config.additionalDrawerLinkText, + logoSvgData: config.logoSvgData, + palettePrimaryDark: config.palettePrimaryDark, + palettePrimaryMain: config.palettePrimaryMain, + palettePrimaryLight: config.palettePrimaryLight, + installationCompleted: config.installationCompleted, + title: config.title, + }; +}; + +export default configSerializer; diff --git a/packages/backend/src/serializers/config.test.js b/packages/backend/src/serializers/config.test.js new file mode 100644 index 00000000..d4984725 --- /dev/null +++ b/packages/backend/src/serializers/config.test.js @@ -0,0 +1,32 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { getConfig } from '../../test/factories/config'; +import configSerializer from './config'; + +describe('configSerializer', () => { + let config; + + beforeEach(async () => { + config = await getConfig(); + }); + + it('should return config data', async () => { + const expectedPayload = { + id: config.id, + disableFavicon: config.disableFavicon, + disableNotificationsPage: config.disableNotificationsPage, + logoSvgData: config.logoSvgData, + palettePrimaryDark: config.palettePrimaryDark, + palettePrimaryMain: config.palettePrimaryMain, + palettePrimaryLight: config.palettePrimaryLight, + installationCompleted: config.installationCompleted, + title: config.title, + additionalDrawerLink: config.additionalDrawerLink, + additionalDrawerLinkIcon: config.additionalDrawerLinkIcon, + additionalDrawerLinkText: config.additionalDrawerLinkText, + createdAt: config.createdAt.getTime(), + updatedAt: config.updatedAt.getTime(), + }; + + expect(configSerializer(config)).toEqual(expectedPayload); + }); +}); diff --git a/packages/backend/src/serializers/index.js b/packages/backend/src/serializers/index.js index 971c4ded..9a38e5e9 100644 --- a/packages/backend/src/serializers/index.js +++ b/packages/backend/src/serializers/index.js @@ -17,6 +17,7 @@ import executionSerializer from './execution.js'; import executionStepSerializer from './execution-step.js'; import subscriptionSerializer from './subscription.ee.js'; import adminUserSerializer from './admin/user.js'; +import configSerializer from './config.js'; const serializers = { AdminUser: adminUserSerializer, @@ -38,6 +39,7 @@ const serializers = { Execution: executionSerializer, ExecutionStep: executionStepSerializer, Subscription: subscriptionSerializer, + Config: configSerializer, }; export default serializers; diff --git a/packages/backend/test/factories/config.js b/packages/backend/test/factories/config.js index 45f6395d..96ccc523 100644 --- a/packages/backend/test/factories/config.js +++ b/packages/backend/test/factories/config.js @@ -1,35 +1,15 @@ -import { faker } from '@faker-js/faker'; import Config from '../../src/models/config'; -export const createConfig = async (params = {}) => { - const configData = { - key: params?.key || faker.lorem.word(), - value: params?.value || { data: 'sampleConfig' }, - }; - - const config = await Config.query().insertAndFetch(configData); - - return config; +export const getConfig = async () => { + return await Config.get(); }; -export const createBulkConfig = async (params = {}) => { - const updateQueries = Object.entries(params).map(([key, value]) => { - const config = { - key, - value: { data: value }, - }; - - return createConfig(config); - }); - - await Promise.all(updateQueries); - - return await Config.query().whereIn('key', Object.keys(params)); +export const updateConfig = async (params = {}) => { + return await Config.update(params); }; -export const createInstallationCompletedConfig = async () => { - return await createConfig({ - key: 'installation.completed', - value: { data: true }, +export const markInstallationCompleted = async () => { + return await updateConfig({ + installationCompleted: true, }); }; 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 ba5cb838..62ae351e 100644 --- a/packages/backend/test/mocks/rest/api/v1/automatisch/config.js +++ b/packages/backend/test/mocks/rest/api/v1/automatisch/config.js @@ -1,28 +1,29 @@ -const infoMock = ( - logoConfig, - primaryDarkConfig, - primaryLightConfig, - primaryMainConfig, - titleConfig -) => { +const configMock = (config) => { return { data: { - disableFavicon: false, - disableNotificationsPage: false, - 'logo.svgData': logoConfig.value.data, - 'palette.primary.dark': primaryDarkConfig.value.data, - 'palette.primary.light': primaryLightConfig.value.data, - 'palette.primary.main': primaryMainConfig.value.data, - title: titleConfig.value.data, + id: config.id, + updatedAt: config.updatedAt.getTime(), + createdAt: config.createdAt.getTime(), + disableFavicon: config.disableFavicon, + disableNotificationsPage: config.disableNotificationsPage, + additionalDrawerLink: config.additionalDrawerLink, + additionalDrawerLinkIcon: config.additionalDrawerLinkIcon, + additionalDrawerLinkText: config.additionalDrawerLinkText, + logoSvgData: config.logoSvgData, + palettePrimaryDark: config.palettePrimaryDark, + palettePrimaryMain: config.palettePrimaryMain, + palettePrimaryLight: config.palettePrimaryLight, + installationCompleted: config.installationCompleted || false, + title: config.title, }, meta: { count: 1, currentPage: null, isArray: false, totalPages: null, - type: 'Object', + type: 'Config', }, }; }; -export default infoMock; +export default configMock; diff --git a/packages/web/src/components/ThemeProvider/index.jsx b/packages/web/src/components/ThemeProvider/index.jsx index cb9fca12..d6d17ec3 100644 --- a/packages/web/src/components/ThemeProvider/index.jsx +++ b/packages/web/src/components/ThemeProvider/index.jsx @@ -2,7 +2,6 @@ import PropTypes from 'prop-types'; import CssBaseline from '@mui/material/CssBaseline'; import { ThemeProvider as BaseThemeProvider } from '@mui/material/styles'; import clone from 'lodash/clone'; -import get from 'lodash/get'; import set from 'lodash/set'; import * as React from 'react'; @@ -10,18 +9,33 @@ import useAutomatischInfo from 'hooks/useAutomatischInfo'; import useAutomatischConfig from 'hooks/useAutomatischConfig'; import { defaultTheme, mationTheme } from 'styles/theme'; +const overrideIfGiven = (theme, key, value) => { + if (value) { + set(theme, key, value); + } +}; + const customizeTheme = (theme, config) => { // `clone` is needed so that the new theme reference triggers re-render const shallowDefaultTheme = clone(theme); - for (const key in config) { - const value = config[key]; - const exists = get(theme, key); + overrideIfGiven( + shallowDefaultTheme, + 'palette.primary.main', + config.palettePrimaryMain, + ); - if (exists) { - set(shallowDefaultTheme, key, value); - } - } + overrideIfGiven( + shallowDefaultTheme, + 'palette.primary.light', + config.palettePrimaryLight, + ); + + overrideIfGiven( + shallowDefaultTheme, + 'palette.primary.dark', + config.palettePrimaryDark, + ); return shallowDefaultTheme; }; diff --git a/packages/web/src/hooks/useAdminUpdateConfig.js b/packages/web/src/hooks/useAdminUpdateConfig.js index 03325253..4b5f513b 100644 --- a/packages/web/src/hooks/useAdminUpdateConfig.js +++ b/packages/web/src/hooks/useAdminUpdateConfig.js @@ -6,7 +6,7 @@ export default function useAdminUpdateConfig(appKey) { const query = useMutation({ mutationFn: async (payload) => { - const { data } = await api.patch(`/v1/admin/config`, payload); + const { data } = await api.patch('/v1/admin/config', payload); return data; }, diff --git a/packages/web/src/pages/UserInterface/index.jsx b/packages/web/src/pages/UserInterface/index.jsx index 5c528e10..a91e8152 100644 --- a/packages/web/src/pages/UserInterface/index.jsx +++ b/packages/web/src/pages/UserInterface/index.jsx @@ -2,7 +2,7 @@ import LoadingButton from '@mui/lab/LoadingButton'; import Grid from '@mui/material/Grid'; import Skeleton from '@mui/material/Skeleton'; import Stack from '@mui/material/Stack'; -import merge from 'lodash/merge'; +import mergeWith from 'lodash/mergeWith'; import * as React from 'react'; import ColorInput from 'components/ColorInput'; @@ -10,7 +10,6 @@ import Container from 'components/Container'; import Form from 'components/Form'; import PageTitle from 'components/PageTitle'; import TextField from 'components/TextField'; -import nestObject from 'helpers/nestObject'; import useAdminUpdateConfig from 'hooks/useAdminUpdateConfig'; import useAutomatischConfig from 'hooks/useAutomatischConfig'; import useFormatMessage from 'hooks/useFormatMessage'; @@ -27,9 +26,17 @@ const getPrimaryLightColor = (color) => color || primaryLightColor; const defaultValues = { title: 'Automatisch', - 'palette.primary.main': primaryMainColor, - 'palette.primary.dark': primaryDarkColor, - 'palette.primary.light': primaryLightColor, + palettePrimaryMain: primaryMainColor, + palettePrimaryDark: primaryDarkColor, + palettePrimaryLight: primaryLightColor, +}; + +const mergeIfGiven = (oldValue, newValue) => { + if (newValue) { + return newValue; + } + + return oldValue; }; export default function UserInterface() { @@ -39,21 +46,16 @@ export default function UserInterface() { const config = configData?.data; const enqueueSnackbar = useEnqueueSnackbar(); - const configWithDefaults = merge({}, defaultValues, nestObject(config)); + const configWithDefaults = mergeWith(defaultValues, config, mergeIfGiven); + const handleUserInterfaceUpdate = async (uiData) => { try { const input = { - title: uiData?.title, - 'palette.primary.main': getPrimaryMainColor( - uiData?.palette?.primary.main, - ), - 'palette.primary.dark': getPrimaryDarkColor( - uiData?.palette?.primary.dark, - ), - 'palette.primary.light': getPrimaryLightColor( - uiData?.palette?.primary.light, - ), - 'logo.svgData': uiData?.logo?.svgData, + title: uiData.title, + palettePrimaryMain: getPrimaryMainColor(uiData.palettePrimaryMain), + palettePrimaryDark: getPrimaryDarkColor(uiData.palettePrimaryDark), + palettePrimaryLight: getPrimaryLightColor(uiData.palettePrimaryLight), + 'logo.svgData': uiData.logoSvgData, }; await updateConfig(input); enqueueSnackbar(formatMessage('userInterfacePage.successfullyUpdated'), { @@ -66,6 +68,7 @@ export default function UserInterface() { throw new Error('Failed while updating!'); } }; + return ( @@ -96,7 +99,7 @@ export default function UserInterface() { />