Skip to content
Merged
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
10 changes: 9 additions & 1 deletion new-ui/src/pages/playground/PlaygroundIndex.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@ import './style.scss';
import type { ComponentType } from 'react';
import { Tabs } from '../../shared/components/Tabs/Tabs';
import { FullPage } from '../../shared/layouts/FullPage/FullPage';
import { PlaygroundTestFactorSelector } from './components/PlaygroundTestFactorSelector/PlaygroundTestFactorSelector';
import { PlaygroundTestLoaderSpinner } from './components/PlaygroundTestLoaderSpinner/PlaygroundTestLoaderSpinner';
import { PlaygroundTestMfaSelector } from './components/PlaygroundTestMfaSelector/PlaygroundTestMfaSelector';
import { PlaygroundTestSelectionItem } from './components/PlaygroundTestSelectionItem/PlaygroundTestSelectionItem';

const tests: ComponentType[] = [PlaygroundTestLoaderSpinner];
const tests: ComponentType[] = [
PlaygroundTestMfaSelector,
PlaygroundTestFactorSelector,
PlaygroundTestSelectionItem,
PlaygroundTestLoaderSpinner,
];

export const PlaygroundIndex = () => {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import './style.scss';
import type { PropsWithChildren } from 'react';

type Props = PropsWithChildren<{
title: string;
}>;

export const PlaygroundGroup = ({ title, children }: Props) => {
return (
<div className="playground-group">
<p className="title">{title}</p>
{children}
</div>
);
};
11 changes: 11 additions & 0 deletions new-ui/src/pages/playground/components/PlaygroundGroup/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.playground-group {
display: flex;
flex-flow: column;
gap: var(--spacing-md);
width: 100%;

& > .title {
font: var(--t-body-xs-500);
color: var(--fg-white-100);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import './style.scss';
import { useState } from 'react';
import { FactorSelector } from '../../../../shared/components/FactorSelector/FactorSelector';
import { SelectionItemVariant } from '../../../../shared/components/SelectionItem/types';
import { MfaMethod, type MfaMethodValue } from '../../../../shared/rust-api/types';
import { PlaygroundCard } from '../PlaygroundCard/PlaygroundCard';
import { PlaygroundGroup } from '../PlaygroundGroup/PlaygroundGroup';

const availableMethods: MfaMethodValue[] = [
MfaMethod.Totp,
MfaMethod.Email,
MfaMethod.Fido2,
];

export const PlaygroundTestFactorSelector = () => {
const [selected, setSelected] = useState<MfaMethodValue[]>([MfaMethod.Totp]);

const toggle = (method: MfaMethodValue) => {
setSelected((current) =>
current.includes(method)
? current.filter((picked) => picked !== method)
: [...current, method],
);
};

return (
<PlaygroundCard>
<div className="playground-test-factor-selector">
<h3>FactorSelector</h3>
<PlaygroundGroup title="Available methods">
{availableMethods.map((method) => (
<FactorSelector
key={method}
method={method}
variant={SelectionItemVariant.Checkbox}
selected={selected.includes(method)}
onClick={() => {
toggle(method);
}}
/>
))}
</PlaygroundGroup>
<PlaygroundGroup title="Not configurable">
<FactorSelector
method={MfaMethod.Oidc}
variant={SelectionItemVariant.Checkbox}
badge="Configured"
selected={false}
disabled
/>
<FactorSelector
method={MfaMethod.MobileApprove}
variant={SelectionItemVariant.Checkbox}
badge="Not configurable"
selected={false}
disabled
/>
</PlaygroundGroup>
</div>
</PlaygroundCard>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.playground-test-factor-selector {
display: flex;
flex-flow: column;
gap: var(--spacing-xl);
width: 100%;

h3 {
margin: 0;
font: var(--t-body-sm-600);
text-align: center;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import './style.scss';
import { useState } from 'react';
import { MfaSelector } from '../../../../shared/components/LocationCard/components/MfaSelector/MfaSelector';
import { MfaMethod, type MfaMethodValue } from '../../../../shared/rust-api/types';
import { PlaygroundCard } from '../PlaygroundCard/PlaygroundCard';
import { PlaygroundGroup } from '../PlaygroundGroup/PlaygroundGroup';

const pickMethods: MfaMethodValue[] = [MfaMethod.Totp, MfaMethod.Email];
const configurableMethods: MfaMethodValue[] = [MfaMethod.MobileApprove, MfaMethod.Fido2];

export const PlaygroundTestMfaSelector = () => {
const [active, setActive] = useState<MfaMethodValue>(MfaMethod.Totp);
const [queued, setQueued] = useState<MfaMethodValue[]>([]);

const toggleQueued = (method: MfaMethodValue) => {
setQueued((current) =>
current.includes(method)
? current.filter((picked) => picked !== method)
: [...current, method],
);
};

return (
<PlaygroundCard>
<div className="playground-test-mfa-selector">
<h3>MfaSelector</h3>
<PlaygroundGroup title="Configured">
{pickMethods.map((method) => (
<MfaSelector
key={method}
factor={method}
active={active === method}
isDefault={method === MfaMethod.Totp}
isSelectable
onClick={() => {
setActive(method);
}}
/>
))}
</PlaygroundGroup>
<PlaygroundGroup title="Can be configured">
{configurableMethods.map((method) => (
<MfaSelector
key={method}
factor={method}
configured={false}
configurable
selected={queued.includes(method)}
isSelectable
onClick={() => {
toggleQueued(method);
}}
/>
))}
</PlaygroundGroup>
<PlaygroundGroup title="Not selectable">
<MfaSelector
factor={MfaMethod.Biometric}
configured={false}
isSelectable={false}
/>
</PlaygroundGroup>
</div>
</PlaygroundCard>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.playground-test-mfa-selector {
display: flex;
flex-flow: column;
gap: var(--spacing-xl);
width: 100%;

h3 {
margin: 0;
font: var(--t-body-sm-600);
text-align: center;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import './style.scss';
import { useState } from 'react';
import { IconKind } from '../../../../shared/components/Icon';
import { SelectionItem } from '../../../../shared/components/SelectionItem/SelectionItem';
import { SelectionItemVariant } from '../../../../shared/components/SelectionItem/types';
import { PlaygroundCard } from '../PlaygroundCard/PlaygroundCard';
import { PlaygroundGroup } from '../PlaygroundGroup/PlaygroundGroup';

const radioItems = ['defguard.example.com', 'vpn.example.org'];
const checkboxItems = ['Office network', 'Staging cluster'];

export const PlaygroundTestSelectionItem = () => {
const [radioValue, setRadioValue] = useState(radioItems[0]);
const [checked, setChecked] = useState<string[]>([checkboxItems[0]]);

const toggleChecked = (item: string) => {
setChecked((current) =>
current.includes(item)
? current.filter((picked) => picked !== item)
: [...current, item],
);
};

return (
<PlaygroundCard>
<div className="playground-test-selection-item">
<h3>SelectionItem</h3>
<PlaygroundGroup title="Radio variant">
{radioItems.map((item) => (
<SelectionItem
key={item}
text={item}
icon={IconKind.Globe}
selected={radioValue === item}
onClick={() => {
setRadioValue(item);
}}
/>
))}
<SelectionItem
text="legacy.example.net"
icon={IconKind.Globe}
badge="Unsupported"
selected={false}
disabled
/>
</PlaygroundGroup>
<PlaygroundGroup title="Checkbox variant">
{checkboxItems.map((item) => (
<SelectionItem
key={item}
text={item}
icon={IconKind.Globe}
variant={SelectionItemVariant.Checkbox}
selected={checked.includes(item)}
onClick={() => {
toggleChecked(item);
}}
/>
))}
<SelectionItem
text="Production cluster"
icon={IconKind.Globe}
variant={SelectionItemVariant.Checkbox}
badge="Locked"
selected
disabled
/>
</PlaygroundGroup>
</div>
</PlaygroundCard>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.playground-test-selection-item {
display: flex;
flex-flow: column;
gap: var(--spacing-xl);
width: 100%;

h3 {
margin: 0;
font: var(--t-body-sm-600);
text-align: center;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
type MfaMethodValue,
} from '../../../../rust-api/types';
import { mfaToText, openIdProviderName } from '../../../../utils/mfa';
import { CheckboxIndicator } from '../../../CheckboxIndicator/CheckboxIndicator';
import { Icon } from '../../../Icon';
import checkboxSrc from './assets/checkbox.svg';

Expand All @@ -18,6 +19,7 @@ interface Props {
active?: boolean;
isDefault?: boolean;
configured?: boolean;
configurable?: boolean;
isSelectable: boolean;
onClick?: MouseEventHandler<HTMLDivElement>;
containerProps?: Omit<HTMLProps<HTMLDivElement>, 'onClick'>;
Expand All @@ -32,10 +34,12 @@ export const MfaSelector = ({
active = false,
isDefault = false,
configured = true,
configurable = false,
isSelectable,
}: Props) => {
const isMobileOnly = factor === MfaMethod.Biometric;
const showCheckbox = isSelectable && selected;
const showHoverCheckbox = isSelectable && configurable && !selected;

return (
<div
Expand All @@ -46,6 +50,7 @@ export const MfaSelector = ({
selected,
active,
disabled: !isSelectable,
'hover-checkbox': showHoverCheckbox,
})}
onClick={(event) => {
if (isSelectable) {
Expand All @@ -59,6 +64,7 @@ export const MfaSelector = ({
<Icon className="factor-icon" icon={mfaMethodIcon[factor]} size={20} />
</div>
)}
{showHoverCheckbox && <CheckboxIndicator active={false} size="sm" />}
<div className="middle">
<p className="name">
{factor === MfaMethod.Oidc
Expand All @@ -67,9 +73,10 @@ export const MfaSelector = ({
</p>
</div>
<div className="right">
{(isMobileOnly || !configured) && (
<p className="disabled-label">
{isMobileOnly ? 'Mobile client only' : 'Not configured'}
{isMobileOnly && <p className="disabled-label">Mobile client only</p>}
{!isMobileOnly && !configured && (
<p className={clsx('disabled-label', { configurable })}>
{configurable ? 'Select to configure' : 'Not configured'}
</p>
)}
{isSelectable && configured && isDefault && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,22 @@
}
}

&.hover-checkbox {
& > .checkbox-indicator {
display: none;
}

&:hover {
& > .icon-col {
display: none;
}

& > .checkbox-indicator {
display: block;
}
}
}

> .middle {
display: flex;
flex-flow: row nowrap;
Expand Down Expand Up @@ -101,6 +117,10 @@
color: var(--fg-white-40);
font: var(--t-body-sm-400);
white-space: nowrap;

&.configurable {
font: var(--t-body-xxs-400);
}
}

.factor-icon {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const MfaSettingsSection = ({
action === MfaFactorAction.Configure &&
configureMethods.some((queued) => queued === method)
}
configurable={action === MfaFactorAction.Configure}
isSelectable={action !== MfaFactorAction.None}
onClick={() => {
if (action === MfaFactorAction.Pick) {
Expand Down
Loading