Skip to content

Commit

Permalink
Use UI SDK (#2464)
Browse files Browse the repository at this point in the history
* Tweak sdk style import order

* WIP

* Override SDK styles

* Cleanup and pass props to component

* Cleanup setup link related code as it's handled via setup-link instructions

* Cleanup locale

* Fix e2e tests

* Fix selectors in e2e test

* Add select dropdown style override

* Use component from SDK

* Cleanup locale

* Use Edit DSync from SDK

* Remove default webhook props from setup token page

* Ability to set default webhook secret

* Tweak header text

* Revert sdk style import order - app styles should be latest

* Override default SDK focus style

* Update locale

* Use Edit component from SDK

* Allow patching oidcMetadata fields

* Tweak return data format

* Route change on edit success and other fixes

* Fix button styles

* Fix data access from API

* Fix focus styling for error btn

* Sync lock file

* Cleanup unused files

* Set `displayInfo` to false for setup link and fix exclude fields for SAML under setup link

* Allow forceAuthn in setup links

* Only update forceAuthn if its a boolean value coming from body

* Cleanup and hideSave only for setup link

* Update UI SDK

* Cleanup locales

* Fix failing e2e

* Reuse styles

* Set min value for expiry field to 1

* Validate expiry before using

* Update SDK and set idpMetadata display to true

---------

Co-authored-by: Deepak Prabhakara <[email protected]>
  • Loading branch information
niwsa and deepakprabhakara authored Mar 28, 2024
1 parent 7f28a6c commit 67f1117
Show file tree
Hide file tree
Showing 29 changed files with 304 additions and 1,528 deletions.
183 changes: 20 additions & 163 deletions components/connection/CreateConnection.tsx
Original file line number Diff line number Diff line change
@@ -1,187 +1,44 @@
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import {
saveConnection,
fieldCatalogFilterByConnection,
renderFieldList,
useFieldCatalog,
excludeFallback,
type AdminPortalSSODefaults,
type FormObj,
type FieldCatalogItem,
} from './utils';
import { mutate } from 'swr';
import { ApiResponse } from 'types';
import { errorToast } from '@components/Toaster';
import { useTranslation } from 'next-i18next';
import { ButtonPrimary } from '@components/ButtonPrimary';
import { LinkBack } from '@components/LinkBack';

function getInitialState(connectionType, fieldCatalog: FieldCatalogItem[]) {
const _state = {};

fieldCatalog.forEach(({ key, type, members, fallback, attributes: { connection } }) => {
let value;
if (connection && connection !== connectionType) {
return;
}
/** By default those fields which do not have a fallback.activateCondition will be excluded */
if (typeof fallback === 'object' && typeof fallback.activateCondition !== 'function') {
return;
}
if (type === 'object') {
value = getInitialState(connectionType, members as FieldCatalogItem[]);
}
_state[key] = value ? value : '';
});
return _state;
}
import { CreateSSOConnection } from '@boxyhq/react-ui/sso';
import { BOXYHQ_UI_CSS } from '@components/styles';
import { AdminPortalSSODefaults } from '@lib/utils';

const CreateConnection = ({
setupLinkToken,
isSettingsView = false,
adminPortalSSODefaults,
}: {
setupLinkToken?: string;
idpEntityID?: string;
isSettingsView?: boolean;
adminPortalSSODefaults?: AdminPortalSSODefaults;
}) => {
const fieldCatalog = useFieldCatalog({ isSettingsView });
const { t } = useTranslation('common');
const router = useRouter();
const [loading, setLoading] = useState(false);

// STATE: New connection type
const [newConnectionType, setNewConnectionType] = useState<'saml' | 'oidc'>('saml');

const handleNewConnectionTypeChange = (event) => {
setNewConnectionType(event.target.value);
};

const connectionIsSAML = newConnectionType === 'saml';
const connectionIsOIDC = newConnectionType === 'oidc';

const backUrl = setupLinkToken
? null
: isSettingsView
? '/admin/settings/sso-connection'
: '/admin/sso-connection';
const redirectUrl = setupLinkToken
? `/setup/${setupLinkToken}/sso-connection`
: isSettingsView
? '/admin/settings/sso-connection'
: '/admin/sso-connection';
const mutationUrl = setupLinkToken
? `/api/setup/${setupLinkToken}/sso-connection`
: isSettingsView
? '/api/admin/connections?isSystemSSO'
: '/api/admin/connections';

// FORM LOGIC: SUBMIT
const save = async (event: React.FormEvent) => {
event.preventDefault();

setLoading(true);

await saveConnection({
formObj: formObj,
connectionIsSAML: connectionIsSAML,
connectionIsOIDC: connectionIsOIDC,
setupLinkToken,
callback: async (rawResponse) => {
setLoading(false);

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

if ('error' in response) {
errorToast(response.error.message);
return;
}

if (rawResponse.ok) {
await mutate(mutationUrl);
router.replace(redirectUrl);
}
},
});
};

// STATE: FORM
const [formObj, setFormObj] = useState<FormObj>(() =>
isSettingsView
? { ...getInitialState(newConnectionType, fieldCatalog), ...adminPortalSSODefaults }
: { ...getInitialState(newConnectionType, fieldCatalog) }
);
// Resync form state on save
useEffect(() => {
const _state = getInitialState(newConnectionType, fieldCatalog);
setFormObj(isSettingsView ? { ..._state, ...adminPortalSSODefaults } : _state);
}, [newConnectionType, fieldCatalog, isSettingsView, adminPortalSSODefaults]);
const redirectUrl = isSettingsView ? '/admin/settings/sso-connection' : '/admin/sso-connection';

// HANDLER: Track fallback display
const activateFallback = (key, fallbackKey) => {
setFormObj((cur) => {
const temp = { ...cur };
delete temp[key];
const fallbackItem = fieldCatalog.find(({ key }) => key === fallbackKey);
const fallbackItemVal = fallbackItem?.type === 'object' ? {} : '';
return { ...temp, [fallbackKey]: fallbackItemVal };
});
};
const backUrl = redirectUrl;

return (
<>
{backUrl && <LinkBack href={backUrl} />}
<div>
<h2 className='mb-5 mt-5 font-bold text-gray-700 dark:text-white md:text-xl'>
{t('create_sso_connection')}
</h2>
<div className='mb-4 flex items-center'>
<div className='mr-2 py-3'>{t('select_sso_type')}:</div>
<div className='flex w-52'>
<div className='form-control'>
<label className='label mr-4 cursor-pointer'>
<input
type='radio'
name='connection'
value='saml'
className='radio-primary radio'
checked={newConnectionType === 'saml'}
onChange={handleNewConnectionTypeChange}
/>
<span className='label-text ml-1'>{t('saml')}</span>
</label>
</div>
<div className='form-control'>
<label className='label mr-4 cursor-pointer' data-testid='sso-type-oidc'>
<input
type='radio'
name='connection'
value='oidc'
className='radio-primary radio'
checked={newConnectionType === 'oidc'}
onChange={handleNewConnectionTypeChange}
/>
<span className='label-text ml-1'>{t('oidc')}</span>
</label>
</div>
</div>
</div>
<form onSubmit={save}>
<div className='min-w-[28rem] rounded border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-800'>
{fieldCatalog
.filter(fieldCatalogFilterByConnection(newConnectionType))
.filter(({ attributes: { hideInSetupView } }) => (setupLinkToken ? !hideInSetupView : true))
.filter(excludeFallback(formObj))
.map(renderFieldList({ formObj, setFormObj, activateFallback }))}
<div className='flex'>
<ButtonPrimary loading={loading} data-testid='submit-form-create-sso'>
{t('save_changes')}
</ButtonPrimary>
</div>
</div>
</form>
<h2 className='mb-8 mt-5 font-bold text-gray-700 dark:text-white md:text-xl'>
{t('create_sso_connection')}
</h2>
<div className='min-w-[28rem] rounded border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-800'>
<CreateSSOConnection
defaults={isSettingsView ? adminPortalSSODefaults : undefined}
variant={{ saml: 'advanced', oidc: 'advanced' }}
urls={{
post: '/api/admin/connections',
}}
excludeFields={{ saml: ['label'], oidc: ['label'] }}
successCallback={() => router.replace(redirectUrl)}
errorCallback={(errMessage) => errorToast(errMessage)}
classNames={BOXYHQ_UI_CSS}
/>
</div>
</>
);
Expand Down
Loading

0 comments on commit 67f1117

Please sign in to comment.