feat: Authentication provider display (#4332)
* layout * Refactor * wip * Quick changes to make this deployable without full management * test
This commit is contained in:
155
app/scenes/Settings/components/DomainManagement.tsx
Normal file
155
app/scenes/Settings/components/DomainManagement.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { CloseIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import Button from "~/components/Button";
|
||||
import Fade from "~/components/Fade";
|
||||
import Flex from "~/components/Flex";
|
||||
import Input from "~/components/Input";
|
||||
import NudeButton from "~/components/NudeButton";
|
||||
import Tooltip from "~/components/Tooltip";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useToasts from "~/hooks/useToasts";
|
||||
import SettingRow from "./SettingRow";
|
||||
|
||||
type Props = {
|
||||
onSuccess: () => void;
|
||||
};
|
||||
|
||||
function DomainManagement({ onSuccess }: Props) {
|
||||
const { auth } = useStores();
|
||||
const team = useCurrentTeam();
|
||||
const { t } = useTranslation();
|
||||
const { showToast } = useToasts();
|
||||
|
||||
const [allowedDomains, setAllowedDomains] = React.useState([
|
||||
...(team.allowedDomains ?? []),
|
||||
]);
|
||||
const [lastKnownDomainCount, updateLastKnownDomainCount] = React.useState(
|
||||
allowedDomains.length
|
||||
);
|
||||
|
||||
const [existingDomainsTouched, setExistingDomainsTouched] = React.useState(
|
||||
false
|
||||
);
|
||||
|
||||
const handleSaveDomains = React.useCallback(async () => {
|
||||
try {
|
||||
await auth.updateTeam({
|
||||
allowedDomains,
|
||||
});
|
||||
onSuccess();
|
||||
setExistingDomainsTouched(false);
|
||||
updateLastKnownDomainCount(allowedDomains.length);
|
||||
} catch (err) {
|
||||
showToast(err.message, {
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}, [auth, allowedDomains, onSuccess, showToast]);
|
||||
|
||||
const handleRemoveDomain = async (index: number) => {
|
||||
const newDomains = allowedDomains.filter((_, i) => index !== i);
|
||||
|
||||
setAllowedDomains(newDomains);
|
||||
|
||||
const touchedExistingDomain = index < lastKnownDomainCount;
|
||||
if (touchedExistingDomain) {
|
||||
setExistingDomainsTouched(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddDomain = () => {
|
||||
const newDomains = [...allowedDomains, ""];
|
||||
|
||||
setAllowedDomains(newDomains);
|
||||
};
|
||||
|
||||
const createOnDomainChangedHandler = (index: number) => (
|
||||
ev: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const newDomains = allowedDomains.slice();
|
||||
|
||||
newDomains[index] = ev.currentTarget.value;
|
||||
setAllowedDomains(newDomains);
|
||||
|
||||
const touchedExistingDomain = index < lastKnownDomainCount;
|
||||
if (touchedExistingDomain) {
|
||||
setExistingDomainsTouched(true);
|
||||
}
|
||||
};
|
||||
|
||||
const showSaveChanges =
|
||||
existingDomainsTouched ||
|
||||
allowedDomains.filter((value: string) => value !== "").length > // New domains were added
|
||||
lastKnownDomainCount;
|
||||
|
||||
return (
|
||||
<SettingRow
|
||||
label={t("Allowed domains")}
|
||||
name="allowedDomains"
|
||||
description={t(
|
||||
"The domains which should be allowed to create new accounts using SSO. Changing this setting does not affect existing user accounts."
|
||||
)}
|
||||
>
|
||||
{allowedDomains.map((domain, index) => (
|
||||
<Flex key={index} gap={4}>
|
||||
<Input
|
||||
key={index}
|
||||
id={`allowedDomains${index}`}
|
||||
value={domain}
|
||||
autoFocus={!domain}
|
||||
placeholder="example.com"
|
||||
required
|
||||
flex
|
||||
onChange={createOnDomainChangedHandler(index)}
|
||||
/>
|
||||
<Remove>
|
||||
<Tooltip tooltip={t("Remove domain")} placement="top">
|
||||
<NudeButton onClick={() => handleRemoveDomain(index)}>
|
||||
<CloseIcon />
|
||||
</NudeButton>
|
||||
</Tooltip>
|
||||
</Remove>
|
||||
</Flex>
|
||||
))}
|
||||
|
||||
<Flex justify="space-between" gap={4} style={{ flexWrap: "wrap" }}>
|
||||
{!allowedDomains.length ||
|
||||
allowedDomains[allowedDomains.length - 1] !== "" ? (
|
||||
<Fade>
|
||||
<Button type="button" onClick={handleAddDomain} neutral>
|
||||
{allowedDomains.length ? (
|
||||
<Trans>Add another</Trans>
|
||||
) : (
|
||||
<Trans>Add a domain</Trans>
|
||||
)}
|
||||
</Button>
|
||||
</Fade>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
|
||||
{showSaveChanges && (
|
||||
<Fade>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleSaveDomains}
|
||||
disabled={auth.isSaving}
|
||||
>
|
||||
<Trans>Save changes</Trans>
|
||||
</Button>
|
||||
</Fade>
|
||||
)}
|
||||
</Flex>
|
||||
</SettingRow>
|
||||
);
|
||||
}
|
||||
|
||||
const Remove = styled("div")`
|
||||
margin-top: 6px;
|
||||
`;
|
||||
|
||||
export default observer(DomainManagement);
|
||||
@@ -7,7 +7,7 @@ import Text from "~/components/Text";
|
||||
|
||||
type Props = {
|
||||
label: React.ReactNode;
|
||||
description: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
name: string;
|
||||
visible?: boolean;
|
||||
border?: boolean;
|
||||
@@ -15,7 +15,7 @@ type Props = {
|
||||
|
||||
const Row = styled(Flex)<{ $border?: boolean }>`
|
||||
display: block;
|
||||
padding: 24px 0;
|
||||
padding: 22px 0;
|
||||
border-bottom: 1px solid
|
||||
${(props) =>
|
||||
props.$border === false
|
||||
@@ -38,7 +38,7 @@ const Column = styled.div`
|
||||
flex: 1;
|
||||
|
||||
&:first-child {
|
||||
min-width: 60%;
|
||||
min-width: 70%;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
@@ -73,7 +73,7 @@ const SettingRow: React.FC<Props> = ({
|
||||
<Label as="h3">
|
||||
<label htmlFor={name}>{label}</label>
|
||||
</Label>
|
||||
<Text type="secondary">{description}</Text>
|
||||
{description && <Text type="secondary">{description}</Text>}
|
||||
</Column>
|
||||
<Column>{children}</Column>
|
||||
</Row>
|
||||
|
||||
Reference in New Issue
Block a user