From 1177eb42cec4c268383b6dfc3bd71867f8c46f2e Mon Sep 17 00:00:00 2001 From: Manojava Koushik <111366021+manojava-gk@users.noreply.github.com> Date: Mon, 13 Jan 2025 15:38:57 +0530 Subject: [PATCH] fix(app release process): enhance add technical user process (#1401) --- src/assets/locales/de/main.json | 20 +- src/assets/locales/en/main.json | 20 +- .../OfferTechnicalIntegration/index.tsx | 157 ++++----- .../TechnicalIntegration/AddTechUserForm.tsx | 298 ++++++++++++++++++ .../TechnicalIntegration/TechUserTable.tsx | 79 +++++ .../TechnicalIntegration/index.tsx | 160 ++++------ .../TechnicalIntegration/style.scss | 26 ++ 7 files changed, 552 insertions(+), 208 deletions(-) create mode 100644 src/components/shared/basic/ReleaseProcess/TechnicalIntegration/AddTechUserForm.tsx create mode 100644 src/components/shared/basic/ReleaseProcess/TechnicalIntegration/TechUserTable.tsx create mode 100644 src/components/shared/basic/ReleaseProcess/TechnicalIntegration/style.scss diff --git a/src/assets/locales/de/main.json b/src/assets/locales/de/main.json index 12ff430a4..6f082744d 100644 --- a/src/assets/locales/de/main.json +++ b/src/assets/locales/de/main.json @@ -1363,7 +1363,22 @@ "roleUpdateError": "Error while updating roles", "encoding": "Encoding(select supported encoding)", "encodingPlaceholder": "Select supported encoding", - "noneOption": "Nicht notwendig (für die Integration ist kein technischer User notwendig)" + "noneOption": "Nicht notwendig (für die Integration ist kein technischer User notwendig)", + "table": { + "addtechUserButton": "Neuen technischen Benutzer hinzufügen", + "title": "Technical Users", + "type": "Type", + "role": "Role", + "noRoles": "Keine technischen Benutzer hinzugefügt" + }, + "form": { + "title": "Technischen Benutzer konfigurieren", + "intro": "Wählen Sie interne oder externe Rollen aus und fahren Sie mit der Konfiguration des technischen Benutzers fort", + "internalUserRoles": "Interner technischer Benutzer", + "internalUserRolesDescription": "Interne technische Benutzer werden in der direkten Umgebung des Portals erstellt und können daher mehrere Rollen haben", + "externalUserRoles": "Externer technischer Benutzer", + "externalUserRolesDescription": "Externe technische Benutzer werden in einer externen Umgebung erstellt und können daher nur eine Rolle haben" + } }, "betaTest": { "headerTitle": "CX Test Runs", @@ -2400,7 +2415,8 @@ "loading": "Loading...", "unsubscribe": "Unsubscribe", "edit": "Edit", - "copyInfoSuccessMessage": "Information erfolgreich kopiert" + "copyInfoSuccessMessage": "Information erfolgreich kopiert", + "continue": "Continue" }, "state": { "enabled": "aktiv", diff --git a/src/assets/locales/en/main.json b/src/assets/locales/en/main.json index 7af38098c..080fcbe7b 100644 --- a/src/assets/locales/en/main.json +++ b/src/assets/locales/en/main.json @@ -1332,7 +1332,22 @@ "roleUpdateError": "Error while updating roles", "encoding": "Encoding(select supported encoding)", "encodingPlaceholder": "Select supported encoding", - "noneOption": "none (no technical user needed to run the app/service)" + "noneOption": "none (no technical user needed to run the app/service)", + "table": { + "addtechUserButton": "Configure Technical User", + "title": "Technical Users", + "type": "Type", + "role": "Role", + "noRoles": "No technical users added" + }, + "form": { + "title": "Configure Technical User", + "intro": "Select internal or external roles and continue to configure technical user", + "internalUserRoles": "Internal technical user", + "internalUserRolesDescription": "Internal technical user are created in the direct environment of the portal therefore can have multiple roles", + "externalUserRoles": "External technical user", + "externalUserRolesDescription": "External technical user are created in external environment therefore can only have one role" + } }, "betaTest": { "headerTitle": "CX Test Runs", @@ -2375,7 +2390,8 @@ "loading": "Loading...", "unsubscribe": "Unsubscribe", "edit": "Edit", - "copyInfoSuccessMessage": "Information copied successfully" + "copyInfoSuccessMessage": "Information copied successfully", + "continue": "Continue" }, "state": { "enabled": "enabled", diff --git a/src/components/shared/basic/ReleaseProcess/OfferTechnicalIntegration/index.tsx b/src/components/shared/basic/ReleaseProcess/OfferTechnicalIntegration/index.tsx index 739ef6be5..d54891b4b 100644 --- a/src/components/shared/basic/ReleaseProcess/OfferTechnicalIntegration/index.tsx +++ b/src/components/shared/basic/ReleaseProcess/OfferTechnicalIntegration/index.tsx @@ -18,7 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import { Typography, Checkbox, Radio } from '@catena-x/portal-shared-components' +import { Typography } from '@catena-x/portal-shared-components' import { useTranslation } from 'react-i18next' import SnackbarNotificationWithButtons from '../components/SnackbarNotificationWithButtons' import { Grid } from '@mui/material' @@ -40,6 +40,8 @@ import { useForm } from 'react-hook-form' import { ButtonLabelTypes } from '..' import { setServiceStatus } from 'features/serviceManagement/actions' import { error, success } from 'services/NotifyService' +import { TechUserTable } from '../TechnicalIntegration/TechUserTable' +import { AddTechUserForm } from '../TechnicalIntegration/AddTechUserForm' export default function OfferTechnicalIntegration() { const { t } = useTranslation('servicerelease') @@ -88,36 +90,7 @@ export default function OfferTechnicalIntegration() { mode: 'onChange', }) - const handleUserProfiles = (item: string, checked: boolean) => { - if ( - serviceTechUserProfiles && - serviceTechUserProfiles[0] === serviceTechnicalUserNone - ) { - setServiceTechUserProfiles([...[], item]) - } else { - const isSelected = serviceTechUserProfiles?.includes(item) - let selectedProfiles: string[] = [] - if (!isSelected && checked) { - selectedProfiles = [...serviceTechUserProfiles, item] - } else if (isSelected && !checked) { - const oldTechUserProfiles = [...serviceTechUserProfiles] - oldTechUserProfiles.splice(oldTechUserProfiles.indexOf(item), 1) - selectedProfiles = [...oldTechUserProfiles] - } - setErrorMessage(selectedProfiles?.length === 0) - setServiceTechUserProfiles(selectedProfiles) - } - } - - const selectProfiles = (type: string, checked: boolean, item: string) => { - if (type === 'radio') { - setServiceTechUserProfiles([...[], item]) - } else if (type === 'checkbox') { - handleUserProfiles(item, checked) - } - } - - const onSubmit = async (_submitData: unknown, buttonLabel: string) => { + const onSubmit = (_submitData: unknown, buttonLabel: string) => { if ( !fetchServiceStatus?.serviceTypeIds.every((item) => [`${ServiceTypeIdsEnum.CONSULTANCY_SERVICE}`]?.includes(item) @@ -135,40 +108,6 @@ export default function OfferTechnicalIntegration() { ) { buttonLabel === ButtonLabelTypes.SAVE_AND_PROCEED && dispatch(serviceReleaseStepIncrement()) - } else if ( - !( - serviceTechUserProfiles.length === userProfiles.length && - serviceTechUserProfiles.every((item) => userProfiles?.includes(item)) - ) - ) { - setLoading(true) - const updateData = { - serviceId, - body: [ - { - technicalUserProfileId: data?.[0]?.technicalUserProfileId ?? null, - userRoleIds: - serviceTechUserProfiles && - serviceTechUserProfiles[0] === serviceTechnicalUserNone - ? [] - : serviceTechUserProfiles, - }, - ], - } - if (updateData) - await saveServiceTechnicalUserProfiles(updateData) - .unwrap() - .then(() => { - setErrorMessage(false) - refetch() - buttonLabel === ButtonLabelTypes.SAVE_AND_PROCEED - ? dispatch(serviceReleaseStepIncrement()) - : success(t('serviceReleaseForm.dataSavedSuccessMessage')) - }) - .catch((err) => { - error(t('technicalIntegration.technicalUserProfileError'), '', err) - }) - setLoading(false) } } @@ -177,6 +116,35 @@ export default function OfferTechnicalIntegration() { dispatch(serviceReleaseStepDecrement()) } + const [showTechUser, setShowTechUser] = useState(false) + + const handleUserProfiles = async (roles: string[]) => { + setShowTechUser(false) + setLoading(true) + const updateData = { + serviceId, + body: [ + { + technicalUserProfileId: data?.[0]?.technicalUserProfileId ?? null, + userRoleIds: + roles && roles[0] === serviceTechnicalUserNone ? [] : roles, + }, + ], + } + if (updateData) + await saveServiceTechnicalUserProfiles(updateData) + .unwrap() + .then(() => { + setErrorMessage(false) + refetch() + success(t('serviceReleaseForm.dataSavedSuccessMessage')) + }) + .catch((err) => { + error(t('technicalIntegration.technicalUserProfileError'), '', err) + }) + setLoading(false) + } + return ( <> @@ -191,40 +159,23 @@ export default function OfferTechnicalIntegration() {
- - {fetchServiceUserRoles?.map((item) => ( - - item.roleId === role - )} - onChange={(e) => { - selectProfiles('checkbox', e.target.checked, item.roleId) - }} - size="small" - /> - - ))} - - { - selectProfiles( - 'radio', - e.target.checked, - serviceTechnicalUserNone - ) - }} - /> - - + {data && ( + { + setShowTechUser(true) + }} + /> + )} + {fetchServiceUserRoles && showTechUser && data && ( + { + setShowTechUser(false) + }} + handleConfirm={handleUserProfiles} + /> + )} {errorMessage && ( @@ -245,10 +196,12 @@ export default function OfferTechnicalIntegration() { pageSnackbar={techIntegrationSnackbar} setPageSnackbar={setTechIntegrationSnackbar} onBackIconClick={onBackIconClick} - onSave={handleSubmit((data) => onSubmit(data, ButtonLabelTypes.SAVE))} - onSaveAndProceed={handleSubmit((data) => + onSave={handleSubmit((data) => { + onSubmit(data, ButtonLabelTypes.SAVE) + })} + onSaveAndProceed={handleSubmit((data) => { onSubmit(data, ButtonLabelTypes.SAVE_AND_PROCEED) - )} + })} isValid={serviceTechUserProfiles?.length > 0} loader={loading} helpUrl={ diff --git a/src/components/shared/basic/ReleaseProcess/TechnicalIntegration/AddTechUserForm.tsx b/src/components/shared/basic/ReleaseProcess/TechnicalIntegration/AddTechUserForm.tsx new file mode 100644 index 000000000..b62997470 --- /dev/null +++ b/src/components/shared/basic/ReleaseProcess/TechnicalIntegration/AddTechUserForm.tsx @@ -0,0 +1,298 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +import { + Button, + Checkbox, + Dialog, + DialogActions, + DialogContent, + DialogHeader, + Radio, + Typography, +} from '@catena-x/portal-shared-components' +import { Box } from '@mui/material' +import { + type ServiceAccountRole, + useFetchServiceAccountRolesQuery, +} from 'features/admin/serviceApiSlice' +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import './style.scss' + +interface AddTechUserFormProps { + handleClose: () => void + handleConfirm: (roles: string[]) => void + userProfiles: { + roleId: string + roleName: string + }[] +} + +enum RoleType { + Internal = 'Internal', + External = 'External', + NONE = 'NONE', +} + +export const AddTechUserForm = ({ + handleClose, + handleConfirm, + userProfiles, +}: AddTechUserFormProps) => { + const { t } = useTranslation() + const roles = useFetchServiceAccountRolesQuery().data + const internalUserRoles = roles?.filter( + (role) => role.roleType === RoleType.Internal + ) + const externalUserRoles = roles?.filter( + (role) => role.roleType === RoleType.External + ) + const [selectedUserRoles, setSelectedUserRoles] = useState([]) + const [selectedRoleType, setSelectedRoleType] = useState('') + + useEffect(() => { + if (userProfiles.length > 0) { + setSelectedUserRoles(() => { + const roles = [] + for (const obj of userProfiles) { + roles.push(obj.roleId) + } + return roles + }) + } else { + setSelectedUserRoles([]) + } + }, [userProfiles]) + + const selectCheckboxRoles = (role: string, select: boolean) => { + if ( + selectedUserRoles && + selectedUserRoles[0] === externalUserRoles?.[0].roleId + ) { + setSelectedUserRoles([...[], role]) + } else { + const isRoleSelected = selectedUserRoles?.includes(role) + if (!isRoleSelected && select) { + setSelectedUserRoles([...selectedUserRoles, role]) + } else if (isRoleSelected && !select) { + const oldUserRoles = [...selectedUserRoles] + oldUserRoles.splice(oldUserRoles.indexOf(role), 1) + setSelectedUserRoles([...oldUserRoles]) + } + } + } + + const selectRoles = (role: string, select: boolean, type: string) => { + if (type === 'checkbox') { + selectCheckboxRoles(role, select) + } else if (type === 'radio') { + setSelectedUserRoles([...[], role]) + } + } + + return ( +
+ + { + handleClose() + }} + /> + + + { + setSelectedRoleType(RoleType.External) + }} + name="radio-button" + value={selectedRoleType} + size="medium" + /> + + {t( + 'content.apprelease.technicalIntegration.form.externalUserRolesDescription' + )} + + {selectedRoleType && selectedRoleType !== RoleType.NONE && ( + + {externalUserRoles?.map((role: ServiceAccountRole) => ( + + + { + selectRoles(role.roleId, e.target.checked, 'radio') + }} + name="radio-buttons" + value={selectedUserRoles} + size="small" + disabled={selectedRoleType === RoleType.Internal} + /> + + + {role.roleDescription} + + + ))} + + )} + { + setSelectedRoleType(RoleType.Internal) + }} + name="radio-button" + value={selectedRoleType} + size="medium" + /> + + {t( + 'content.apprelease.technicalIntegration.form.internalUserRolesDescription' + )} + + {selectedRoleType && selectedRoleType !== RoleType.NONE && ( + + {internalUserRoles?.map((role: ServiceAccountRole) => ( + + + { + selectRoles(role.roleId, e.target.checked, 'checkbox') + }} + size="medium" + value={selectedUserRoles} + disabled={selectedRoleType === RoleType.External} + /> + + + {role.roleDescription} + + + ))} + + )} + { + setSelectedRoleType(RoleType.NONE) + }} + /> + + + + + + + +
+ ) +} diff --git a/src/components/shared/basic/ReleaseProcess/TechnicalIntegration/TechUserTable.tsx b/src/components/shared/basic/ReleaseProcess/TechnicalIntegration/TechUserTable.tsx new file mode 100644 index 000000000..2ec3f94bb --- /dev/null +++ b/src/components/shared/basic/ReleaseProcess/TechnicalIntegration/TechUserTable.tsx @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +import { t } from 'i18next' +import { Table } from '@catena-x/portal-shared-components' +import { type technicalUserProfiles } from 'features/appManagement/apiSlice' + +interface TechUserTableProps { + userProfiles: technicalUserProfiles[] + handleAddTechUser: () => void +} + +interface UserRoleType { + roleId: string + roleName: string + type: string +} + +export const TechUserTable = ({ + userProfiles, + handleAddTechUser, +}: TechUserTableProps) => { + const profiles = userProfiles?.[0]?.userRoles + return ( + row.roleId} + rows={profiles ?? []} + rowsCount={profiles?.length ?? 0} + onCellClick={() => {}} + columns={[ + { + field: 'type', + headerAlign: 'left', + align: 'left', + headerName: t('content.apprelease.technicalIntegration.table.type'), + flex: 1.5, + valueGetter: ({ row }: { row: UserRoleType }) => row.type, + }, + { + field: 'roleName', + headerAlign: 'left', + align: 'left', + headerName: t('content.apprelease.technicalIntegration.table.role'), + flex: 2, + valueGetter: ({ row }: { row: UserRoleType }) => row.roleName, + }, + ]} + disableColumnMenu + /> + ) +} diff --git a/src/components/shared/basic/ReleaseProcess/TechnicalIntegration/index.tsx b/src/components/shared/basic/ReleaseProcess/TechnicalIntegration/index.tsx index f90271490..132ec35e3 100644 --- a/src/components/shared/basic/ReleaseProcess/TechnicalIntegration/index.tsx +++ b/src/components/shared/basic/ReleaseProcess/TechnicalIntegration/index.tsx @@ -20,11 +20,9 @@ import { Button, - Checkbox, Chip, CustomAccordion, LoadingButton, - Radio, SelectList, Typography, } from '@catena-x/portal-shared-components' @@ -57,6 +55,8 @@ import SnackbarNotificationWithButtons from '../components/SnackbarNotificationW import { ErrorType } from 'features/appManagement/types' import { error, success } from 'services/NotifyService' import { ButtonLabelTypes } from '..' +import { TechUserTable } from './TechUserTable' +import { AddTechUserForm } from './AddTechUserForm' type RoleDesT = { desEN: string @@ -175,32 +175,6 @@ export default function TechnicalIntegration() { if (fetchAppStatus) dispatch(setAppStatus(fetchAppStatus)) }, [dispatch, fetchAppStatus]) - const handleCheckedUserProfiles = (item: string, select: boolean) => { - if (techUserProfiles && techUserProfiles[0] === technicalUserNone) { - setTechUserProfiles([...[], item]) - } else { - const isSelected = techUserProfiles?.includes(item) - let selectedProfiles: string[] = [] - if (!isSelected && select) { - selectedProfiles = [...techUserProfiles, item] - } else if (isSelected && !select) { - const oldTechUserProfiles = [...techUserProfiles] - oldTechUserProfiles.splice(oldTechUserProfiles.indexOf(item), 1) - selectedProfiles = [...oldTechUserProfiles] - } - setEnableUserProfilesErrorMessage(selectedProfiles?.length === 0) - setTechUserProfiles(selectedProfiles) - } - } - - const selectUserProfiles = (type: string, select: boolean, item: string) => { - if (type === 'checkbox') { - handleCheckedUserProfiles(item, select) - } else if (type === 'radio') { - setTechUserProfiles([...[], item]) - } - } - const handleSaveSuccess = (buttonLabel: string) => { setEnableUserProfilesErrorMessage(false) setEnableErrorMessage(false) @@ -218,10 +192,7 @@ export default function TechnicalIntegration() { ) } - const onIntegrationSubmit = async ( - _submitData: unknown, - buttonLabel: string - ) => { + const onIntegrationSubmit = (_submitData: unknown, buttonLabel: string) => { if ( buttonLabel === ButtonLabelTypes.SAVE && (data?.length === 0 || techUserProfiles.length === 0) @@ -230,43 +201,6 @@ export default function TechnicalIntegration() { techUserProfiles.length === 0 && setEnableUserProfilesErrorMessage(true) } else if (handleSaveAndProceed()) { buttonLabel === ButtonLabelTypes.SAVE_AND_PROCEED && dispatch(increment()) - } else if ( - !( - techUserProfiles.length === userProfiles.length && - techUserProfiles.every((item) => userProfiles?.includes(item)) - ) - ) { - setLoading(true) - const updateData = { - appId, - body: [ - { - technicalUserProfileId: - fetchTechnicalUserProfiles?.[0]?.technicalUserProfileId ?? null, - userRoleIds: - techUserProfiles && techUserProfiles[0] === technicalUserNone - ? [] - : techUserProfiles, - }, - ], - } - - if (updateData) - await saveTechnicalUserProfiles(updateData) - .unwrap() - .then(() => { - handleSaveSuccess(buttonLabel) - }) - .catch((err) => { - error( - t( - 'content.apprelease.technicalIntegration.technicalUserProfileError' - ), - '', - err - ) - }) - setLoading(false) } } @@ -395,6 +329,39 @@ export default function TechnicalIntegration() { dispatch(decrement()) } + const [showAddTechUser, setShowAddTechUser] = useState(false) + + const handletechUserProfiles = async (roles: string[]) => { + setShowAddTechUser(false) + setLoading(true) + setTechUserProfiles(roles) + const updateData = { + appId, + body: [ + { + technicalUserProfileId: + fetchTechnicalUserProfiles?.[0]?.technicalUserProfileId ?? null, + userRoleIds: roles && roles[0] === technicalUserNone ? [] : roles, + }, + ], + } + await saveTechnicalUserProfiles(updateData) + .unwrap() + .then(() => { + handleSaveSuccess(ButtonLabelTypes.SAVE) + }) + .catch((err) => { + error( + t( + 'content.apprelease.technicalIntegration.technicalUserProfileError' + ), + '', + err + ) + }) + setLoading(false) + } + return ( <> @@ -696,35 +663,24 @@ export default function TechnicalIntegration() { {t('content.apprelease.technicalIntegration.step2HeaderDescription')} - - {fetchUserRoles?.map((item) => ( - - item.roleId === role)} - onChange={(e) => { - selectUserProfiles('checkbox', e.target.checked, item.roleId) - }} - size="small" - /> - - ))} - - { - selectUserProfiles('radio', e.target.checked, technicalUserNone) - }} - /> - - + {fetchTechnicalUserProfiles && ( + { + setShowAddTechUser(true) + }} + /> + )} + {fetchUserRoles && showAddTechUser && fetchTechnicalUserProfiles && ( + { + setShowAddTechUser(false) + }} + handleConfirm={handletechUserProfiles} + /> + )} + {enableUserProfilesErrorMessage && ( {t( @@ -739,12 +695,12 @@ export default function TechnicalIntegration() { setPageNotification={setTechnicalIntegrationNotification} setPageSnackbar={setTechnicalIntegrationSnackbar} onBackIconClick={onBackIconClick} - onSave={handleSubmit((data) => + onSave={handleSubmit((data) => { onIntegrationSubmit(data, ButtonLabelTypes.SAVE) - )} - onSaveAndProceed={handleSubmit((data) => + })} + onSaveAndProceed={handleSubmit((data) => { onIntegrationSubmit(data, ButtonLabelTypes.SAVE_AND_PROCEED) - )} + })} pageNotificationsObject={{ title: t('content.apprelease.appReleaseForm.error.title'), description: t('content.apprelease.appReleaseForm.error.message'), diff --git a/src/components/shared/basic/ReleaseProcess/TechnicalIntegration/style.scss b/src/components/shared/basic/ReleaseProcess/TechnicalIntegration/style.scss new file mode 100644 index 000000000..c02e95464 --- /dev/null +++ b/src/components/shared/basic/ReleaseProcess/TechnicalIntegration/style.scss @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +.radio-container { + margin-top: 30px; + padding: 0px 120px 20px 120px; + label { + display: block; + } +}