Initial version of the Account UI (#3410)

Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
Erik Jan de Wit 2022-10-10 12:14:19 +02:00 committed by GitHub
parent 9ec757cae1
commit dc3c08aa59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 2395 additions and 6146 deletions

4
apps/account-ui/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
# Vite
dist
dist-ssr
*.local

View file

@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<base href="./" />
<link rel="icon" type="image/svg+xml" href="./favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Web site to manage keycloak" />
<title>Keycloak account console</title>
<style>
body {
margin: 0;
}
body, #app {
height: 100%;
}
.container {
padding: 0;
margin: 0;
width: 100%;
}
.keycloak__loading-container {
height: 100vh;
width: 100%;
background-color: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin: 0;
}
#loading-text {
z-index: 1000;
font-size: 20px;
font-weight: 600;
padding-top: 32px;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<div class="keycloak__loading-container">
<span class="pf-c-spinner pf-m-xl" role="progressbar" aria-valuetext="Loading...">
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
<div>
<p id="loading-text">Loading the account console</p>
</div>
</div>
</div>
</div>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View file

@ -0,0 +1,28 @@
{
"name": "account-ui",
"scripts": {
"dev": "vite --host",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --ext js,jsx,mjs,ts,tsx"
},
"dependencies": {
"@patternfly/patternfly": "^4.215.1",
"@patternfly/react-core": "^4.239.0",
"@patternfly/react-icons": "^4.90.0",
"i18next": "^21.9.2",
"i18next-http-backend": "^1.4.4",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-i18next": "^11.18.6",
"react-router": "^6.4.1",
"react-router-dom": "^6.4.1"
},
"devDependencies": {
"@types/react": "^17.0.45",
"@types/react-dom": "^17.0.16",
"@vitejs/plugin-react": "^2.1.0",
"vite": "^3.1.4",
"vite-plugin-checker": "^0.5.1"
}
}

View file

@ -0,0 +1,63 @@
<svg id="Layer_1" data-name="Layer 1" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs id="defs10">
<clipPath id="clip-path">
<path class="cls-1" id="rect4" d="M-1018.62 565.7H862.62v1175.78h-1881.24z" />
</clipPath>
<clipPath id="clip-path-2">
<path class="cls-1" id="rect7" d="M0 0h512v512H0z" />
</clipPath>
<style id="style2">.cls-1{fill:none}.cls-24{fill:#d0d0d0}.cls-26{fill:#d9d9d9}.cls-28{fill:#d8d8d8}.cls-29{fill:#e2e2e2}.cls-31{fill:#dedede}.cls-36{fill:#00b8e3}.cls-37{fill:#33c6e9}.cls-38{fill:#008aaa}</style>
</defs>
<g clip-path="url(#clip-path)" id="g36" stroke-width="1.51">
<path d="M-42.82 358l245 24.8 199.4 2z" id="path14" fill="#b17c81" stroke="#b17c81" />
<path d="M-42.82 358l444.44 26.79 227.18-2z" id="path16" fill="#a2747c" stroke="#a2747c" />
<path d="M401.62 384.74l163.69 138.89 63.49-140.87z" id="path18" fill="#996976" stroke="#996976" />
<path d="M202.22 382.76l54.56 14.88 144.84-12.9z" id="path20" fill="#aa787e" stroke="#aa787e" />
<path d="M401.62 384.74L356 537.52l209.32-13.89z" id="path22" fill="#b2777e" stroke="#b2777e" />
<path d="M256.78 397.64L356 537.52l45.63-152.78z" id="path24" fill="#b27a7f" stroke="#b27a7f" />
<path d="M256.78 397.64l-92.26 135.91 191.47 4z" id="path26" fill="#c78485" stroke="#c78485" />
<path d="M202.22 382.76l-37.7 150.79 92.26-135.91z" id="path28" fill="#c08184" stroke="#c08184" />
<path d="M-42.82 358l207.34 175.55 37.7-150.79z" id="path30" fill="#c48485" stroke="#c48485" />
<path d="M-42.82 358l-51.59 137.9 258.93 37.7z" id="path32" fill="#d58b88" stroke="#d58b88" />
<path d="M-94.41 495.85L-33.89 598l198.41-64.48z" id="path34" fill="#e09790" stroke="#e09790" />
</g>
<g clip-path="url(#clip-path-2)" id="g110">
<path d="M438.48 152a3.79 3.79 0 01-3.32-1.89L377.39 49.94a3.91 3.91 0 00-3.39-1.89H138.33a3.79 3.79 0 00-3.33 1.89L75 153.89l-55.83 100.2a3.88 3.88 0 000 3.82L75 358l60 104a3.79 3.79 0 003.32 1.89H374a3.91 3.91 0 003.36-1.89l57.84-100.1a3.79 3.79 0 013.32-1.89h71.93a4.32 4.32 0 004.32-4.32V156.32a4.32 4.32 0 00-4.32-4.32h-72z" id="path38" fill="#4d4d4d" />
<path d="M72.85 157.64l-55.191 98.369 5.871 12.931 54.845 89.592L114.19 360l287.27-.02H461l38.264-9.68 15.496-42.81.01-49.36v-45.72L510.46 152h-71.98l-22.11.01H147.94l-75.09 5.63" id="path27674" fill="#e2e2e2" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-opacity="1" fill-opacity="1" />
<path class="cls-1" d="M510.46 152H78.34a3.91 3.91 0 00-3.34 1.89v.07l-2.14 3.69-26.45 45.83-29.23 50.63a3.8 3.8 0 000 3.83l6.35 11L75 358.06a3.84 3.84 0 003.34 1.94h432.18a4.27 4.27 0 004.24-4.28V156.34a4.32 4.32 0 00-4.3-4.34z" id="path40" />
<path d="M88.1 245.5l-64.57 23.44-6.35-11a3.8 3.8 0 010-3.83l29.23-50.63z" id="path42" fill="#e1e1e1" />
<path id="polygon44" fill="#c8c8c8" d="M472.21 264.21l42.56-6.08v49.36l-42.56-43.28z" />
<path d="M472.21 264.21l42.55 43.28v48.21a4.27 4.27 0 01-4.24 4.28H461z" id="path46" fill="#c2c2c2" />
<path id="polygon48" fill="#c7c7c7" d="M472.21 264.21L461 359.98h-59.54l-18.04-43.45 88.79-52.32z" />
<path id="polygon50" fill="#cecece" d="M472.21 264.21l42.56-51.8v45.72l-42.56 6.08z" />
<path d="M514.77 156.33v56.08l-42.55 51.8L440.12 152h70.33a4.32 4.32 0 014.32 4.33z" id="path52" fill="#d3d3d3" />
<path id="polygon54" fill="#c6c6c6" d="M401.46 359.98h-31.4l-8.14-11.67 21.5-31.78 18.04 43.45z" />
<path id="polygon56" fill="#d5d5d5" d="M472.21 264.21l-117.79-49.79 61.95-62.41h23.75l32.09 112.2z" />
<path class="cls-24" d="M354.42 214.42l29 102.11 88.8-52.32z" id="path58" />
<path id="polygon60" fill="#bfbfbf" d="M370.06 359.98h-8.52l.38-11.67 8.14 11.67z" />
<path class="cls-26" id="polygon62" d="M416.37 152.01l-61.95 62.41-11.18-55.82 23.92-6.59h49.21z" />
<path d="M354.42 214.42l-143 33 150.5 100.89z" id="path64" fill="#d4d4d4" />
<path class="cls-24" d="M354.42 214.42l7.49 133.9 21.5-31.78z" id="path66" />
<path class="cls-26" d="M343.24 158.6l-131.77 88.79 143-33z" id="path68" />
<path class="cls-28" id="polygon70" d="M211.47 247.39L149.5 359.98h-35.31L88.1 245.5l123.37 1.89z" />
<path class="cls-29" d="M147.94 152L88.1 245.5l-15.25-87.86 2.15-3.7v-.07a3.91 3.91 0 013.33-1.87h69.61z" id="path72" />
<path class="cls-28" d="M114.19 360H78.33a3.84 3.84 0 01-3.33-2l-51.47-89.06L88.1 245.5z" id="path74" />
<path id="polygon76" fill="#e4e4e4" d="M46.41 203.47l26.44-45.83L88.1 245.5z" />
<path class="cls-31" id="polygon78" d="M276.77 152.01H172.39l39.08 95.38 131.77-88.79-39.72-6.59h-26.75z" />
<path class="cls-31" id="polygon80" d="M156.09 152.01h-8.15L88.1 245.5l123.37 1.89-39.08-95.38h-16.3z" />
<path id="polygon82" fill="#c5c5c5" d="M333.23 359.98h28.31l.38-11.67-28.69 11.67z" />
<path class="cls-24" id="polygon84" d="M361.92 348.31L211.47 247.39l27.1 112.59h94.66l28.69-11.67z" />
<path id="polygon86" fill="#d1d1d1" d="M149.5 359.98H238.57l-27.1-112.59-61.97 112.59z" />
<path id="polygon88" fill="#ddd" d="M343.65 152.01l-.41 6.59 23.92-6.59H343.65z" />
<path id="polygon90" fill="#e3e3e3" d="M303.52 152.01l39.72 6.59-3.66-6.59h-36.06z" />
<path class="cls-29" id="polygon92" d="M339.58 152.01l3.66 6.59.41-6.59h-4.07z" />
<path class="cls-36" d="M235.15 153.81L177 254.46a3.38 3.38 0 00-.42 1.64h-40.51l79.74-138.18a3.14 3.14 0 011.19 1.15l.11.11 18.08 31.41a3.49 3.49 0 01-.04 3.22z" id="path94" />
<path class="cls-37" d="M235.08 361.89l-18 31.27a3.51 3.51 0 01-1.22 1.15L136 256.14h40.6a3.09 3.09 0 00.38 1.57.37.37 0 00.07.17l58 100.58a3.41 3.41 0 01.03 3.43z" id="path96" />
<path class="cls-38" d="M215.81 117.92L136.07 256.1l-20 34.66-19.1-33.12a3.09 3.09 0 01-.38-1.57 3.38 3.38 0 01.42-1.64l19.3-33.43 58.75-101.74a3.4 3.4 0 013-1.75h36.04a3.58 3.58 0 011.71.41z" id="path98" />
<path class="cls-36" d="M215.81 394.31a3.58 3.58 0 01-1.71.45H178a3.4 3.4 0 01-3-1.75l-53.72-93-5.28-9.22 20-34.66z" id="path100" />
<path class="cls-38" d="M376.19 256.1l-79.8 138.21a3.73 3.73 0 01-1.19-1.15l-.07-.1L277 361.72a3.49 3.49 0 010-3.22l58.06-100.65a3.38 3.38 0 00.49-1.75h40.57z" id="path102" />
<path class="cls-36" d="M415.68 256.1a3.38 3.38 0 01-.49 1.75l-78.13 135.31a3.42 3.42 0 01-2.9 1.61h-36a3.72 3.72 0 01-1.75-.45l79.78-138.22 20-34.62 19 32.91a3.35 3.35 0 01.49 1.71z" id="path104" />
<path class="cls-36" d="M376.19 256.1h-40.56a3.35 3.35 0 00-.49-1.71l-58-100.55a3.41 3.41 0 010-3.46l18.08-31.3a3.73 3.73 0 011.19-1.15z" id="path106" />
<path class="cls-37" d="M396.2 221.44l-20 34.62-79.81-138.14a3.72 3.72 0 011.75-.45h36a3.42 3.42 0 012.9 1.61z" id="path108" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

View file

@ -0,0 +1,16 @@
{
"accountSecurity": "Account security",
"applications": "Applications",
"deviceActivity": "Device activity",
"globalNavigation": "Global navigation",
"groups": "Groups",
"linkedAccounts": "Linked accounts",
"logo": "Logo",
"personalInfo": "Personal info",
"resources": "Resources",
"signingIn": "Signing in",
"somethingWentWrong": "Something went wrong",
"somethingWentWrongDescription": "Sorry, an unexpected error has occurred.",
"tryAgain": "Try again",
"welcomeMessage": "Welcome to Keycloak Account Management."
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -0,0 +1,7 @@
import { PageSection } from "@patternfly/react-core";
const DeviceActivity = () => (
<PageSection>This is the device activity page.</PageSection>
);
export default DeviceActivity;

View file

@ -0,0 +1,7 @@
import { PageSection } from "@patternfly/react-core";
const LinkedAccounts = () => (
<PageSection>This is the linked accounts page.</PageSection>
);
export default LinkedAccounts;

View file

@ -0,0 +1,5 @@
import { PageSection } from "@patternfly/react-core";
const SigningIn = () => <PageSection>This is the signing in page.</PageSection>;
export default SigningIn;

View file

@ -0,0 +1,7 @@
import { PageSection } from "@patternfly/react-core";
const Applications = () => (
<PageSection>This is the applications page.</PageSection>
);
export default Applications;

View file

@ -0,0 +1,11 @@
export type Environment = {
/** The URL to resources such as the files in the `public` directory. */
resourceUrl: string;
};
// The default environment, used during development.
const defaultEnvironment: Environment = {
resourceUrl: "http://localhost:8080",
};
export { defaultEnvironment as environment };

View file

@ -0,0 +1,5 @@
import { PageSection } from "@patternfly/react-core";
const Groups = () => <PageSection>This is the groups page.</PageSection>;
export default Groups;

View file

@ -0,0 +1,23 @@
import { createInstance } from "i18next";
import HttpBackend from "i18next-http-backend";
import { initReactI18next } from "react-i18next";
import { environment } from "./environment";
const DEFAULT_LOCALE = "en";
const DEFAULT_NAMESPACE = "translation";
export const i18n = createInstance({
defaultNS: DEFAULT_NAMESPACE,
fallbackLng: DEFAULT_LOCALE,
ns: [DEFAULT_NAMESPACE],
interpolation: {
escapeValue: false,
},
backend: {
loadPath: environment.resourceUrl + "/locales/{{lng}}/{{ns}}.json",
},
});
i18n.use(HttpBackend);
i18n.use(initReactI18next);

View file

@ -0,0 +1,19 @@
import "@patternfly/react-core/dist/styles/base.css";
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { i18n } from "./i18n";
import { routes } from "./routes";
await i18n.init();
const router = createBrowserRouter(routes);
ReactDOM.render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
document.getElementById("app")
);

View file

@ -0,0 +1,7 @@
import { PageSection } from "@patternfly/react-core";
const PersonalInfo = () => (
<PageSection>This is the personal info page.</PageSection>
);
export default PersonalInfo;

12
apps/account-ui/src/react-i18next.d.ts vendored Normal file
View file

@ -0,0 +1,12 @@
import "react-i18next";
import translation from "../public/locales/en/translation.json";
declare module "react-i18next" {
interface CustomTypeOptions {
defaultNS: "translation";
resources: {
translation: typeof translation;
};
}
}

View file

@ -0,0 +1,5 @@
import { PageSection } from "@patternfly/react-core";
const Resources = () => <PageSection>This is the resources page.</PageSection>;
export default Resources;

View file

@ -0,0 +1,57 @@
import {
Button,
Modal,
ModalVariant,
Page,
Text,
TextContent,
TextVariants,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { useRouteError } from "react-router";
export const ErrorPage = () => {
const { t } = useTranslation();
const error = useRouteError();
const errorMessage = getErrorMessage(error);
function onRetry() {
location.href = location.origin + location.pathname;
}
return (
<Page>
<Modal
variant={ModalVariant.small}
title={t("somethingWentWrong")}
titleIconVariant="danger"
showClose={false}
isOpen
actions={[
<Button key="tryAgain" variant="primary" onClick={onRetry}>
{t("tryAgain")}
</Button>,
]}
>
<TextContent>
<Text>{t("somethingWentWrongDescription")}</Text>
{errorMessage && (
<Text component={TextVariants.small}>{errorMessage}</Text>
)}
</TextContent>
</Modal>
</Page>
);
};
function getErrorMessage(error: unknown) {
if (typeof error === "string") {
return error;
}
if (error instanceof Error) {
return error.message;
}
return null;
}

View file

@ -0,0 +1,3 @@
.brand {
height: 35px;
}

View file

@ -0,0 +1,39 @@
import {
Brand,
Masthead,
MastheadBrand,
MastheadMain,
MastheadToggle,
PageToggleButton,
} from "@patternfly/react-core";
import { BarsIcon } from "@patternfly/react-icons";
import { useTranslation } from "react-i18next";
import { useHref, useLinkClickHandler } from "react-router-dom";
import { environment } from "../environment";
import classes from "./PageHeader.module.css";
export const PageHeader = () => {
const { t } = useTranslation();
const href = useHref("/");
const handleClick = useLinkClickHandler("/");
return (
<Masthead>
<MastheadToggle>
<PageToggleButton variant="plain" aria-label={t("globalNavigation")}>
<BarsIcon />
</PageToggleButton>
</MastheadToggle>
<MastheadMain>
<MastheadBrand href={href} onClick={handleClick}>
<Brand
className={classes.brand}
src={environment.resourceUrl + "/logo.svg"}
alt={t("logo")}
/>
</MastheadBrand>
</MastheadMain>
</Masthead>
);
};

View file

@ -0,0 +1,150 @@
import {
Nav,
NavExpandable,
NavItem,
NavList,
PageSidebar,
} from "@patternfly/react-core";
import {
FunctionComponent,
MouseEvent as ReactMouseEvent,
useMemo,
} from "react";
import { TFuncKey, useTranslation } from "react-i18next";
import {
matchPath,
To,
useHref,
useLinkClickHandler,
useLocation,
} from "react-router-dom";
type RootMenuItem = {
label: TFuncKey;
path: string;
};
type MenuItemWithChildren = {
label: TFuncKey;
children: MenuItem[];
};
type MenuItem = RootMenuItem | MenuItemWithChildren;
const menuItems: MenuItem[] = [
{
label: "personalInfo",
path: "personal-info",
},
{
label: "accountSecurity",
children: [
{
label: "signingIn",
path: "account-security/signing-in",
},
{
label: "deviceActivity",
path: "account-security/device-activity",
},
{
label: "linkedAccounts",
path: "account-security/linked-accounts",
},
],
},
{
label: "applications",
path: "applications",
},
{
label: "groups",
path: "groups",
},
{
label: "resources",
path: "resources",
},
];
export const PageNav = () => (
<PageSidebar
nav={
<Nav>
<NavList>
{menuItems.map((menuItem) => (
<NavMenuItem key={menuItem.label} menuItem={menuItem} />
))}
</NavList>
</Nav>
}
/>
);
type NavMenuItemProps = {
menuItem: MenuItem;
};
function NavMenuItem({ menuItem }: NavMenuItemProps) {
const { t } = useTranslation();
const { pathname } = useLocation();
const isActive = useMemo(
() => matchMenuItem(pathname, menuItem),
[pathname, menuItem]
);
if ("path" in menuItem) {
return (
<NavLink to={menuItem.path} isActive={isActive}>
{t(menuItem.label)}
</NavLink>
);
}
return (
<NavExpandable
title={t(menuItem.label)}
isActive={isActive}
isExpanded={isActive}
>
{menuItem.children.map((child) => (
<NavMenuItem key={child.label} menuItem={child} />
))}
</NavExpandable>
);
}
function matchMenuItem(currentPath: string, menuItem: MenuItem): boolean {
if ("path" in menuItem) {
return !!matchPath(menuItem.path, currentPath);
}
return menuItem.children.some((child) => matchMenuItem(currentPath, child));
}
type NavLinkProps = {
to: To;
isActive: boolean;
};
const NavLink: FunctionComponent<NavLinkProps> = ({
to,
isActive,
children,
}) => {
const href = useHref(to);
const handleClick = useLinkClickHandler(to);
return (
<NavItem
to={href}
isActive={isActive}
onClick={(event) =>
// PatternFly does not have the correct type for this event, so we need to cast it.
handleClick(event as unknown as ReactMouseEvent<HTMLAnchorElement>)
}
>
{children}
</NavItem>
);
};

View file

@ -0,0 +1,14 @@
import { Page, Spinner } from "@patternfly/react-core";
import { Suspense } from "react";
import { Outlet } from "react-router";
import { PageHeader } from "./PageHeader";
import { PageNav } from "./PageNav";
export const Root = () => (
<Page header={<PageHeader />} sidebar={<PageNav />} isManagedSidebar>
<Suspense fallback={<Spinner />}>
<Outlet />
</Suspense>
</Page>
);

View file

@ -0,0 +1,7 @@
import { PageSection } from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
export const RootIndex = () => {
const { t } = useTranslation();
return <PageSection>{t("welcomeMessage")}</PageSection>;
};

View file

@ -0,0 +1,72 @@
import { lazy } from "react";
import type { IndexRouteObject, RouteObject } from "react-router";
import { ErrorPage } from "./root/ErrorPage";
import { Root } from "./root/Root";
import { RootIndex } from "./root/RootIndex";
const DeviceActivity = lazy(() => import("./account-security/DeviceActivity"));
const LinkedAccounts = lazy(() => import("./account-security/LinkedAccounts"));
const SigningIn = lazy(() => import("./account-security/SigningIn"));
const Applications = lazy(() => import("./applications/Applications"));
const Groups = lazy(() => import("./groups/Groups"));
const PersonalInfo = lazy(() => import("./personal-info/PersonalInfo"));
const Resources = lazy(() => import("./resources/Resources"));
export const DeviceActivityRoute: RouteObject = {
path: "account-security/device-activity",
element: <DeviceActivity />,
};
export const LinkedAccountsRoute: RouteObject = {
path: "account-security/linked-accounts",
element: <LinkedAccounts />,
};
export const SigningInRoute: RouteObject = {
path: "account-security/signing-in",
element: <SigningIn />,
};
export const ApplicationsRoute: RouteObject = {
path: "applications",
element: <Applications />,
};
export const GroupsRoute: RouteObject = {
path: "groups",
element: <Groups />,
};
export const PersonalInfoRoute: RouteObject = {
path: "personal-info",
element: <PersonalInfo />,
};
export const ResourcesRoute: RouteObject = {
path: "resources",
element: <Resources />,
};
export const RootIndexRoute: IndexRouteObject = {
index: true,
element: <RootIndex />,
};
export const RootRoute: RouteObject = {
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
children: [
RootIndexRoute,
DeviceActivityRoute,
LinkedAccountsRoute,
SigningInRoute,
ApplicationsRoute,
GroupsRoute,
PersonalInfoRoute,
ResourcesRoute,
],
};
export const routes: RouteObject[] = [RootRoute];

1
apps/account-ui/src/vite-env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="vite/client" />

View file

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"exclude": [
"**/*.test.ts",
"**/*.test.tsx"
]
}

View file

@ -0,0 +1,17 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import checker from "vite-plugin-checker";
// https://vitejs.dev/config/
export default defineConfig({
base: "",
server: {
port: 8080,
},
resolve: {
// Resolve the 'module' entrypoint at all times (not the default due to Node.js compatibility issues).
mainFields: ["module"],
dedupe: ["react", "react-dom"],
},
plugins: [react(), checker({ typescript: true })],
});

7889
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,7 @@
"name": "keycloak-ui",
"workspaces": [
"libs/keycloak-js",
"apps/account-ui",
"apps/admin-ui"
],
"scripts": {