diff --git a/apps/sensenet/src/assets/admin-favicon.ico b/apps/sensenet/src/assets/admin-favicon.ico new file mode 100644 index 0000000000..0bc9fabe8f Binary files /dev/null and b/apps/sensenet/src/assets/admin-favicon.ico differ diff --git a/apps/sensenet/src/assets/admin-favicon.svg b/apps/sensenet/src/assets/admin-favicon.svg new file mode 100644 index 0000000000..eeb5ad601a --- /dev/null +++ b/apps/sensenet/src/assets/admin-favicon.svg @@ -0,0 +1,8 @@ + + sensenet Admin + + + + + + diff --git a/apps/sensenet/src/assets/favicon.ico b/apps/sensenet/src/assets/favicon.ico deleted file mode 100644 index fc0450626b..0000000000 Binary files a/apps/sensenet/src/assets/favicon.ico and /dev/null differ diff --git a/apps/sensenet/src/assets/sensenet-icon-32.png b/apps/sensenet/src/assets/sensenet-icon-32.png deleted file mode 100644 index 4ef5a708b5..0000000000 Binary files a/apps/sensenet/src/assets/sensenet-icon-32.png and /dev/null differ diff --git a/apps/sensenet/src/assets/sensenet-icon-32_hat.png b/apps/sensenet/src/assets/sensenet-icon-32_hat.png deleted file mode 100644 index 6d26c104ed..0000000000 Binary files a/apps/sensenet/src/assets/sensenet-icon-32_hat.png and /dev/null differ diff --git a/apps/sensenet/src/assets/sensenet_logo_transparent.png b/apps/sensenet/src/assets/sensenet_logo_transparent.png deleted file mode 100644 index bad98671e8..0000000000 Binary files a/apps/sensenet/src/assets/sensenet_logo_transparent.png and /dev/null differ diff --git a/apps/sensenet/src/assets/sensenet_white.png b/apps/sensenet/src/assets/sensenet_white.png deleted file mode 100644 index 5cc1e0f62e..0000000000 Binary files a/apps/sensenet/src/assets/sensenet_white.png and /dev/null differ diff --git a/apps/sensenet/src/assets/sn-zseni__listekn__s.png b/apps/sensenet/src/assets/sn-zseni__listekn__s.png deleted file mode 100644 index 380540e9e9..0000000000 Binary files a/apps/sensenet/src/assets/sn-zseni__listekn__s.png and /dev/null differ diff --git a/apps/sensenet/src/components/appbar/desktop-app-bar.tsx b/apps/sensenet/src/components/appbar/desktop-app-bar.tsx index 6e183f6f97..3bf5ab1866 100644 --- a/apps/sensenet/src/components/appbar/desktop-app-bar.tsx +++ b/apps/sensenet/src/components/appbar/desktop-app-bar.tsx @@ -4,12 +4,12 @@ import { useLogger, useRepository } from '@sensenet/hooks-react' import { clsx } from 'clsx' import React, { useContext, useEffect, useState } from 'react' import { Link } from 'react-router-dom' -import logo from '../../assets/sensenet_white.png' import { ResponsiveContext, ResponsivePersonalSettings } from '../../context' import { globals, useGlobalStyles } from '../../globalStyles' import { useLocalization } from '../../hooks' import { loadRepositorySettings } from '../../services/repository-settings-service' import { CommandPalette } from '../command-palette/CommandPalette' +import { SensenetLogo } from '../sensenet-logo' import { DesktopNavMenu } from './desktop-nav-menu' import '../drawer/app-navigation.css' @@ -111,7 +111,7 @@ export const DesktopAppBar: React.FunctionComponent<{ openDrawer?: () => void; d - + {personalSettings.drawer.enabled && (personalSettings.drawer.type === 'temporary' || device === 'mobile') ? ( - + {props.subscription.plan.displayName} {props.subscription.plan.baseprice !== undefined && `(${ diff --git a/apps/sensenet/src/components/login/assets/README.md b/apps/sensenet/src/components/login/assets/README.md index c83ec9e53f..e1dbdd9df2 100644 --- a/apps/sensenet/src/components/login/assets/README.md +++ b/apps/sensenet/src/components/login/assets/README.md @@ -1,8 +1,9 @@ Assets copied without modification from SenseNet/sn-auth: + - src/sn-auth/wwwroot/images/background.png -> local-auth-background.png -- src/sn-auth/wwwroot/images/sensenet-logo.svg -> local-auth-logo.svg - src/sn-auth/wwwroot/fonts/spartan/Spartan-Medium.ttf - src/sn-auth/wwwroot/fonts/spartan/Spartan-SemiBold.ttf -The default local login shares SNAuth's visual assets; repository appearance settings -can replace the background/logo and colors. Rendering uses native HTML/CSS. +The local login shares SNAuth's background and fonts. Its default brand mark comes +from the shared AdminUI `SensenetLogo` component; repository appearance settings +can replace the panel logo, background and colors. Rendering uses native HTML/CSS. diff --git a/apps/sensenet/src/components/login/assets/local-auth-logo.svg b/apps/sensenet/src/components/login/assets/local-auth-logo.svg deleted file mode 100644 index 8fd1103934..0000000000 --- a/apps/sensenet/src/components/login/assets/local-auth-logo.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/sensenet/src/components/login/auth-override-skeleton.tsx b/apps/sensenet/src/components/login/auth-override-skeleton.tsx index f18be1ca86..baff39b9a2 100644 --- a/apps/sensenet/src/components/login/auth-override-skeleton.tsx +++ b/apps/sensenet/src/components/login/auth-override-skeleton.tsx @@ -1,8 +1,8 @@ import { AppBar, Button, Container, createStyles, makeStyles, Theme, Toolbar, Typography } from '@material-ui/core' import React from 'react' import { NavLink } from 'react-router-dom' -import logo from '../../assets/sensenet_white.png' import { globals, useGlobalStyles } from '../../globalStyles' +import { SensenetLogo } from '../sensenet-logo' const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -58,7 +58,7 @@ export const AuthOverrideSkeleton = (props: { - + diff --git a/apps/sensenet/src/components/login/local-login-page.css b/apps/sensenet/src/components/login/local-login-page.css index 5c5fa7d3df..a95ddca2c6 100644 --- a/apps/sensenet/src/components/login/local-login-page.css +++ b/apps/sensenet/src/components/login/local-login-page.css @@ -52,6 +52,10 @@ max-width: 100%; height: auto; } +.local-auth-brand .local-auth-default-logo { + width: 96px; + height: 96px; +} .local-auth-content { flex: 1; min-width: 0; @@ -181,8 +185,8 @@ } .local-auth-footer img { display: block; - width: 98px; - height: auto; + width: 32px; + height: 32px; } @media (max-width: 720px) { .local-auth-screen { diff --git a/apps/sensenet/src/components/login/local-login-page.tsx b/apps/sensenet/src/components/login/local-login-page.tsx index c822c519ee..5cb5524e7f 100644 --- a/apps/sensenet/src/components/login/local-login-page.tsx +++ b/apps/sensenet/src/components/login/local-login-page.tsx @@ -1,7 +1,7 @@ import React, { FormEvent, useEffect, useRef, useState } from 'react' import { LocalLoginAppearance, LocalMfaChallenge, LocalRepositorySession } from '../../services/local-authentication' +import { SensenetLogo } from '../sensenet-logo' import background from './assets/local-auth-background.png' -import logo from './assets/local-auth-logo.svg' import './local-login-page.css' const color = (value: unknown, fallback: string) => @@ -14,16 +14,35 @@ const imageUrl = (value?: string) => { return undefined } } -export const loginAppearance = (appearance: LocalLoginAppearance = {}) => - ({ +const legacyAppearanceColors = { + backgroundColor: '#d4f3fa', + brandColor: '#38a9cb', + buttonColor: '#38a9cb', + buttonTextColor: '#ffffff', + textColor: '#343b43', + panelColor: '#ffffff', +} + +export const loginAppearance = (appearance: LocalLoginAppearance = {}) => { + // Older servers send the entire default palette, so a fallback alone cannot update it. + // Keep repository branding when any color or image differs from that stock palette. + const legacyDefaults = + !appearance.logoUrl && + !appearance.backgroundImageUrl && + Object.entries(legacyAppearanceColors).every(([key, expected]) => { + const value = appearance[key as keyof LocalLoginAppearance] + return value == null || (typeof value === 'string' && value.toLowerCase() === expected) + }) + return { '--local-background': color(appearance.backgroundColor, '#d4f3fa'), - '--local-brand': color(appearance.brandColor, '#38a9cb'), + '--local-brand': legacyDefaults ? '#0197db' : color(appearance.brandColor, '#0197db'), '--local-button': color(appearance.buttonColor, '#38a9cb'), '--local-button-text': color(appearance.buttonTextColor, '#ffffff'), '--local-text': color(appearance.textColor, '#343b43'), '--local-panel': color(appearance.panelColor, '#ffffff'), backgroundImage: `url(${JSON.stringify(imageUrl(appearance.backgroundImageUrl) || background)})`, - } as React.CSSProperties) + } as React.CSSProperties +} export function LocalLoginPage({ repositoryUrl, @@ -57,6 +76,7 @@ export function LocalLoginPage({ const [failure, setFailure] = useState('') const [notice, setNotice] = useState('') const appearance = session?.endpoints.appearance + const repositoryLogo = imageUrl(appearance?.logoUrl) const title = typeof appearance?.title === 'string' ? appearance.title.slice(0, 120) : 'Login to sensenet' const busy = working || loading const disabled = busy || !session @@ -136,7 +156,11 @@ export function LocalLoginPage({ {heading} @@ -285,7 +309,7 @@ export function LocalLoginPage({ - + ) diff --git a/apps/sensenet/src/components/login/login-page.tsx b/apps/sensenet/src/components/login/login-page.tsx index 76e32c1caa..492fc50ba3 100644 --- a/apps/sensenet/src/components/login/login-page.tsx +++ b/apps/sensenet/src/components/login/login-page.tsx @@ -29,7 +29,6 @@ import MoreHoriz from '@material-ui/icons/MoreHoriz' import Storage from '@material-ui/icons/Storage' import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react' import { Link } from 'react-router-dom' -import snLogo from '../../assets/sensenet_white.png' import { globals } from '../../globalStyles' import { useLocalization } from '../../hooks' import { useRepositoryConnections } from '../../hooks/use-repository-connections' @@ -42,6 +41,7 @@ import { restoreRepositoryConnection, setRepositoryConnectionPinned, } from '../../services/repository-connections' +import { SensenetLogo } from '../sensenet-logo' const recentRepositoryLimit = 6 @@ -299,7 +299,7 @@ export default function LoginPage({ <> - + diff --git a/apps/sensenet/src/components/sensenet-logo.tsx b/apps/sensenet/src/components/sensenet-logo.tsx new file mode 100644 index 0000000000..3bb9a0acd8 --- /dev/null +++ b/apps/sensenet/src/components/sensenet-logo.tsx @@ -0,0 +1,6 @@ +import React from 'react' +import logo from '../assets/admin-favicon.svg' + +export function SensenetLogo(props: Omit, 'src'>) { + return +} diff --git a/apps/sensenet/src/context/ThemeProvider.tsx b/apps/sensenet/src/context/ThemeProvider.tsx index 6aa0e8cdd7..7a932f4f64 100644 --- a/apps/sensenet/src/context/ThemeProvider.tsx +++ b/apps/sensenet/src/context/ThemeProvider.tsx @@ -75,7 +75,7 @@ export const ThemeProvider: React.FunctionComponent = (props) => { }, primary: { light: '#00B1E4', - main: '#0192db', + main: '#0197db', dark: '#004E8F', }, secondary: { diff --git a/apps/sensenet/src/globalStyles.ts b/apps/sensenet/src/globalStyles.ts index b51d154ff8..7cb282faa3 100644 --- a/apps/sensenet/src/globalStyles.ts +++ b/apps/sensenet/src/globalStyles.ts @@ -13,8 +13,8 @@ export const globals = { settingsDrawerWidth: 250, addButtonHeight: 40, //Colors - headerBackground: '#0192db', - headerLightBackground: '#0192db', + headerBackground: '#0197db', + headerLightBackground: '#0197db', headerText: 'rgba(255,255,255,0.87)', secondaryIconSize: '16px', elavationShadow: '1px 1px 3px 0px hsl(0deg 0% 0% / 28%)', diff --git a/apps/sensenet/test/local-login-page.test.tsx b/apps/sensenet/test/local-login-page.test.tsx index 5c020a9672..152202f31b 100644 --- a/apps/sensenet/test/local-login-page.test.tsx +++ b/apps/sensenet/test/local-login-page.test.tsx @@ -4,6 +4,8 @@ import { act, Simulate } from 'react-dom/test-utils' import { LocalLoginPage, loginAppearance } from '../src/components/login/local-login-page' import { LocalRepositorySession, localSessionKey } from '../src/services/local-authentication' +jest.mock('../src/assets/admin-favicon.svg', () => 'admin-favicon.svg') + jest.mock('../src/services/repository-session', () => ({ normalizeRepositoryUrl: (url: string) => url.replace(/\/$/, ''), })) @@ -77,6 +79,54 @@ it('renders native controls and repository branding without accepting arbitrary expect(safe.backgroundImage).not.toContain('javascript:') }) +it('uses the shared rocket in the default brand panel and footer', () => { + mount(new LocalRepositorySession(repo, endpoints, jest.fn())) + expect((element.querySelector('main') as HTMLElement).style.getPropertyValue('--local-brand')).toBe('#0197db') + const panelLogo = element.querySelector('.local-auth-brand img')! + const footerLogo = element.querySelector('.local-auth-footer img')! + expect(panelLogo.getAttribute('src')).toBe('admin-favicon.svg') + expect(footerLogo.getAttribute('src')).toBe('admin-favicon.svg') +}) + +const legacyServerAppearance = { + title: 'Login to Test Repository', + backgroundColor: '#d4f3fa', + brandColor: '#38a9cb', + buttonColor: '#38a9cb', + buttonTextColor: '#ffffff', + textColor: '#343b43', + panelColor: '#ffffff', +} + +it.each(['#38a9cb', '#38A9CB'])('updates the stock legacy server panel color %s', (brandColor) => { + const appearance = loginAppearance({ ...legacyServerAppearance, brandColor }) as any + expect(appearance['--local-brand']).toBe('#0197db') + expect(appearance['--local-button']).toBe('#38a9cb') +}) + +it.each([ + { logoUrl: 'https://customer.example/logo.svg' }, + { backgroundImageUrl: 'https://customer.example/background.png' }, + { buttonColor: '#254f78' }, +])('preserves the old panel color for customized repository appearance %o', (custom) => { + const appearance = loginAppearance({ ...legacyServerAppearance, ...custom }) as any + expect(appearance['--local-brand']).toBe('#38a9cb') +}) + +it('preserves the repository logo override while keeping the footer on the shared rocket', () => { + const brandedEndpoints = { + ...endpoints, + appearance: { ...endpoints.appearance, brandColor: '#254f78', logoUrl: 'https://customer.example/logo.svg' }, + } + mount(new LocalRepositorySession(repo, brandedEndpoints, jest.fn())) + const panelLogo = element.querySelector('.local-auth-brand img')! + const footerLogo = element.querySelector('.local-auth-footer img')! + expect(panelLogo.getAttribute('src')).toBe('https://customer.example/logo.svg') + expect((element.querySelector('main') as HTMLElement).style.getPropertyValue('--local-brand')).toBe('#254f78') + expect(panelLogo.getAttribute('referrerpolicy')).toBe('no-referrer') + expect(footerLogo.getAttribute('src')).toBe('admin-favicon.svg') +}) + it('requires a separate MFA step and never stores the password or challenge', async () => { const request = jest .fn() diff --git a/apps/sensenet/webpack.dev.js b/apps/sensenet/webpack.dev.js index 841f6e6f2a..5a43b432d4 100644 --- a/apps/sensenet/webpack.dev.js +++ b/apps/sensenet/webpack.dev.js @@ -46,7 +46,7 @@ module.exports = (env) => { }), new HtmlWebpackPlugin({ template: './index.html', - favicon: './src/assets/favicon.ico', + favicon: './src/assets/admin-favicon.ico', }), ], module: { diff --git a/apps/sensenet/webpack.prod.js b/apps/sensenet/webpack.prod.js index e0fa5a8898..262ac5b9ee 100644 --- a/apps/sensenet/webpack.prod.js +++ b/apps/sensenet/webpack.prod.js @@ -50,7 +50,7 @@ module.exports = merge(common, { new MiniCssExtractPlugin(), new HtmlWebpackPlugin({ template: './index.html', - favicon: './src/assets/favicon.ico', + favicon: './src/assets/admin-favicon.ico', minify: { removeComments: true, collapseWhitespace: true,