Skip to content

Commit

Permalink
Add simple login logic
Browse files Browse the repository at this point in the history
closes #53

Signed-off-by: Sean Sundberg <[email protected]>
  • Loading branch information
seansund committed Sep 22, 2023
1 parent f95191c commit aed038b
Show file tree
Hide file tree
Showing 16 changed files with 299 additions and 131 deletions.
54 changes: 41 additions & 13 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,30 @@
import React from "react";
import {Route, Routes} from "react-router-dom";
import {Notification, Search, User} from "@carbon/icons-react";
import {useAtomValue, useSetAtom, useAtom} from "jotai";
import {Loading} from "@carbon/react";

import './App.css'
import {activeItemAtom, currentUserAtom, currentUserAtomLoadable} from "./atoms";
import {NotFound, UIShell} from "./components";
import {CustomerRisk, Dashboard, KYC, KYCCaseDetail, KYCCaseList, KycSummarize, RTCQC, Utilities} from "./views";
import {MenuLinksModel, NavigationModel} from "./models";
import {DataExtraction} from "./views/DataExtraction";
import {
CustomerRisk,
Dashboard,
DataExtraction,
KYC,
KYCCaseDetail,
KYCCaseList,
KycSummarize,
Login,
RTCQC,
Utilities
} from "./views";

function App() {
const setCurrentUser = useSetAtom(currentUserAtom);
const loadable = useAtomValue(currentUserAtomLoadable)
const [activeItem, setActiveItem] = useAtom(activeItemAtom)

const menuLinks: MenuLinksModel[] = [
{title: 'Dashboard', href: '/', element: <Dashboard />},
Expand Down Expand Up @@ -43,7 +59,9 @@ function App() {
{title: 'Notifications', action: (<Notification size={20} />)},
{title: 'User Profile', action: (<User size={20} />)}
],
sideNav: menuLinks.map(link => ({title: link.title, href: link.href}))
sideNav: menuLinks
.filter(link => !link.excludeFromMenu)
.map(link => ({title: link.title, href: link.href}))
}

const renderMenuLinks = (menuLinks: MenuLinksModel[]) => {
Expand All @@ -68,16 +86,26 @@ function App() {
})
}

return (
<div>
<UIShell prefix="watsonx" navigation={navigation}>
<Routes>
{renderMenuLinks(menuLinks)}
<Route key="route-all" path="*" element={<NotFound />} />
</Routes>
</UIShell>
</div>
)
if (loadable.state === 'loading' || loadable.state === 'hasError') {
return (<Loading active={true} description="Login loading" id="login-loading" withOverlay={true} />)
}

const currentUser = loadable.data;

if (!currentUser) {
return (<Login setCurrentUser={setCurrentUser} />)
}

return (
<div>
<UIShell prefix="watsonx" navigation={navigation} activeItem={activeItem} setActiveItem={setActiveItem}>
<Routes>
{renderMenuLinks(menuLinks)}
<Route key="route-all" path="*" element={<NotFound />} />
</Routes>
</UIShell>
</div>
)
}

export default App
7 changes: 7 additions & 0 deletions src/atoms/current-user.atom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {atom} from "jotai";
import {UserProfileModel} from "../models";
import {loadable} from "jotai/utils";

export const currentUserAtom = atom<Promise<UserProfileModel>>(Promise.resolve(undefined));

export const currentUserAtomLoadable = loadable(currentUserAtom);
4 changes: 3 additions & 1 deletion src/atoms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ export * from './menu-config.atom';
export * from './kyc-case-management.atom';
export * from './menu-options.atom';
export * from './data-extraction.atom';
export * from './dashboard.atom.ts';
export * from './dashboard.atom';
export * from './current-user.atom';
export * from './navigation.atom';
3 changes: 3 additions & 0 deletions src/atoms/navigation.atom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {atom} from "jotai";

export const activeItemAtom = atom('')
225 changes: 108 additions & 117 deletions src/components/UIShell/UIShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,142 +25,133 @@ import {ErrorBoundary} from "../ErrorBoundary";
import {isMenuItemModel, MenuModel, NavigationModel} from "../../models";

export interface UIShellProps {
prefix: string
title?: string
navigation?: NavigationModel
children: unknown
prefix: string;
title?: string;
navigation?: NavigationModel;
activeItem: string;
setActiveItem: (item: string) => void;
children: unknown;
}

export interface UIShellState {
activeItem: string
}

export class UIShell extends React.Component<UIShellProps, UIShellState> {

constructor(props: UIShellProps) {
super(props);
this.state = {
activeItem: `/${window.location.pathname.split('/')[1] ?? ''}`
};
const renderHeaderMenuItem = (item: MenuModel) => {
if (isMenuItemModel(item)) {
return (<HeaderMenuItem href={item.href} key={item.title}>{item.title}</HeaderMenuItem>)
}

renderHeaderMenuItem(item: MenuModel) {
if (isMenuItemModel(item)) {
return (<HeaderMenuItem href={item.href} key={item.title}>{item.title}</HeaderMenuItem>)
}
return (
<HeaderMenu aria-label={item.title} menuLinkName={item.title} key={item.title}>
{item.items.map(childItem => renderHeaderMenuItem(childItem))}
</HeaderMenu>
)
}

return (
<HeaderMenu aria-label={item.title} menuLinkName={item.title} key={item.title}>
{item.items.map(childItem => this.renderHeaderMenuItem(childItem))}
</HeaderMenu>
)
const renderHeaderNavigation = (props: UIShellProps, label: string = 'React App') => {
const headerNav = props.navigation?.headerNav
if (headerNav === undefined || headerNav.length === 0) {
return (<></>)
}

renderHeaderNavigation(label: string = 'React App') {
const headerNav = this.props.navigation?.headerNav
if (headerNav === undefined || headerNav.length === 0) {
return (<></>)
}
return (
<HeaderNavigation aria-label={label}>
{headerNav.map(item => renderHeaderMenuItem(item))}
</HeaderNavigation>
)
}

return (
<HeaderNavigation aria-label={label}>
{headerNav.map(item => this.renderHeaderMenuItem(item))}
</HeaderNavigation>
)
const renderGlobalBar = (props: UIShellProps) => {
const globalBar = props.navigation?.globalBar
if (globalBar === undefined || globalBar.length === 0) {
return (<></>)
}

renderGlobalBar() {
const globalBar = this.props.navigation?.globalBar
if (globalBar === undefined || globalBar.length === 0) {
return (<></>)
}
return (
<HeaderGlobalBar>
{globalBar.map(item => {
return (
<HeaderGlobalAction
aria-label={item.title}
tooltipAlignment="end"
key={item.title}
>
{item.action}
</HeaderGlobalAction>
)
})}
</HeaderGlobalBar>

)
}

const renderSideNavItem = (activeItem: string, setActiveItem, item: MenuModel) => {
if (isMenuItemModel(item)) {
return (
<HeaderGlobalBar>
{globalBar.map(item => {
return (
<HeaderGlobalAction
aria-label={item.title}
tooltipAlignment="end"
key={item.title}
>
{item.action}
</HeaderGlobalAction>
)
})}
</HeaderGlobalBar>

<SideNavMenuItem
element={Link}
to={item.href}
isActive={activeItem === item.href}
onClick={() => setActiveItem(item.href)}
key={item.title}
>
{item.title}
</SideNavMenuItem>
)
}

renderSideNavItem(item: MenuModel) {
if (isMenuItemModel(item)) {
return (
<SideNavMenuItem
element={Link}
to={item.href}
isActive={this.state.activeItem === item.href}
onClick={() => { this.setState({ activeItem: item.href }) }}
key={item.title}
>
{item.title}
</SideNavMenuItem>
)
}
return (
<SideNavMenu renderIcon={Fade} title={item.title} defaultExpanded>
{item.items.map(childItem => renderSideNavItem(activeItem, setActiveItem, childItem))}
</SideNavMenu>
)
}

return (
<SideNavMenu renderIcon={Fade} title={item.title} defaultExpanded>
{item.items.map(childItem => this.renderSideNavItem(childItem))}
</SideNavMenu>
)
const renderSideNav = (props: UIShellProps, activeItem: string, setActiveItem: (item: string) => void, isSideNavExpanded: boolean) => {
const sideNav = props.navigation?.sideNav
if (sideNav === undefined || sideNav.length === 0) {
return (<></>)
}

renderSideNav(isSideNavExpanded: boolean) {
const sideNav = this.props.navigation?.sideNav
if (sideNav === undefined || sideNav.length === 0) {
return (<></>)
}
return (
<SideNav aria-label="Side navigation" expanded={isSideNavExpanded}>
<SideNavItems>
{sideNav.map(item => renderSideNavItem(activeItem, setActiveItem, item))}
</SideNavItems>
</SideNav>
)
}

return (
<SideNav aria-label="Side navigation" expanded={isSideNavExpanded}>
<SideNavItems>
{sideNav.map(item => this.renderSideNavItem(item))}
</SideNavItems>
</SideNav>
)
}

render() {
return (
<BrowserRouter>
<Theme theme='g90'>
<HeaderContainer
render={({ isSideNavExpanded, onClickSideNavExpand }: {isSideNavExpanded: boolean, onClickSideNavExpand: () => void}) => (
<div>
<Header aria-label="IBM Platform Name">
<SkipToContent />
<HeaderMenuButton
aria-label="Open menu"
onClick={onClickSideNavExpand}
isActive={isSideNavExpanded}
/>
<HeaderName href="#" prefix={this.props.prefix}>
&nbsp;{this.props.title}
</HeaderName>
{this.renderHeaderNavigation()}
{this.renderGlobalBar()}
<ErrorBoundary>
{this.renderSideNav(isSideNavExpanded)}
</ErrorBoundary>
</Header>
</div>
)}
/>
</Theme>
<Content className='content'>
{this.props.children}
</Content>
</BrowserRouter>
);
}
export const UIShell: React.FunctionComponent<UIShellProps> = (props: UIShellProps) => {

return (
<BrowserRouter>
<Theme theme='g90'>
<HeaderContainer
render={({ isSideNavExpanded, onClickSideNavExpand }: {isSideNavExpanded: boolean, onClickSideNavExpand: () => void}) => (
<div>
<Header aria-label="IBM Platform Name">
<SkipToContent />
<HeaderMenuButton
aria-label="Open menu"
onClick={onClickSideNavExpand}
isActive={isSideNavExpanded}
/>
<HeaderName href="#" prefix={props.prefix}>
&nbsp;{props.title}
</HeaderName>
{renderHeaderNavigation(props)}
{renderGlobalBar(props)}
<ErrorBoundary>
{renderSideNav(props, props.activeItem, props.setActiveItem, isSideNavExpanded)}
</ErrorBoundary>
</Header>
</div>
)}
/>
</Theme>
<Content className='content'>
{props.children}
</Content>
</BrowserRouter>
);
}
1 change: 1 addition & 0 deletions src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './menu-links.model';
export * from './kyc-case.model';
export * from './form-option.model';
export * from './data-extraction.model';
export * from './user-profile.model';
1 change: 1 addition & 0 deletions src/models/menu-links.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export interface MenuLinksModel {
title: string;
href: string;
element: any;
excludeFromMenu?: boolean;
subMenus?: MenuLinksModel[];
}
11 changes: 11 additions & 0 deletions src/models/user-profile.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

export interface UserProfileModel {
firstName: string;
lastName: string;
roles?: string[];
}

export interface LoginModel {
username: string;
password: string;
}
1 change: 1 addition & 0 deletions src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './kyc-case-management';
export * from './menu-options';
export * from './data-extraction';
export * from './file-upload';
export * from './login';
13 changes: 13 additions & 0 deletions src/services/login/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {LoginApi} from "./login.api";
import {LoginMock} from "./login.mock";

export * from './login.api';

let _instance: LoginApi;
export const loginApi = (): LoginApi => {
if (_instance) {
return _instance
}

return _instance = new LoginMock();
}
Loading

0 comments on commit aed038b

Please sign in to comment.