diff --git a/components/layouts/SetupLinkLayout.tsx b/components/layouts/SetupLinkLayout.tsx index 2cb8b5ef5..164e93417 100644 --- a/components/layouts/SetupLinkLayout.tsx +++ b/components/layouts/SetupLinkLayout.tsx @@ -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') @@ -33,14 +34,21 @@ export const SetupLinkLayout = ({ children }: { children: React.ReactNode }) => : null; return ( - <> +
{`${title} - ${branding?.companyName}`} {branding?.faviconUrl && } {primaryColor && ( - + )}
@@ -64,6 +72,6 @@ export const SetupLinkLayout = ({ children }: { children: React.ReactNode }) =>
- + ); }; diff --git a/ee/branding/api/admin/index.ts b/ee/branding/api/admin/index.ts index 3658de29e..ba7452b64 100644 --- a/ee/branding/api/admin/index.ts +++ b/ee/branding/api/admin/index.ts @@ -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, + }), }); }; diff --git a/ee/branding/pages/index.tsx b/ee/branding/pages/index.tsx index f20fac0a4..8bb2672a1 100644 --- a/ee/branding/pages/index.tsx +++ b/ee/branding/pages/index.tsx @@ -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({ 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', }, @@ -47,6 +58,7 @@ const Branding: NextPage = () => { }); setLoading(false); + setIsDarkThemeEnabled(false); const response: ApiResponse = await rawResponse.json(); @@ -64,6 +76,23 @@ const Branding: NextPage = () => { const onChange = (event: React.ChangeEvent) => { 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, @@ -80,23 +109,19 @@ const Branding: NextPage = () => {

{t('settings_branding_description')}

-
-
- +
+

Light theme

+
+

Enable dark theme

setIsDarkThemeEnabled(!isDarkThemeEnabled)} + className='toggle toggle-md' + checked={isDarkThemeEnabled} /> -
+
+
-
+
- +
-
- {t('save_changes')} +
+
+
+ +
+
+

+ {branding.primaryColor || '#000000'} +

+ +
+
+
+
+ +
+
+

+ {branding.backgroundColor || '#000000'} +

+ +
+
+
+
+ +
+
+

{branding.textColor || '#000000'}

+ +
+
+
+
+ +
+
+

{branding.borderColor || '#000000'}

+ +
+
+
+ {/* Only to be displayed in case of dark theme state being set to true */} + {isDarkThemeEnabled ? ( + <> +

Dark theme

+
+ + + +
+
+
+
+ +
+
+

+ {/* 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'} +

+ +
+
+
+
+ +
+
+

+ {branding.darkTheme?.backgroundColor || '#18181b'} +

+ +
+
+
+
+ +
+
+

+ {branding.darkTheme?.textColor || '#f4f4f5'} +

+ +
+
+
+
+ +
+
+

+ {branding.darkTheme?.borderColor || '#e5e7eb'} +

+ +
+
+
+ + ) : null} +
+ + {t('save_changes')} +
diff --git a/ee/federated-saml/pages/edit.tsx b/ee/federated-saml/pages/edit.tsx index 4461b6e53..935281aa1 100644 --- a/ee/federated-saml/pages/edit.tsx +++ b/ee/federated-saml/pages/edit.tsx @@ -30,6 +30,9 @@ const UpdateApp: NextPage = () => { logoUrl: '', faviconUrl: '', primaryColor: '', + textColor: '', + borderColor: '', + backgroundColor: '', }); const { id } = router.query as { id: string }; diff --git a/lib/settings.ts b/lib/settings.ts index 1400030a4..376aedda0 100644 --- a/lib/settings.ts +++ b/lib/settings.ts @@ -2,10 +2,14 @@ import jackson from '@lib/jackson'; // BoxyHQ branding export const boxyhqBranding = { - logoUrl: '/logo.png', - faviconUrl: '/favicon.ico', + logoUrl: 'http://localhost:5225/logo.png', + faviconUrl: 'http://localhost:5225/favicon.ico', companyName: 'BoxyHQ', primaryColor: '#25c2a0', + backgroundColor: '#ffffff', + textColor: '#000000', + borderColor: '#0000000', + darkTheme: null, } as const; export const getPortalBranding = async () => { @@ -21,7 +25,11 @@ export const getPortalBranding = async () => { return { logoUrl: customBranding?.logoUrl || boxyhqBranding.logoUrl, primaryColor: customBranding?.primaryColor || boxyhqBranding.primaryColor, + backgroundColor: customBranding?.backgroundColor || boxyhqBranding.backgroundColor, + textColor: customBranding?.textColor || boxyhqBranding.textColor, + borderColor: customBranding?.borderColor || boxyhqBranding.borderColor, faviconUrl: customBranding?.faviconUrl || boxyhqBranding.faviconUrl, companyName: customBranding?.companyName || boxyhqBranding.companyName, + darkTheme: customBranding?.darkTheme || boxyhqBranding.darkTheme, }; }; diff --git a/lib/ui/hooks/usePortalBranding.ts b/lib/ui/hooks/usePortalBranding.ts index 0883c2ec9..4f055c959 100644 --- a/lib/ui/hooks/usePortalBranding.ts +++ b/lib/ui/hooks/usePortalBranding.ts @@ -11,6 +11,9 @@ const usePortalBranding = () => { primaryColor: string; faviconUrl: string; companyName: string; + backgroundColor: string; + textColor: string; + borderColor: string; }>, ApiError >(url, fetcher); diff --git a/next.config.js b/next.config.js index f8ba19a57..a81e5b115 100644 --- a/next.config.js +++ b/next.config.js @@ -95,6 +95,18 @@ module.exports = { protocol: 'https', hostname: '*', }, + { + protocol: 'http', + hostname: 'localhost', + port: '5225', + pathname: '/logo.png', + }, + { + protocol: 'http', + hostname: 'localhost', + port: '5225', + pathname: '/favicon.ico', + }, ], }, }; diff --git a/npm/src/ee/branding/index.ts b/npm/src/ee/branding/index.ts index 233579f94..cf9d035ae 100644 --- a/npm/src/ee/branding/index.ts +++ b/npm/src/ee/branding/index.ts @@ -22,6 +22,10 @@ export class BrandingController { faviconUrl: null, companyName: null, primaryColor: null, + textColor: null, + borderColor: null, + backgroundColor: null, + darkTheme: null, }; return branding ? branding : defaultBranding; @@ -31,7 +35,16 @@ export class BrandingController { public async update(params: Partial) { await throwIfInvalidLicense(this.opts.boxyhqLicenseKey); - const { logoUrl, faviconUrl, companyName, primaryColor } = params; + const { + logoUrl, + faviconUrl, + companyName, + primaryColor, + textColor, + borderColor, + backgroundColor, + darkTheme, + } = params; const currentBranding = await this.get(); @@ -40,6 +53,10 @@ export class BrandingController { faviconUrl: faviconUrl ?? null, companyName: companyName ?? null, primaryColor: primaryColor ?? null, + textColor: textColor ?? null, + borderColor: borderColor ?? null, + backgroundColor: backgroundColor ?? null, + darkTheme: darkTheme ?? null, }; const updatedbranding = { diff --git a/npm/src/ee/federated-saml/app.ts b/npm/src/ee/federated-saml/app.ts index 62b2bd883..2046ac2cc 100644 --- a/npm/src/ee/federated-saml/app.ts +++ b/npm/src/ee/federated-saml/app.ts @@ -42,6 +42,9 @@ export class App { logoUrl: null, faviconUrl: null, primaryColor: null, + textColor: null, + borderColor: null, + backgroundColor: null, }; await this.store.put(id, app, { diff --git a/npm/src/ee/federated-saml/types.ts b/npm/src/ee/federated-saml/types.ts index 9490dbe33..a0e4e2d58 100644 --- a/npm/src/ee/federated-saml/types.ts +++ b/npm/src/ee/federated-saml/types.ts @@ -12,6 +12,16 @@ export type SAMLFederationApp = { logoUrl: string | null; faviconUrl: string | null; primaryColor: string | null; + textColor: string | null; + borderColor: string | null; + backgroundColor: string | null; + darkTheme?: { + primaryColor?: string; + backgroundColor?: string | null; + textColor?: string | null; + borderColor?: string | null; + logoUrl?: string; + }; }; export type SAMLFederationAppWithMetadata = SAMLFederationApp & { diff --git a/npm/src/typings.ts b/npm/src/typings.ts index a4bfcc5fe..de1b7cf40 100644 --- a/npm/src/typings.ts +++ b/npm/src/typings.ts @@ -537,7 +537,17 @@ export type AdminPortalBranding = { logoUrl: string | null; faviconUrl: string | null; primaryColor: string | null; + backgroundColor: string | null; companyName: string | null; + textColor: string | null; + borderColor: string | null; + darkTheme?: { + primaryColor?: string; + backgroundColor?: string | null; + textColor?: string | null; + borderColor?: string | null; + logoUrl?: string; + }; }; export type Webhook = { diff --git a/pages/idp/select.tsx b/pages/idp/select.tsx index 09265fa2d..537513222 100644 --- a/pages/idp/select.tsx +++ b/pages/idp/select.tsx @@ -21,36 +21,71 @@ export default function ChooseIdPConnection({ const { t } = useTranslation('common'); const primaryColor = hexToHsl(branding.primaryColor); + const textColor = hexToHsl(branding.textColor); const title = requestType === 'sp-initiated' ? t('select_an_idp') : t('select_an_app'); return ( -
-
- - {`${title} - ${branding.companyName}`} - {branding?.faviconUrl && } - - - {primaryColor && ( - - )} - - {branding?.logoUrl && ( -
- {branding.companyName} -
- )} - - {requestType === 'sp-initiated' ? ( - - ) : ( - - )} +
+
+
+ + {`${title} - ${branding.companyName}`} + {branding?.faviconUrl && } + + + {primaryColor && ( + + )} + + {/* --bc: ${textColor}; */} + + {branding?.logoUrl && ( +
+ {branding.companyName} +
+ )} + + {requestType === 'sp-initiated' ? ( + + ) : ( + + )} +
+
+ +
-
- -
-
+ ); } @@ -85,9 +120,9 @@ const IdpSelector = ({ connections }: { connections: (OIDCSSORecord | SAMLSSORec onClick={() => { connectionSelected(connection.clientID); }}> -
+
-
+
{name.charAt(0).toUpperCase()}
@@ -98,7 +133,7 @@ const IdpSelector = ({ connections }: { connections: (OIDCSSORecord | SAMLSSORec ); })} -

+

Choose an Identity Provider to continue. If you don't see your Identity Provider, please contact your administrator.

@@ -213,6 +248,10 @@ export const getServerSideProps = async ({ query, locale, req }) => { primaryColor: samlFederationApp?.primaryColor || branding.primaryColor, faviconUrl: samlFederationApp?.faviconUrl || branding.faviconUrl, companyName: samlFederationApp?.name || branding.companyName, + textColor: samlFederationApp?.textColor || branding.textColor, + borderColor: samlFederationApp?.borderColor || branding.borderColor, + backgroundColor: samlFederationApp?.backgroundColor || branding.backgroundColor, + darkTheme: {}, }; } diff --git a/pages/setup/[token]/sso-connection/index.tsx b/pages/setup/[token]/sso-connection/index.tsx index 7fd72787b..7042c0978 100644 --- a/pages/setup/[token]/sso-connection/index.tsx +++ b/pages/setup/[token]/sso-connection/index.tsx @@ -1,16 +1,34 @@ import type { NextPage } from 'next'; import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; -import ConnectionList from '@components/connection/ConnectionList'; +import { ConnectionList } from '@boxyhq/react-ui/sso'; import { useRouter } from 'next/router'; import useIdpEntityID from '@lib/ui/hooks/useIdpEntityID'; +import usePaginate from '@lib/ui/hooks/usePaginate'; const ConnectionsIndexPage: NextPage = () => { const router = useRouter(); const { idpEntityID } = useIdpEntityID(); - + const isSettingsView = false; + const { paginate } = usePaginate(); + // The token value can be used to determine the value of setupLinkToken const { token } = router.query as { token: string }; + const getConnectionsUrl = token + ? `/api/setup/${token}/sso-connection` + : isSettingsView + ? `/api/admin/connections?isSystemSSO` + : `/api/admin/connections?pageOffset=${paginate.offset}&pageLimit=${token}`; + + function handleActionClick(e: any) { + console.log('hello world'); + } - return ; + return ( + + ); }; export async function getServerSideProps({ locale }) {