Added missing logic to realm form (#80)

* made alerts easier to use

* better clear message

* added missing logic to realm form

* format

* fixed types

* fixed test

* fix merge error
This commit is contained in:
Erik Jan de Wit 2020-09-15 21:44:28 +02:00 committed by GitHub
parent 782a09e064
commit 3798c41db0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 584 additions and 56 deletions

View file

@ -10,12 +10,14 @@ import {
import { RealmSelector } from "./components/realm-selector/RealmSelector";
import { DataLoader } from "./components/data-loader/DataLoader";
import { HttpClientContext } from "./http-service/HttpClientContext";
import { Realm } from "./realm/models/Realm";
import { RealmRepresentation } from "./realm/models/Realm";
export const PageNav: React.FunctionComponent = () => {
const httpClient = useContext(HttpClientContext)!;
const realmLoader = async () => {
const response = await httpClient.doGet<Realm[]>("/admin/realms");
const response = await httpClient.doGet<RealmRepresentation[]>(
"/admin/realms"
);
return response.data;
};

View file

@ -14,7 +14,6 @@ import FileSaver from "file-saver";
import { ExternalLink } from "../components/external-link/ExternalLink";
import { HttpClientContext } from "../http-service/HttpClientContext";
import { useAlerts } from "../components/alert/Alerts";
import { AlertPanel } from "../components/alert/AlertPanel";
import { ClientRepresentation } from "./models/client-model";
type ClientListProps = {
@ -32,7 +31,7 @@ const columns: (keyof ClientRepresentation)[] = [
export const ClientList = ({ baseUrl, clients }: ClientListProps) => {
const { t } = useTranslation("clients");
const httpClient = useContext(HttpClientContext)!;
const [add, alerts, hide] = useAlerts();
const [add, Alerts] = useAlerts();
const convertClientId = (clientId: string) =>
clientId.substring(0, clientId.indexOf("#"));
@ -77,7 +76,7 @@ export const ClientList = ({ baseUrl, clients }: ClientListProps) => {
});
return (
<>
<AlertPanel alerts={alerts} onCloseAlert={hide} />
<Alerts />
<Table
variant={TableVariant.compact}
cells={[

View file

@ -11,7 +11,6 @@ import { HttpClientContext } from "../../http-service/HttpClientContext";
import { Step1 } from "./Step1";
import { Step2 } from "./Step2";
import { ClientRepresentation } from "../models/client-model";
import { AlertPanel } from "../../components/alert/AlertPanel";
import { useAlerts } from "../../components/alert/Alerts";
import { useTranslation } from "react-i18next";
@ -26,7 +25,7 @@ export const NewClientForm = () => {
publicClient: false,
authorizationServicesEnabled: false,
});
const [add, alerts, hide] = useAlerts();
const [add, Alerts] = useAlerts();
const save = async () => {
try {
@ -53,7 +52,7 @@ export const NewClientForm = () => {
const title = t("Create client");
return (
<>
<AlertPanel alerts={alerts} onCloseAlert={hide} />
<Alerts />
<PageSection variant="light">
<TextContent>
<Text component="h1">{title}</Text>

View file

@ -18,13 +18,12 @@ import { ClientDescription } from "../ClientDescription";
import { HttpClientContext } from "../../http-service/HttpClientContext";
import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload";
import { useAlerts } from "../../components/alert/Alerts";
import { AlertPanel } from "../../components/alert/AlertPanel";
export const ImportForm = () => {
const { t } = useTranslation("clients");
const httpClient = useContext(HttpClientContext)!;
const [add, alerts, hide] = useAlerts();
const [add, Alerts] = useAlerts();
const defaultClient = {
protocol: "",
clientId: "",
@ -57,7 +56,7 @@ export const ImportForm = () => {
};
return (
<>
<AlertPanel alerts={alerts} onCloseAlert={hide} />
<Alerts />
<PageSection variant="light">
<TextContent>
<Text component="h1">{t("Import client")}</Text>

View file

@ -30,7 +30,6 @@ export function AlertPanel({ alerts, onCloseAlert }: AlertPanelProps) {
actionClose={
<AlertActionCloseButton
title={message}
variantLabel={`${variant} alert`}
onClose={() => onCloseAlert(key)}
/>
}

View file

@ -1,11 +1,12 @@
import { useState } from "react";
import { AlertType } from "./AlertPanel";
import React, { useState, ReactElement } from "react";
import { AlertType, AlertPanel } from "./AlertPanel";
import { AlertVariant } from "@patternfly/react-core";
export function useAlerts(): [
(message: string, type: AlertVariant) => void,
AlertType[],
(key: number) => void
(message: string, type?: AlertVariant) => void,
() => ReactElement,
(key: number) => void,
AlertType[]
] {
const [alerts, setAlerts] = useState<AlertType[]>([]);
const createId = () => new Date().getTime();
@ -14,11 +15,16 @@ export function useAlerts(): [
setAlerts((alerts) => [...alerts.filter((el) => el.key !== key)]);
};
const add = (message: string, variant: AlertVariant) => {
const add = (
message: string,
variant: AlertVariant = AlertVariant.default
) => {
const key = createId();
setAlerts([...alerts, { key, message, variant }]);
setTimeout(() => hideAlert(key), 8000);
};
return [add, alerts, hideAlert];
const Panel = () => <AlertPanel alerts={alerts} onCloseAlert={hideAlert} />;
return [add, Panel, hideAlert, alerts];
}

View file

@ -1,5 +1,5 @@
import React from "react";
import { Button, AlertVariant } from "@patternfly/react-core";
import { Button } from "@patternfly/react-core";
import { mount } from "enzyme";
import { act } from "react-dom/test-utils";
@ -9,11 +9,11 @@ import { useAlerts } from "../Alerts";
jest.useFakeTimers();
const WithButton = () => {
const [add, alerts, hide] = useAlerts();
const [add, _, hide, alerts] = useAlerts();
return (
<>
<AlertPanel alerts={alerts} onCloseAlert={hide} />
<Button onClick={() => add("Hello", AlertVariant.default)}>Add</Button>
<Button onClick={() => add("Hello")}>Add</Button>
</>
);
};

View file

@ -104,7 +104,7 @@ exports[`remove alert after timeout: with alert 1`] = `
>
<button
aria-disabled="false"
aria-label="Close default alert alert: Hello"
aria-label="Close alert: Hello"
class="pf-c-button pf-m-plain"
data-ouia-component-id="2"
data-ouia-component-type="PF4/Button"

View file

@ -85,7 +85,7 @@ export const JsonFileUpload = ({
</Button>,
]}
>
{t("confirmImportClear")}
{t("Are you sure you want to clear this file?")}
</Modal>
)}
<FormGroup

View file

@ -1,6 +1,6 @@
import React, { useState, useContext, useEffect } from "react";
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import { Realm } from "../../realm/models/Realm";
import { RealmRepresentation } from "../../realm/models/Realm";
import {
Dropdown,
@ -14,7 +14,7 @@ import "./realm-selector.css";
type RealmSelectorProps = {
realm: string;
realmList: Realm[];
realmList: RealmRepresentation[];
};
export const RealmSelector = ({ realm, realmList }: RealmSelectorProps) => {

View file

@ -1,4 +1,5 @@
import React from "react";
import React, { useState, FormEvent, useContext } from "react";
import { useTranslation } from "react-i18next";
import {
Text,
PageSection,
@ -7,22 +8,51 @@ import {
Form,
TextInput,
Switch,
FileUpload,
ActionGroup,
Button,
Divider,
AlertVariant,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
//type NewRealmFormProps = {
// realm: string;
//};
import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload";
import { RealmRepresentation } from "../models/Realm";
import { HttpClientContext } from "../../http-service/HttpClientContext";
import { useAlerts } from "../../components/alert/Alerts";
export const NewRealmForm = () => {
const { t } = useTranslation("realm");
//({ realm }: NewRealmFormProps) => {
const httpClient = useContext(HttpClientContext)!;
const [add, Alerts] = useAlerts();
const defaultRealm = { id: "", realm: "", enabled: true };
const [realm, setRealm] = useState<RealmRepresentation>(defaultRealm);
const handleFileChange = (value: string | File) => {
setRealm({
...realm,
...(value ? JSON.parse(value as string) : defaultRealm),
});
};
const handleChange = (
value: string | boolean,
event: FormEvent<HTMLInputElement>
) => {
const name = (event.target as HTMLInputElement).name;
setRealm({ ...realm, [name]: value });
};
const save = async () => {
try {
await httpClient.doPost("/admin/realms", realm);
add(t("Realm created"), AlertVariant.success);
} catch (error) {
add(`${t("Could not create realm:")} '${error}'`, AlertVariant.danger);
}
};
return (
<>
<Alerts />
<PageSection variant="light">
<TextContent>
<Text component="h1">Create Realm</Text>
@ -31,40 +61,31 @@ export const NewRealmForm = () => {
<Divider />
<PageSection variant="light">
<Form isHorizontal>
<FormGroup label={t("Upload JSON file")} fieldId="kc-realm-filename">
<FileUpload
id="simple-text-file"
type="text"
// value={value}
// filename={filename}
// onChange={this.handleFileChange}
// onReadStarted={this.handleFileReadStarted}
// onReadFinished={this.handleFileReadFinished}
// isLoading={isLoading}
/>
</FormGroup>
<JsonFileUpload id="kc-realm-filename" onChange={handleFileChange} />
<FormGroup label={t("Realm name")} isRequired fieldId="kc-realm-name">
<TextInput
isRequired
type="text"
id="kc-realm-name"
name="kc-realm-name"
// value={value2}
// onChange={this.handleTextInputChange2}
name="realm"
value={realm.realm}
onChange={handleChange}
/>
</FormGroup>
<FormGroup label={t("Enabled")} fieldId="kc-realm-enabled-switch">
<Switch
id="kc-realm-enabled-switch"
name="kc-realm-enabled-switch"
name="enabled"
label={t("On")}
labelOff={t("Off")}
// isChecked={isChecked}
// onChange={this.handleChange}
isChecked={realm.enabled}
onChange={handleChange}
/>
</FormGroup>
<ActionGroup>
<Button variant="primary">{t("Create")}</Button>
<Button variant="primary" onClick={() => save()}>
{t("Create")}
</Button>
<Button variant="link">{t("Cancel")}</Button>
</ActionGroup>
</Form>

View file

@ -1,4 +1,507 @@
export interface Realm {
export interface RealmRepresentation {
id: string;
realm: string;
displayName?: string;
displayNameHtml?: string;
notBefore?: number;
defaultSignatureAlgorithm?: string;
revokeRefreshToken?: boolean;
refreshTokenMaxReuse?: number;
accessTokenLifespan?: number;
accessTokenLifespanForImplicitFlow?: number;
ssoSessionIdleTimeout?: number;
ssoSessionMaxLifespan?: number;
ssoSessionIdleTimeoutRememberMe?: number;
ssoSessionMaxLifespanRememberMe?: number;
offlineSessionIdleTimeout?: number;
offlineSessionMaxLifespanEnabled?: boolean;
offlineSessionMaxLifespan?: number;
clientSessionIdleTimeout?: number;
clientSessionMaxLifespan?: number;
clientOfflineSessionIdleTimeout?: number;
clientOfflineSessionMaxLifespan?: number;
accessCodeLifespan?: number;
accessCodeLifespanUserAction?: number;
accessCodeLifespanLogin?: number;
actionTokenGeneratedByAdminLifespan?: number;
actionTokenGeneratedByUserLifespan?: number;
enabled?: boolean;
sslRequired?: string;
passwordCredentialGrantAllowed?: boolean;
registrationAllowed?: boolean;
registrationEmailAsUsername?: boolean;
rememberMe?: boolean;
verifyEmail?: boolean;
loginWithEmailAllowed?: boolean;
duplicateEmailsAllowed?: boolean;
resetPasswordAllowed?: boolean;
editUsernameAllowed?: boolean;
bruteForceProtected?: boolean;
permanentLockout?: boolean;
maxFailureWaitSeconds?: number;
minimumQuickLoginWaitSeconds?: number;
waitIncrementSeconds?: number;
quickLoginCheckMilliSeconds?: number;
maxDeltaTimeSeconds?: number;
failureFactor?: number;
privateKey?: string;
publicKey?: string;
certificate?: string;
codeSecret?: string;
roles?: RolesRepresentation;
groups?: GroupRepresentation[];
defaultRoles?: string[];
defaultGroups?: string[];
requiredCredentials?: string[];
passwordPolicy?: string;
otpPolicyType?: string;
otpPolicyAlgorithm?: string;
otpPolicyInitialCounter?: number;
otpPolicyDigits?: number;
otpPolicyLookAheadWindow?: number;
otpPolicyPeriod?: number;
otpSupportedApplications?: string[];
webAuthnPolicyRpEntityName?: string;
webAuthnPolicySignatureAlgorithms?: string[];
webAuthnPolicyRpId?: string;
webAuthnPolicyAttestationConveyancePreference?: string;
webAuthnPolicyAuthenticatorAttachment?: string;
webAuthnPolicyRequireResidentKey?: string;
webAuthnPolicyUserVerificationRequirement?: string;
webAuthnPolicyCreateTimeout?: number;
webAuthnPolicyAvoidSameAuthenticatorRegister?: boolean;
webAuthnPolicyAcceptableAaguids?: string[];
webAuthnPolicyPasswordlessRpEntityName?: string;
webAuthnPolicyPasswordlessSignatureAlgorithms?: string[];
webAuthnPolicyPasswordlessRpId?: string;
webAuthnPolicyPasswordlessAttestationConveyancePreference?: string;
webAuthnPolicyPasswordlessAuthenticatorAttachment?: string;
webAuthnPolicyPasswordlessRequireResidentKey?: string;
webAuthnPolicyPasswordlessUserVerificationRequirement?: string;
webAuthnPolicyPasswordlessCreateTimeout?: number;
webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister?: boolean;
webAuthnPolicyPasswordlessAcceptableAaguids?: string[];
users?: UserRepresentation[];
federatedUsers?: UserRepresentation[];
scopeMappings?: ScopeMappingRepresentation[];
clientScopeMappings?: { [index: string]: ScopeMappingRepresentation[] };
clients?: ClientRepresentation[];
clientScopes?: ClientScopeRepresentation[];
defaultDefaultClientScopes?: string[];
defaultOptionalClientScopes?: string[];
browserSecurityHeaders?: { [index: string]: string };
smtpServe?: { [index: string]: string };
userFederationProviders?: UserFederationProviderRepresentation[];
userFederationMappers?: UserFederationMapperRepresentation[];
loginTheme?: string;
accountTheme?: string;
adminTheme?: string;
emailTheme?: string;
eventsEnabled?: boolean;
eventsExpiration?: number;
eventsListeners?: string[];
enabledEventTypes?: string[];
adminEventsEnabled?: boolean;
adminEventsDetailsEnabled?: boolean;
identityProviders?: IdentityProviderRepresentation[];
identityProviderMappers?: IdentityProviderMapperRepresentation[];
protocolMappers?: ProtocolMapperRepresentation[];
components?: { [index: string]: ComponentExportRepresentation };
internationalizationEnabled?: boolean;
supportedLocales?: string[];
defaultLocale?: string;
authenticationFlows?: AuthenticationFlowRepresentation[];
authenticatorConfig?: AuthenticatorConfigRepresentation[];
requiredActions?: RequiredActionProviderRepresentation[];
browserFlow?: string;
registrationFlow?: string;
directGrantFlow?: string;
resetCredentialsFlow?: string;
clientAuthenticationFlow?: string;
dockerAuthenticationFlow?: string;
attributes?: { [index: string]: string };
keycloakVersion?: string;
userManagedAccessAllowed?: boolean;
social?: boolean;
updateProfileOnInitialSocialLogin?: boolean;
socialProviders?: { [index: string]: string };
applicationScopeMappings?: { [index: string]: ScopeMappingRepresentation[] };
applications?: ApplicationRepresentation[];
oauthClients?: OAuthClientRepresentation[];
clientTemplates?: ClientTemplateRepresentation[];
}
export interface RolesRepresentation {
realm: RoleRepresentation[];
client: { [index: string]: RoleRepresentation[] };
application: { [index: string]: RoleRepresentation[] };
}
export interface GroupRepresentation {
id: string;
name: string;
path: string;
attributes: { [index: string]: string[] };
realmRoles: string[];
clientRoles: { [index: string]: string[] };
subGroups: GroupRepresentation[];
access: { [index: string]: boolean };
}
export interface UserRepresentation {
self: string;
id: string;
origin: string;
createdTimestamp: number;
username: string;
enabled: boolean;
totp: boolean;
emailVerified: boolean;
firstName: string;
lastName: string;
email: string;
federationLink: string;
serviceAccountClientId: string;
attributes: { [index: string]: string[] };
credentials: CredentialRepresentation[];
disableableCredentialTypes: string[];
requiredActions: string[];
federatedIdentities: FederatedIdentityRepresentation[];
realmRoles: string[];
clientRoles: { [index: string]: string[] };
clientConsents: UserConsentRepresentation[];
notBefore: number;
applicationRoles: { [index: string]: string[] };
socialLinks: SocialLinkRepresentation[];
groups: string[];
access: { [index: string]: boolean };
}
export interface ScopeMappingRepresentation {
self: string;
client: string;
clientTemplate: string;
clientScope: string;
roles: string[];
}
export interface ClientRepresentation {
id: string;
clientId: string;
name: string;
description: string;
rootUrl: string;
adminUrl: string;
baseUrl: string;
surrogateAuthRequired: boolean;
enabled: boolean;
alwaysDisplayInConsole: boolean;
clientAuthenticatorType: string;
secret: string;
registrationAccessToken: string;
defaultRoles: string[];
redirectUris: string[];
webOrigins: string[];
notBefore: number;
bearerOnly: boolean;
consentRequired: boolean;
standardFlowEnabled: boolean;
implicitFlowEnabled: boolean;
directAccessGrantsEnabled: boolean;
serviceAccountsEnabled: boolean;
authorizationServicesEnabled: boolean;
directGrantsOnly: boolean;
publicClient: boolean;
frontchannelLogout: boolean;
protocol: string;
attributes: { [index: string]: string };
authenticationFlowBindingOverrides: { [index: string]: string };
fullScopeAllowed: boolean;
nodeReRegistrationTimeout: number;
registeredNodes: { [index: string]: number };
protocolMappers: ProtocolMapperRepresentation[];
clientTemplate: string;
useTemplateConfig: boolean;
useTemplateScope: boolean;
useTemplateMappers: boolean;
defaultClientScopes: string[];
optionalClientScopes: string[];
authorizationSettings: ResourceServerRepresentation;
access: { [index: string]: boolean };
origin: string;
}
export interface ClientScopeRepresentation {
id: string;
name: string;
description: string;
protocol: string;
attributes: { [index: string]: string };
protocolMappers: ProtocolMapperRepresentation[];
}
export interface UserFederationProviderRepresentation {
id: string;
displayName: string;
providerName: string;
config: { [index: string]: string };
priority: number;
fullSyncPeriod: number;
changedSyncPeriod: number;
lastSync: number;
}
export interface UserFederationMapperRepresentation {
id: string;
name: string;
federationProviderDisplayName: string;
federationMapperType: string;
config: { [index: string]: string };
}
export interface IdentityProviderRepresentation {
alias: string;
displayName: string;
internalId: string;
providerId: string;
enabled: boolean;
updateProfileFirstLoginMode: string;
trustEmail: boolean;
storeToken: boolean;
addReadTokenRoleOnCreate: boolean;
authenticateByDefault: boolean;
linkOnly: boolean;
firstBrokerLoginFlowAlias: string;
postBrokerLoginFlowAlias: string;
config: { [index: string]: string };
}
export interface IdentityProviderMapperRepresentation {
id: string;
name: string;
identityProviderAlias: string;
identityProviderMapper: string;
config: { [index: string]: string };
}
export interface ProtocolMapperRepresentation {
id: string;
name: string;
protocol: string;
protocolMapper: string;
consentRequired: boolean;
consentText: string;
config: { [index: string]: string };
}
export interface ComponentExportRepresentation {
id: string;
name: string;
providerId: string;
subType: string;
subComponents: { [index: string]: ComponentExportRepresentation };
config: { [index: string]: string };
}
export interface AuthenticationFlowRepresentation extends Serializable {
id: string;
alias: string;
description: string;
providerId: string;
topLevel: boolean;
builtIn: boolean;
authenticationExecutions: AuthenticationExecutionExportRepresentation[];
}
export interface AuthenticatorConfigRepresentation extends Serializable {
id: string;
alias: string;
config: { [index: string]: string };
}
export interface RequiredActionProviderRepresentation {
alias: string;
name: string;
providerId: string;
enabled: boolean;
defaultAction: boolean;
priority: number;
config: { [index: string]: string };
}
export interface ApplicationRepresentation extends ClientRepresentation {
claims: ClaimRepresentation;
}
export interface OAuthClientRepresentation extends ApplicationRepresentation {}
export interface ClientTemplateRepresentation {
id: string;
name: string;
description: string;
protocol: string;
fullScopeAllowed: boolean;
bearerOnly: boolean;
consentRequired: boolean;
standardFlowEnabled: boolean;
implicitFlowEnabled: boolean;
directAccessGrantsEnabled: boolean;
serviceAccountsEnabled: boolean;
publicClient: boolean;
frontchannelLogout: boolean;
attributes: { [index: string]: string };
protocolMappers: ProtocolMapperRepresentation[];
}
export interface RoleRepresentation {
id: string;
name: string;
description: string;
scopeParamRequired: boolean;
composite: boolean;
composites: Composites;
clientRole: boolean;
containerId: string;
attributes: { [index: string]: string[] };
}
export interface CredentialRepresentation {
id: string;
type: string;
userLabel: string;
createdDate: number;
secretData: string;
credentialData: string;
priority: number;
value: string;
temporary: boolean;
device: string;
hashedSaltedValue: string;
salt: string;
hashIterations: number;
counter: number;
algorithm: string;
digits: number;
period: number;
config: { [index: string]: string };
}
export interface FederatedIdentityRepresentation {
identityProvider: string;
userId: string;
userName: string;
}
export interface UserConsentRepresentation {
clientId: string;
grantedClientScopes: string[];
createdDate: number;
lastUpdatedDate: number;
grantedRealmRoles: string[];
}
export interface SocialLinkRepresentation {
socialProvider: string;
socialUserId: string;
socialUsername: string;
}
export interface ResourceServerRepresentation {
id: string;
clientId: string;
name: string;
allowRemoteResourceManagement: boolean;
policyEnforcementMode: PolicyEnforcementMode;
resources: ResourceRepresentation[];
policies: PolicyRepresentation[];
scopes: ScopeRepresentation[];
decisionStrategy: DecisionStrategy;
}
export interface AuthenticationExecutionExportRepresentation
extends AbstractAuthenticationExecutionRepresentation {
flowAlias: string;
userSetupAllowed: boolean;
}
export interface Serializable {}
export interface ClaimRepresentation {
name: boolean;
username: boolean;
profile: boolean;
picture: boolean;
website: boolean;
email: boolean;
gender: boolean;
locale: boolean;
address: boolean;
phone: boolean;
}
export interface Composites {
realm: string[];
client: { [index: string]: string[] };
application: { [index: string]: string[] };
}
export interface ResourceRepresentation {
name: string;
type: string;
owner: ResourceOwnerRepresentation;
ownerManagedAccess: boolean;
displayName: string;
attributes: { [index: string]: string[] };
_id: string;
uris: string[];
scopes: ScopeRepresentation[];
icon_uri: string;
}
export interface PolicyRepresentation extends AbstractPolicyRepresentation {
config: { [index: string]: string };
}
export interface ScopeRepresentation {
id: string;
name: string;
iconUri: string;
policies: PolicyRepresentation[];
resources: ResourceRepresentation[];
displayName: string;
}
export interface AbstractAuthenticationExecutionRepresentation
extends Serializable {
authenticatorConfig: string;
authenticator: string;
requirement: string;
priority: number;
autheticatorFlow: boolean;
}
export interface ResourceOwnerRepresentation {
id: string;
name: string;
}
export interface AbstractPolicyRepresentation {
id: string;
name: string;
description: string;
type: string;
policies: string[];
resources: string[];
scopes: string[];
logic: Logic;
decisionStrategy: DecisionStrategy;
owner: string;
resourcesData: ResourceRepresentation[];
scopesData: ScopeRepresentation[];
}
export type PolicyEnforcementMode = "ENFORCING" | "PERMISSIVE" | "DISABLED";
export type DecisionStrategy = "AFFIRMATIVE" | "UNANIMOUS" | "CONSENSUS";
export type Logic = "POSITIVE" | "NEGATIVE";

View file

@ -17,10 +17,10 @@ export const Api = () => (
/>
);
export const AddAlert = () => {
const [add, alerts, hide] = useAlerts();
const [add, Alerts] = useAlerts();
return (
<>
<AlertPanel alerts={alerts} onCloseAlert={hide} />
<Alerts />
<Button onClick={() => add("Hello", AlertVariant.default)}>Add</Button>
</>
);