Skip to content

Custom branding enhancement #1523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions components/layouts/SetupLinkLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const SetupLinkLayout = ({ children }: { children: React.ReactNode }) =>
}

const primaryColor = branding?.primaryColor ? hexToHsl(branding?.primaryColor) : null;
const backgroundColor = branding?.backgroundColor ? branding?.backgroundColor : null;
const title =
setupLink?.service === 'sso'
? t('configure_sso')
Expand All @@ -33,14 +34,21 @@ export const SetupLinkLayout = ({ children }: { children: React.ReactNode }) =>
: null;

return (
<>
<div className={`bg-red-500 borderColor textColor h-[100vh] w-[100vw]`}>
<Head>
<title>{`${title} - ${branding?.companyName}`}</title>
{branding?.faviconUrl && <link rel='icon' href={branding.faviconUrl} />}
</Head>

{primaryColor && (
<style>{`:root { --p: ${primaryColor}; --pf: ${darkenHslColor(primaryColor, 30)}; }`}</style>
<style>{`:root { --p: ${primaryColor}; --pf: ${darkenHslColor(primaryColor, 30)}; } .backgroundColor {
background-color: ${backgroundColor};
} .textColor {
color: ${branding?.textColor}
} .borderColor {
border: 0.5px solid ${branding?.borderColor};
border-radius: 4px;
}`}</style>
)}

<div className='mx-auto max-w-3xl'>
Expand All @@ -64,6 +72,6 @@ export const SetupLinkLayout = ({ children }: { children: React.ReactNode }) =>
</div>
</div>
<PoweredBy />
</>
</div>
);
};
22 changes: 20 additions & 2 deletions ee/branding/api/admin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,28 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const handlePOST = async (req: NextApiRequest, res: NextApiResponse) => {
const { brandingController } = await jackson();

const { logoUrl, faviconUrl, companyName, primaryColor } = req.body;
const {
logoUrl,
faviconUrl,
companyName,
primaryColor,
backgroundColor,
textColor,
borderColor,
darkTheme,
} = req.body;

return res.json({
data: await brandingController.update({ logoUrl, faviconUrl, companyName, primaryColor }),
data: await brandingController.update({
logoUrl,
faviconUrl,
companyName,
primaryColor,
backgroundColor,
textColor,
borderColor,
darkTheme,
}),
});
};

Expand Down
239 changes: 218 additions & 21 deletions ee/branding/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,27 @@ import LicenseRequired from '@components/LicenseRequired';
const Branding: NextPage = () => {
const { t } = useTranslation('common');
const [loading, setLoading] = useState(false);
const [isDarkThemeEnabled, setIsDarkThemeEnabled] = useState(false);
const [branding, setBranding] = useState<AdminPortalBranding>({
logoUrl: '',
faviconUrl: '',
companyName: '',
primaryColor: '',
backgroundColor: '',
textColor: '',
borderColor: '',
darkTheme: {
primaryColor: '',
backgroundColor: '',
textColor: '',
borderColor: '',
logoUrl: '',
},
});

// Fetch settings
const fetchSettings = async () => {
const rawResponse = await fetch('/api/admin/branding', {
const rawResponse = await fetch('/api/branding', {
headers: {
'Content-Type': 'application/json',
},
Expand Down Expand Up @@ -47,6 +58,7 @@ const Branding: NextPage = () => {
});

setLoading(false);
setIsDarkThemeEnabled(false);

const response: ApiResponse<AdminPortalBranding> = await rawResponse.json();

Expand All @@ -64,6 +76,23 @@ const Branding: NextPage = () => {
const onChange = (event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const target = event.target as HTMLInputElement;

// to handle onChange handler for dark theme values
if (target.id.startsWith('darkTheme')) {
// split the id which is in the form "darkTheme.property"
// in order to only get the property name
const id = event.target.id.split('.')[1];

setBranding({
...branding,
darkTheme: {
...branding.darkTheme,
[id]: target.value,
},
});

return;
}

setBranding({
...branding,
[target.id]: target.value,
Expand All @@ -80,23 +109,19 @@ const Branding: NextPage = () => {
<p className='py-3 text-base leading-6 text-gray-800'>{t('settings_branding_description')}</p>
<div className='rounded border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-800'>
<form onSubmit={onSubmit}>
<div className='flex flex-col space-y-2'>
<div className='form-control w-full md:w-1/2'>
<label className='label'>
<span className='label-text'>{t('branding_logo_url_label')}</span>
</label>
<div className='flex gap-3 mb-5 justify-between'>
<h1 className='text-2xl font-bold'>Light theme</h1>
<div className='flex items-center justify-center gap-3'>
<h3 className='font-bold text-xl'>Enable dark theme</h3>
<input
type='url'
id='logoUrl'
className='input-bordered input'
onChange={onChange}
value={branding.logoUrl || ''}
placeholder='https://company.com/logo.png'
type='checkbox'
onChange={() => setIsDarkThemeEnabled(!isDarkThemeEnabled)}
className='toggle toggle-md'
checked={isDarkThemeEnabled}
/>
<label className='label'>
<span className='label-text-alt'>{t('branding_logo_url_alt')}</span>
</label>
</div>
</div>
<div className='flex flex-col space-y-2'>
<div className='form-control w-full md:w-1/2'>
<label className='label'>
<span className='label-text'>{t('branding_favicon_url_label')}</span>
Expand Down Expand Up @@ -129,17 +154,189 @@ const Branding: NextPage = () => {
<span className='label-text-alt'>{t('branding_company_name_alt')}</span>
</label>
</div>
<div className='form-control'>
<div className='form-control w-full md:w-1/2'>
<label className='label'>
<span className='label-text'>{t('branding_primary_color_label')}</span>
<span className='label-text'>{t('branding_logo_url_label')}</span>
</label>
<input type='color' id='primaryColor' onChange={onChange} value={branding.primaryColor || ''} />
<input
type='url'
id='logoUrl'
className='input-bordered input'
onChange={onChange}
value={branding.logoUrl || ''}
placeholder='https://company.com/logo.png'
/>
<label className='label'>
<span className='label-text-alt'>{t('branding_primary_color_alt')}</span>
<span className='label-text-alt'>{t('branding_logo_url_alt')}</span>
</label>
</div>
<div className='mt-5'>
<ButtonPrimary loading={loading}>{t('save_changes')}</ButtonPrimary>
<div className='flex gap-7'>
<div className='form-control'>
<div className='flex'>
<label className='label pr-3'>
<span className='label-text'>{t('branding_primary_color_label')}</span>
</label>
</div>
<div className='flex gap-3 border-[1px] border-gray-200 rounded-md p-2 w-fit'>
<h3 className='border-r-[1px] border-gray-200 pr-2'>
{branding.primaryColor || '#000000'}
</h3>
<input
type='color'
id='primaryColor'
onChange={onChange}
value={branding.primaryColor || ''}
/>
</div>
</div>
<div className='form-control'>
<div className='flex'>
<label className='label pr-3'>
<span className='label-text'>Background Color</span>
</label>
</div>
<div className='flex gap-3 border-[1px] border-gray-200 rounded-md p-2 w-fit'>
<h3 className='border-r-[1px] border-gray-200 pr-2'>
{branding.backgroundColor || '#000000'}
</h3>
<input
id='backgroundColor'
type='color'
onChange={onChange}
value={branding.backgroundColor || ''}
/>
</div>
</div>
<div className='form-control'>
<div className='flex'>
<label className='label pr-3'>
<span className='label-text'>Text Color</span>
</label>
</div>
<div className='flex gap-3 border-[1px] border-gray-200 rounded-md p-2 w-fit'>
<h3 className='border-r-[1px] border-gray-200 pr-2'>{branding.textColor || '#000000'}</h3>
<input id='textColor' type='color' onChange={onChange} value={branding.textColor || ''} />
</div>
</div>
<div className='form-control'>
<div className='flex'>
<label className='label pr-3'>
<span className='label-text'>Border Color</span>
</label>
</div>
<div className='flex gap-3 border-[1px] border-gray-200 rounded-md p-2 w-fit'>
<h3 className='border-r-[1px] border-gray-200 pr-2'>{branding.borderColor || '#000000'}</h3>
<input
id='borderColor'
type='color'
onChange={onChange}
value={branding.borderColor || ''}
/>
</div>
</div>
</div>
{/* Only to be displayed in case of dark theme state being set to true */}
{isDarkThemeEnabled ? (
<>
<h1 className='text-2xl font-bold mt-5 pt-5'>Dark theme</h1>
<div className='form-control w-full md:w-1/2'>
<label className='label'>
<span className='label-text'>{t('branding_logo_url_label')}</span>
</label>
<input
type='url'
id='darkTheme.logoUrl'
className='input-bordered input'
onChange={onChange}
value={branding.darkTheme?.logoUrl || ''}
placeholder='https://company.com/logo.png'
/>
<label className='label'>
<span className='label-text-alt'>{t('branding_logo_url_alt')}</span>
</label>
</div>
<div className='flex gap-7'>
<div className='form-control'>
<div className='flex'>
<label className='label pr-3'>
<span className='label-text'>{t('branding_primary_color_label')}</span>
</label>
</div>
<div className='flex gap-3 border-[1px] border-gray-200 rounded-md p-2 w-fit'>
<h3 className='border-r-[1px] border-gray-200 pr-2'>
{/* Not sure exactly what the default primary color should be in this case, since default
color for primary color is #25c2a0 so '#124f45' since its a darker shade of that */}
{branding.darkTheme?.primaryColor || '#124f45'}
</h3>
<input
type='color'
id='darkTheme.primaryColor'
onChange={onChange}
value={branding.darkTheme?.primaryColor || ''}
/>
</div>
</div>
<div className='form-control'>
<div className='flex'>
<label className='label pr-3'>
<span className='label-text'>Background Color</span>
</label>
</div>
<div className='flex gap-3 border-[1px] border-gray-200 rounded-md p-2 w-fit'>
<h3 className='border-r-[1px] border-gray-200 pr-2'>
{branding.darkTheme?.backgroundColor || '#18181b'}
</h3>
<input
id='darkTheme.backgroundColor'
type='color'
onChange={onChange}
value={branding.darkTheme?.backgroundColor || ''}
/>
</div>
</div>
<div className='form-control'>
<div className='flex'>
<label className='label pr-3'>
<span className='label-text'>Text color</span>
</label>
</div>
<div className='flex gap-3 border-[1px] border-gray-200 rounded-md p-2 w-fit'>
<h3 className='border-r-[1px] border-gray-200 pr-2'>
{branding.darkTheme?.textColor || '#f4f4f5'}
</h3>
<input
id='darkTheme.textColor'
type='color'
onChange={onChange}
value={branding.darkTheme?.textColor || ''}
/>
</div>
</div>
<div className='form-control'>
<div className='flex'>
<label className='label pr-3'>
<span className='label-text'>Border color</span>
</label>
</div>
<div className='flex gap-3 border-[1px] border-gray-200 rounded-md p-2 w-fit'>
<h3 className='border-r-[1px] border-gray-200 pr-2'>
{branding.darkTheme?.borderColor || '#e5e7eb'}
</h3>
<input
id='darkTheme.borderColor'
type='color'
onChange={onChange}
value={branding.darkTheme?.borderColor || ''}
/>
</div>
</div>
</div>
</>
) : null}
<div>
<ButtonPrimary className='mt-5' loading={loading}>
{t('save_changes')}
</ButtonPrimary>
</div>
</div>
</form>
Expand Down
3 changes: 3 additions & 0 deletions ee/federated-saml/pages/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ const UpdateApp: NextPage = () => {
logoUrl: '',
faviconUrl: '',
primaryColor: '',
textColor: '',
borderColor: '',
backgroundColor: '',
});

const { id } = router.query as { id: string };
Expand Down
Loading