Merge pull request #17990 from overleaf/rd-button-links

[web] Migrating buttons to Bootstrap 5 on the Account Settings page

GitOrigin-RevId: c9dfa9b1dee50f4c0b30abf8ac464e53cf98db95
This commit is contained in:
Rebeka Dekany 2024-04-23 16:12:25 +02:00 committed by Copybot
parent 06f34c71bc
commit 898acab307
25 changed files with 277 additions and 146 deletions

View file

@ -124,7 +124,8 @@ function AccountInfoSection() {
type="submit"
variant="primary"
form="account-info-form"
isLoading={isLoading || !isFormValid}
disabled={!isFormValid}
isLoading={isLoading}
bs3Props={{
loading: isLoading ? `${t('saving')}` : t('update'),
}}

View file

@ -1,7 +1,8 @@
import { useTranslation, Trans } from 'react-i18next'
import { Modal, Button } from 'react-bootstrap'
import { Modal } from 'react-bootstrap'
import AccessibleModal from '../../../../../../shared/components/accessible-modal'
import { MergeAndOverride } from '../../../../../../../../types/utils'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
type ConfirmationModalProps = MergeAndOverride<
React.ComponentProps<typeof AccessibleModal>,
@ -40,22 +41,24 @@ function ConfirmationModal({
<p className="mb-0">{t('log_in_with_primary_email_address')}</p>
</Modal.Body>
<Modal.Footer>
<Button
bsStyle={null}
className="btn-secondary-info btn-secondary"
<ButtonWrapper
variant="secondary"
onClick={onHide}
bs3Props={{
bsStyle: null,
className: 'btn-secondary-info btn-secondary',
}}
>
{t('cancel')}
</Button>
<Button
type="button"
bsStyle={null}
className="btn-primary"
</ButtonWrapper>
<ButtonWrapper
variant="primary"
disabled={isConfirmDisabled}
onClick={onConfirm}
bs3Props={{ bsStyle: null, className: 'btn-primary' }}
>
{t('confirm')}
</Button>
</ButtonWrapper>
</Modal.Footer>
</AccessibleModal>
)

View file

@ -82,7 +82,7 @@ function MakePrimary({ userEmailData, makePrimaryAsync }: MakePrimaryProps) {
return (
<>
{makePrimaryAsync.isLoading ? (
<PrimaryButton disabled>
<PrimaryButton disabled isLoading={state.isLoading}>
{t('processing_uppercase')}&hellip;
</PrimaryButton>
) : (

View file

@ -1,19 +1,24 @@
import ButtonWrapper, {
ButtonWrapperProps,
} from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import { bsVersion } from '@/features/utils/bootstrap-5'
function PrimaryButton({ children, disabled, onClick }: ButtonWrapperProps) {
function PrimaryButton({
children,
disabled,
isLoading,
onClick,
}: ButtonWrapperProps) {
return (
<ButtonWrapper
size="small"
disabled={disabled}
disabled={disabled && !isLoading}
isLoading={isLoading}
onClick={onClick}
variant="secondary"
bs3Props={{ bsStyle: null }}
className={bsVersion({
bs3: 'btn-secondary btn-secondary-info',
})}
bs3Props={{
bsStyle: null,
className: 'btn-secondary btn-secondary-info',
}}
>
{children}
</ButtonWrapper>

View file

@ -214,7 +214,8 @@ function AddEmail() {
>
<AddNewEmailBtn
email={newEmail}
disabled={isLoading || state.isLoading}
disabled={state.isLoading}
isLoading={isLoading}
onClick={handleAddNewEmail}
/>
</Cell>

View file

@ -1,18 +1,20 @@
import { useTranslation } from 'react-i18next'
import { Button, ButtonProps } from 'react-bootstrap'
import ButtonWrapper, {
ButtonWrapperProps,
} from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
function AddAnotherEmailBtn({ onClick, ...props }: ButtonProps) {
function AddAnotherEmailBtn({ onClick, ...props }: ButtonWrapperProps) {
const { t } = useTranslation()
return (
<Button
className="btn-inline-link"
<ButtonWrapper
variant="link"
onClick={onClick}
{...props}
bsStyle={null}
bs3Props={{ bsStyle: null, className: 'btn-inline-link' }}
>
{t('add_another_email')}
</Button>
</ButtonWrapper>
)
}

View file

@ -1,5 +1,7 @@
import { useTranslation } from 'react-i18next'
import { Button, ButtonProps } from 'react-bootstrap'
import ButtonWrapper, {
ButtonWrapperProps,
} from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
const isValidEmail = (email: string) => {
return Boolean(email)
@ -7,20 +9,26 @@ const isValidEmail = (email: string) => {
type AddNewEmailColProps = {
email: string
} & ButtonProps
} & ButtonWrapperProps
function AddNewEmailBtn({ email, disabled, ...props }: AddNewEmailColProps) {
function AddNewEmailBtn({
email,
disabled,
isLoading,
...props
}: AddNewEmailColProps) {
const { t } = useTranslation()
return (
<Button
bsSize="small"
bsStyle="primary"
disabled={disabled || !isValidEmail(email)}
<ButtonWrapper
size="small"
variant="primary"
disabled={(disabled && !isLoading) || !isValidEmail(email)}
isLoading={isLoading}
{...props}
>
{t('add_new_email')}
</Button>
</ButtonWrapper>
)
}

View file

@ -1,20 +1,25 @@
import { useTranslation } from 'react-i18next'
import { Button, ButtonProps } from 'react-bootstrap'
import ButtonWrapper, {
ButtonWrapperProps,
} from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
function EmailAffiliatedWithInstitution({ onClick, ...props }: ButtonProps) {
function EmailAffiliatedWithInstitution({
onClick,
...props
}: ButtonWrapperProps) {
const { t } = useTranslation()
return (
<div className="mt-1">
{t('is_email_affiliated')}
<Button
className="btn-inline-link"
<ButtonWrapper
variant="link"
onClick={onClick}
bs3Props={{ bsStyle: null, className: 'btn-inline-link' }}
{...props}
bsStyle={null}
>
{t('let_us_know')}
</Button>
</ButtonWrapper>
</div>
)
}

View file

@ -1,10 +1,10 @@
import { useState } from 'react'
import { Button } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { DomainInfo } from './input'
import { ExposedSettings } from '../../../../../../../types/exposed-settings'
import getMeta from '../../../../../utils/meta'
import { useLocation } from '../../../../../shared/hooks/use-location'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
type SSOLinkingInfoProps = {
domainInfo: DomainInfo
@ -54,14 +54,15 @@ function SsoLinkingInfo({ domainInfo, email }: SSOLinkingInfoProps) {
{t('find_out_more_about_institution_login')}.
</a>
</p>
<Button
bsStyle="primary"
className="btn-sm btn-link-accounts"
<ButtonWrapper
variant="primary"
className="btn-link-accounts"
size="small"
disabled={linkAccountsButtonDisabled}
onClick={handleLinkAccountsButtonClick}
>
{t('link_accounts_and_add_email')}
</Button>
</ButtonWrapper>
</>
)
}

View file

@ -1,5 +1,5 @@
import { useTranslation } from 'react-i18next'
import { Button } from 'react-bootstrap'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
type UniversityNameProps = {
name: string
@ -14,9 +14,13 @@ function UniversityName({ name, onClick }: UniversityNameProps) {
{name}
<span className="small">
{' '}
<Button className="btn-inline-link" onClick={onClick} bsStyle={null}>
<ButtonWrapper
variant="link"
onClick={onClick}
bs3Props={{ bsStyle: null, className: 'btn-inline-link' }}
>
{t('change')}
</Button>
</ButtonWrapper>
</span>
</p>
)

View file

@ -1,7 +1,6 @@
import { useState, useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { UserEmailData } from '../../../../../../types/user-email'
import { Button } from 'react-bootstrap'
import { isChangingAffiliation } from '../../utils/selectors'
import { useUserEmailsContext } from '../../context/user-email-context'
import DownshiftInput from './downshift-input'
@ -10,6 +9,7 @@ import { getJSON, postJSON } from '../../../../infrastructure/fetch-json'
import defaultRoles from '../../data/roles'
import defaultDepartments from '../../data/departments'
import { University } from '../../../../../../types/university'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
type InstitutionAndRoleProps = {
userEmailData: UserEmailData
@ -107,11 +107,15 @@ function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) {
<br />
</>
)}
<Button className="btn-inline-link" onClick={handleChangeAffiliation}>
<ButtonWrapper
onClick={handleChangeAffiliation}
variant="link"
bs3Props={{ className: 'btn-inline-link' }}
>
{!affiliation.department && !affiliation.role
? t('add_role_and_department')
: t('change')}
</Button>
</ButtonWrapper>
</div>
) : (
<div className="affiliation-change-container small">
@ -135,23 +139,30 @@ function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) {
setValue={setDepartment}
/>
</div>
<Button
bsSize="small"
bsStyle="primary"
<ButtonWrapper
size="small"
variant="primary"
type="submit"
disabled={!role || !department || isLoading || state.isLoading}
disabled={!role || !department}
isLoading={isLoading}
bs3Props={{
loading: isLoading
? `${t('saving')}`
: t('save_or_cancel-save'),
}}
>
{isLoading ? <>{t('saving')}</> : t('save_or_cancel-save')}
</Button>
{t('save_or_cancel-save')}
</ButtonWrapper>
{!isLoading && (
<>
<span className="mx-1">{t('save_or_cancel-or')}</span>
<Button
className="btn-inline-link"
<ButtonWrapper
variant="link"
onClick={handleCancelAffiliationChange}
bs3Props={{ className: 'btn-inline-link' }}
>
{t('save_or_cancel-cancel')}
</Button>
</ButtonWrapper>
</>
)}
</form>

View file

@ -146,9 +146,13 @@ function ReconfirmationInfo({ userEmailData }: ReconfirmationInfoProps) {
</>
) : (
<ButtonWrapper
className="btn-inline-link"
variant="link"
disabled={state.isLoading}
onClick={handleRequestReconfirmation}
bs3Props={{
className: 'btn-inline-link',
bsStyle: null,
}}
>
{t('resend_confirmation_email')}
</ButtonWrapper>
@ -157,7 +161,8 @@ function ReconfirmationInfo({ userEmailData }: ReconfirmationInfoProps) {
) : (
<ButtonWrapper
variant="secondary"
disabled={state.isLoading || isPending}
disabled={isPending}
isLoading={isLoading}
onClick={handleRequestReconfirmation}
bs3Props={{ bsStyle: 'info' }}
>
@ -200,9 +205,10 @@ function ReconfirmationInfo({ userEmailData }: ReconfirmationInfoProps) {
</>
) : (
<ButtonWrapper
className="btn-inline-link"
variant="link"
disabled={state.isLoading}
onClick={handleRequestReconfirmation}
bs3Props={{ className: 'btn-inline-link', bsStyle: null }}
>
{t('resend_confirmation_email')}
</ButtonWrapper>

View file

@ -1,11 +1,11 @@
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import Icon from '../../../../shared/components/icon'
import { Button } from 'react-bootstrap'
import { FetchError, postJSON } from '../../../../infrastructure/fetch-json'
import useAsync from '../../../../shared/hooks/use-async'
import { UserEmailData } from '../../../../../../types/user-email'
import { useUserEmailsContext } from '../../context/user-email-context'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
type ResendConfirmationEmailButtonProps = {
email: UserEmailData['email']
@ -47,14 +47,14 @@ function ResendConfirmationEmailButton({
return (
<>
<Button
className="btn-inline-link"
disabled={state.isLoading}
<ButtonWrapper
variant="link"
disabled={state.isLoading || isLoading}
onClick={handleResendConfirmationEmail}
bsStyle={null}
bs3Props={{ bsStyle: null, className: 'btn-inline-link' }}
>
{t('resend_confirmation_email')}
</Button>
</ButtonWrapper>
<br />
{isError && (
<div className="text-danger">

View file

@ -1,7 +1,6 @@
import { useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { UserEmailData } from '../../../../../../types/user-email'
import { Button } from 'react-bootstrap'
import Email from './email'
import InstitutionAndRole from './institution-and-role'
import EmailCell from './cell'
@ -16,6 +15,7 @@ import { useLocation } from '../../../../shared/hooks/use-location'
import RowWrapper from '@/features/ui/components/bootstrap-5/wrappers/row-wrapper'
import ColWrapper from '@/features/ui/components/bootstrap-5/wrappers/col-wrapper'
import { bsVersion } from '@/features/utils/bootstrap-5'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
type EmailsRowProps = {
userEmailData: UserEmailData
@ -160,14 +160,15 @@ function SSOAffiliationInfo({ userEmailData }: SSOAffiliationInfoProps) {
})}
>
<EmailCell>
<Button
bsStyle="primary"
className="btn-sm btn-link-accounts"
<ButtonWrapper
variant="primary"
className="btn-link-accounts"
disabled={linkAccountsButtonDisabled}
onClick={handleLinkAccountsButtonClick}
size="small"
>
{t('link_accounts')}
</Button>
</ButtonWrapper>
</EmailCell>
</ColWrapper>
</RowWrapper>

View file

@ -2,6 +2,8 @@ import { useState, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import LeaveModal from './leave/modal'
import getMeta from '../../../utils/meta'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import { bsVersion } from '@/features/utils/bootstrap-5'
function LeaveSection() {
const { t } = useTranslation()
@ -28,9 +30,16 @@ function LeaveSection() {
return (
<>
{t('need_to_leave')}{' '}
<button className="btn btn-inline-link btn-danger" onClick={handleOpen}>
<ButtonWrapper
className={bsVersion({
bs3: 'btn btn-inline-link btn-danger',
bs5: 'btn-link',
})}
variant="danger"
onClick={handleOpen}
>
{t('delete_your_account')}
</button>
</ButtonWrapper>
<LeaveModal isOpen={isModalOpen} handleClose={handleClose} />
</>
)

View file

@ -1,9 +1,10 @@
import { useState, Dispatch, SetStateAction } from 'react'
import { Modal, Button } from 'react-bootstrap'
import { Modal } from 'react-bootstrap'
import { useTranslation, Trans } from 'react-i18next'
import getMeta from '../../../../utils/meta'
import LeaveModalForm, { LeaveModalFormProps } from './modal-form'
import { ExposedSettings } from '../../../../../../types/exposed-settings'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
type LeaveModalContentProps = {
handleHide: () => void
@ -68,24 +69,23 @@ function LeaveModalContent({
</Modal.Body>
<Modal.Footer>
<Button
type="button"
<ButtonWrapper
disabled={inFlight}
onClick={handleHide}
bsStyle={null}
className="btn-secondary"
variant="secondary"
bs3Props={{ bsStyle: null, className: 'btn-secondary' }}
>
{t('cancel')}
</Button>
</ButtonWrapper>
<Button
<ButtonWrapper
form="leave-form"
type="submit"
bsStyle="danger"
variant="danger"
disabled={inFlight || !isFormValid}
>
{inFlight ? <>{t('deleting')}</> : t('delete')}
</Button>
</ButtonWrapper>
</Modal.Footer>
</>
)

View file

@ -1,8 +1,8 @@
import { ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import { Button } from 'react-bootstrap'
import { sendMB } from '@/infrastructure/event-tracking'
import BadgeWrapper from '@/features/ui/components/bootstrap-5/wrappers/badge-wrapper'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
function trackUpgradeClick() {
sendMB('settings-upgrade-click')
@ -92,36 +92,39 @@ function ActionButton({
const { t } = useTranslation()
if (!hasFeature) {
return (
<Button
bsStyle={null}
className="btn-primary"
<ButtonWrapper
variant="primary"
href="/user/subscription/plans"
onClick={trackUpgradeClick}
bs3Props={{ bsStyle: null, className: 'btn-primary' }}
>
<span className="text-capitalize">{t('upgrade')}</span>
</Button>
</ButtonWrapper>
)
} else if (linked) {
return (
<Button
className="btn-danger-ghost"
<ButtonWrapper
variant="danger-ghost"
onClick={handleUnlinkClick}
bsStyle={null}
disabled={disabled}
bs3Props={{ bsStyle: null, className: 'btn-danger-ghost' }}
>
{t('turn_off')}
</Button>
</ButtonWrapper>
)
} else {
return (
<Button
<ButtonWrapper
variant="secondary"
disabled={disabled}
bsStyle={null}
onClick={handleLinkClick}
className="btn btn-secondary-info btn-secondary text-capitalize"
bs3Props={{
bsStyle: null,
className: 'btn btn-secondary-info btn-secondary',
}}
>
{t('turn_on')}
</Button>
</ButtonWrapper>
)
}
}

View file

@ -1,10 +1,12 @@
import { useCallback, useState, ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import AccessibleModal from '../../../../shared/components/accessible-modal'
import { Modal } from 'react-bootstrap'
import BadgeWrapper from '@/features/ui/components/bootstrap-5/wrappers/badge-wrapper'
import { Button, Modal } from 'react-bootstrap'
import getMeta from '../../../../utils/meta'
import { sendMB } from '../../../../infrastructure/event-tracking'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import { bsVersion } from '@/features/utils/bootstrap-5'
function trackUpgradeClick() {
sendMB('settings-upgrade-click')
@ -107,43 +109,52 @@ function ActionButton({
const { t } = useTranslation()
if (!hasFeature) {
return (
<Button
bsStyle={null}
className="btn-primary"
<ButtonWrapper
variant="primary"
href="/user/subscription/plans"
onClick={trackUpgradeClick}
bs3Props={{ bsStyle: null, className: 'btn-primary' }}
>
<span className="text-capitalize">{t('upgrade')}</span>
</Button>
</ButtonWrapper>
)
} else if (linked) {
return (
<Button
className="btn-danger-ghost"
<ButtonWrapper
variant="danger-ghost"
onClick={handleUnlinkClick}
bsStyle={null}
disabled={disabled}
bs3Props={{ bsStyle: null, className: 'btn-danger-ghost' }}
>
{t('unlink')}
</Button>
</ButtonWrapper>
)
} else {
return (
<>
{disabled ? (
<button
<ButtonWrapper
disabled
className="btn btn-secondary-info btn-secondary text-capitalize"
variant="secondary"
className={bsVersion({
bs3: 'btn btn-secondary-info btn-secondary text-capitalize',
bs5: 'text-capitalize',
})}
>
{t('link')}
</button>
</ButtonWrapper>
) : (
<a
className="btn btn-secondary-info btn-secondary text-capitalize"
<ButtonWrapper
variant="secondary"
href={linkPath}
className={bsVersion({
bs3: 'btn btn-secondary-info btn-secondary text-capitalize',
bs5: 'text-capitalize',
})}
bs3Props={{ bsStyle: null }}
>
{t('link')}
</a>
</ButtonWrapper>
)}
</>
)
@ -167,9 +178,7 @@ function UnlinkConfirmationModal({
}: UnlinkConfirmModalProps) {
const { t } = useTranslation()
const handleCancel = (
event: React.MouseEvent<HTMLButtonElement & Button>
) => {
const handleCancel = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault()
handleHide()
}
@ -186,15 +195,23 @@ function UnlinkConfirmationModal({
<Modal.Footer>
<form action={unlinkPath} method="POST" className="form-inline">
<input type="hidden" name="_csrf" value={getMeta('ol-csrfToken')} />
<Button
className="btn-secondary-info btn-secondary"
<ButtonWrapper
variant="secondary"
onClick={handleCancel}
bs3Props={{
bsStyle: null,
className: 'btn-secondary-info btn-secondary',
}}
>
{t('cancel')}
</Button>
<Button type="submit" className="btn-danger-ghost" bsStyle={null}>
</ButtonWrapper>
<ButtonWrapper
type="submit"
variant="danger-ghost"
bs3Props={{ bsStyle: null, className: 'btn-danger-ghost' }}
>
{t('unlink')}
</Button>
</ButtonWrapper>
</form>
</Modal.Footer>
</AccessibleModal>

View file

@ -1,12 +1,14 @@
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Button, Modal } from 'react-bootstrap'
import { Modal } from 'react-bootstrap'
import { FetchError } from '../../../../infrastructure/fetch-json'
import AccessibleModal from '../../../../shared/components/accessible-modal'
import IEEELogo from '../../../../shared/svgs/ieee-logo'
import GoogleLogo from '../../../../shared/svgs/google-logo'
import OrcidLogo from '../../../../shared/svgs/orcid-logo'
import LinkingStatus from './status'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import { bsVersion } from '@/features/utils/bootstrap-5'
const providerLogos: { readonly [p: string]: JSX.Element } = {
collabratec: <IEEELogo />,
@ -112,28 +114,37 @@ function ActionButton({
const { t } = useTranslation()
if (unlinkRequestInflight) {
return (
<Button className="btn-danger-ghost" bsStyle={null} disabled>
<ButtonWrapper
variant="danger-ghost"
disabled
bs3Props={{ bsStyle: null, className: 'btn-danger-ghost' }}
>
{t('unlinking')}
</Button>
</ButtonWrapper>
)
} else if (accountIsLinked) {
return (
<Button
className="btn-danger-ghost"
bsStyle={null}
<ButtonWrapper
variant="danger-ghost"
onClick={onUnlinkClick}
bs3Props={{ bsStyle: null, className: 'btn-danger-ghost' }}
>
{t('unlink')}
</Button>
</ButtonWrapper>
)
} else {
return (
<a
<ButtonWrapper
variant="secondary"
href={linkPath}
className="btn btn-secondary-info btn-secondary text-capitalize"
bs3Props={{ bsStyle: null }}
className={bsVersion({
bs3: 'btn btn-secondary-info btn-secondary text-capitalize',
bs5: 'text-capitalize',
})}
>
{t('link')}
</a>
</ButtonWrapper>
)
}
}
@ -166,20 +177,23 @@ function UnlinkConfirmModal({
</Modal.Body>
<Modal.Footer>
<Button
bsStyle={null}
className="btn-secondary-info btn-secondary"
<ButtonWrapper
variant="secondary"
onClick={handleHide}
bs3Props={{
bsStyle: null,
className: 'btn-secondary-info btn-secondary',
}}
>
{t('cancel')}
</Button>
<Button
className="btn-danger-ghost"
bsStyle={null}
</ButtonWrapper>
<ButtonWrapper
variant="danger-ghost"
onClick={handleConfirmation}
bs3Props={{ bsStyle: null, className: 'btn-danger-ghost' }}
>
{t('unlink')}
</Button>
</ButtonWrapper>
</Modal.Footer>
</AccessibleModal>
)

View file

@ -201,7 +201,8 @@ function PasswordForm() {
form="password-change-form"
type="submit"
variant="primary"
disabled={isLoading || !isFormValid}
disabled={!isFormValid}
isLoading={isLoading}
bs3Props={{
loading: isLoading ? `${t('saving')}` : t('change'),
}}

View file

@ -2,6 +2,7 @@ import MaterialIcon from '@/shared/components/material-icon'
import { Trans, useTranslation } from 'react-i18next'
import { GroupSSOLinkingStatus } from '../../../../../types/subscription/sso'
import getMeta from '../../../utils/meta'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
function SecuritySection() {
const { t } = useTranslation()
@ -84,12 +85,13 @@ function SecuritySection() {
</div>
{linked ? null : (
<div className="button-column">
<a
className="btn btn-primary"
<ButtonWrapper
variant="primary"
bs3Props={{ className: 'btn btn-primary', bsStyle: null }}
href={`/subscription/${groupId}/sso_enrollment`}
>
{t('set_up_sso')}
</a>
</ButtonWrapper>
</div>
)}
</div>

View file

@ -7,6 +7,7 @@ import Button from '../button'
export type ButtonWrapperProps = ButtonProps & {
bs3Props?: {
bsStyle?: string | null
className?: string
loading?: React.ReactNode
}
}
@ -26,11 +27,12 @@ export default function ButtonWrapper(props: ButtonWrapperProps) {
const { bs3Props, ...rest } = props
const bs3ButtonProps: BS3ButtonProps = {
bsStyle: rest.variant,
bsStyle: rest.variant === 'secondary' ? 'default' : rest.variant,
bsSize: mapBsButtonSizes(rest.size),
className: rest.className,
disabled: rest.isLoading || rest.disabled,
form: rest.form,
href: rest.href,
onClick: rest.onClick,
type: rest.type,
...bs3Props,

View file

@ -19,4 +19,5 @@ export type ButtonProps = {
| 'danger'
| 'danger-ghost'
| 'premium'
| 'link'
}

View file

@ -2,12 +2,12 @@
// Use CSS variables for link colors to make it easy to override in marketing page
:root {
--link-color: var(--link-ui-visited);
--link-color: var(--link-ui);
--link-hover-color: var(--link-ui-hover);
--link-visited-color: var(--link-ui-visited);
}
a {
a:not([role='button']) {
color: var(--link-color);
&:hover {

View file

@ -87,10 +87,44 @@
}
}
// Link buttons
// -------------------------
// Make a button look and behave like a link
.btn-link {
color: var(--link-ui);
font-weight: normal;
cursor: pointer;
border-radius: 0;
text-decoration: underline;
padding: 0;
font-size: inherit;
vertical-align: inherit;
&,
&:active,
&[disabled],
fieldset[disabled] & {
background-color: transparent;
@include box-shadow(none);
}
&:hover,
&:focus {
color: var(--link-ui-hover);
text-decoration: none;
background-color: transparent;
}
&.btn-danger {
color: var(--content-danger);
}
}
.button-loading {
align-items: center;
display: inline-grid;
grid-template-areas: 'container'; // Define a single grid area
pointer-events: none;
}
.button-loading > * {