Search…
Core

Service Portal Core

This library contains all the core functionality needed to build a library into the service portal shell

Libraries

About

Service Portal libraries are designed to be dynamically loaded into view when a user has access to their functionality and has navigated to a part of it's feature.

When to create a new module

As the service portal grows in size and complexity it can sometimes be hard to define if a feature belongs to a module or whether a new module should be created. In general, a module is created for each specific service within the Island.is organization and belongs to a specific team within the Island.is organization. Maintenance and code ownership falls to that team but for larger modules that is not always the case. The service portal application supports modules defining routes outside of their "domain" so fx a "Health" module can define routes such as /stillingar/heilsa. It can help to think about modules as API domains, a new domain should be created for each branch of the services like health, education, finance etc.

Usage

1
export interface ServicePortalModule {
2
name: string
3
widgets: (props: ServicePortalModuleProps) => ServicePortalWidget[]
4
routes: (props: ServicePortalModuleProps) => ServicePortalRoute[]
5
}
Copied!
All libraries are implemented by defining an interface that gets loaded into the service portal shell on startup. This interface defines four aspects about the library:
  • Name - The name of the library
  • Widgets - A function that return an array of widgets
  • Routes - A function that returns an array of routes
  • Global - A function that returns an array of global components

Widgets

A widget is a small component rendered on the frontpage that usually gives a small amount of basic info about the libraries functionality and information. The widget function receives props of the type ServicePortalModuleProps that it should use to determine which widgets should be presented to the Service Portal Shell and how they should be rendered.
1
export interface ServicePortalModuleProps {
2
userInfo: User
3
client: ApolloClient<NormalizedCacheObject>
4
}
Copied!
The userInfo property contains information about the current session and user. Based on which user is logged in and what he has access to, different widgets could be rendered out for the user.
1
export type ServicePortalWidget = {
2
name: string
3
weight: number
4
render: (props: ServicePortalModuleProps) => ServicePortalModuleRenderValue
5
}
Copied!
The weight property determines where on the frontpage it should be rendered, the lower the weight, the higher up it will be. The render returns a lazy loaded component. An example of an implementation of a widget property might be something like this:
1
widgets: ({ userInfo }) => {
2
const applicationWidgets = [
3
{
4
name: 'Applications',
5
weight: 2,
6
render: () => lazy(() => import('./widgets/ApplicationOverview')),
7
},
8
]
9
const openApplications = getOpenApplicationsForUser(userInfo)
10
if (openApplications.length > 0)
11
applicationWidgets.push({
12
name: 'Open Applications',
13
weight: 1,
14
render: () => lazy(() => import('widgets/OpenApplications')),
15
})
16
return applicationWidgets
17
}
Copied!

Routes

Routes function in many of the same ways as widgets but instead of returning an array of widgets they return an array of routes.
1
export interface ServicePortalModuleProps {
2
userInfo: User
3
client: ApolloClient<NormalizedCacheObject>
4
}
Copied!
1
export type ServicePortalRoute = {
2
name: string
3
path: ServicePortalPath | ServicePortalPath[]
4
render?: (props: ServicePortalModuleProps) => ServicePortalModuleRenderValue
5
}
Copied!
Path defines at what path or paths this route should be rendered. The render property returns a lazy loaded component to be rendered when the user navigates to the described path. An example of an implementation of a route property might be something like this:
1
routes: (userInfo) => {
2
const applicationRoutes = [
3
{
4
name: 'Applications',
5
path: ServicePortalPath.ApplicationRoot,
6
render: () =>
7
lazy(() => import('./screens/ApplicationList/ApplicationList')),
8
},
9
]
10
11
const protectedApplication = getProtectedApplication(userInfo)
12
13
if (protectedApplication) {
14
applicationRoutes.push({
15
name: 'Super secret application screen',
16
path: ServicePortalPath.UmsoknirSecret,
17
render: () =>
18
lazy(() => import('./screens/ProtectedScreen/ProtectedScreen')),
19
})
20
}
21
22
return applicationRoutes
23
}
Copied!

Global Components

Global components will always be rendered by default These are usually utility components that prompt the user about certain things or provide other global functionality Example: A modal providing onboarding for unfilled user profiles
Global components should be used very sparingly to reduce harrassment on the user.
1
{
2
global?: (
3
props: ServicePortalModuleProps,
4
) => Promise<ServicePortalGlobalComponent[]>
5
}
Copied!
An example of how a global component might be implemented
1
global: async ({ client }) => {
2
if(client.userDoesNotHaveAUserProfile()) return [{
3
render: () =>
4
lazy(() =>
5
import('./components/UserOnboardingModal/UserOnboardingModal'),
6
),
7
}]
8
9
return []
Copied!
A service portal library might then look something like this:
1
import {
2
ServicePortalModule,
3
ServicePortalPath,
4
} from '@island.is/service-portal/core'
5
import { lazy } from 'react'
6
7
export const applicationsModule: ServicePortalModule = {
8
name: 'Applications',
9
widgets: () => [
10
{
11
name: 'Applications',
12
weight: 0,
13
render: () => lazy(() => import('./Widgets')),
14
},
15
],
16
routes: (userInfo) => {
17
const applicationRoutes = [
18
{
19
name: 'Applications',
20
path: ServicePortalPath.ApplicationRoot,
21
render: () =>
22
lazy(() => import('./screens/ApplicationList/ApplicationList')),
23
},
24
]
25
26
return applicationRoutes
27
},
28
}
29
30
// Widgets.tsx
31
const ApplicationWidgets: ServicePortalModuleComponent = ({ userInfo }) => (
32
<>
33
<h1>Widgets for {userInfo.profile.name}</h1>
34
<OpenApplications />
35
</>
36
)
37
38
// ApplicationList.tsx
39
const ApplicationList: ServicePortalModuleComponent = ({ userInfo }) => (
40
<>
41
<h1>Applications for {userInfo.profile.name}</h1>
42
<div>Application 1</div>
43
<div>Application 2</div>
44
<div>Application 3</div>
45
</>
46
)
Copied!

Service Portal Shell

The application shell takes care of initializing and maintaining the libraries along with implementing core functionality in the Service Portal.

Adding a library to the shell

Libraries are stored in the shell's store and loaded into view, to add a libary to the shell's module list, import and add it to the list defined in modules.ts
1
// other imports...
2
import { myNewModule } from '@island.is/service-portal/my-new-module'
3
4
export const modules: ServicePortalModule[] = [
5
// other modules...
6
myNewModule,
7
]
Copied!

Declaring routes for a library

Declaring a new route for the service portal involves a few steps:
  • Declare a path for the route
  • Declare a route in the master navigation
  • Implement the route based on the user's authorization scope and return it so it gets rendered into the navigation.
Declaring a path for a library
All Service Portal paths are declared as an enum in paths.ts
Declare a route in the master navigation
The master navigation is defined in the service portal core in masterNavigation.ts Navigation items are defined as such:
1
export interface ServicePortalNavigationItem {
2
name: MessageDescriptor | string
3
path?: ServicePortalPath
4
external?: boolean
5
// System routes are always rendered in the navigation
6
systemRoute?: boolean
7
icon?: Pick<IconProps, 'icon' | 'type'>
8
children?: ServicePortalNavigationItem[]
9
}
Copied!
Implement the route
Each library implements it's own routes (see above). Routes should only be returned if available to the session scope. Items will be rendered into the navigation if a route has been declared for it.
Last modified 8d ago