Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/single rights/503 choose rights page #518

Merged
merged 31 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b00d957
first implementation of ChooseRightsPage
Albertlarsen Aug 24, 2023
8f45e7a
Change status check
Albertlarsen Aug 24, 2023
c7c9bad
implementation of warning color on ActionBars
Albertlarsen Aug 25, 2023
74c81f2
Merge branch 'main' into feat/single-rights/503-ChooseRightsPage
Albertlarsen Aug 25, 2023
62054f3
Before fixing rendered fewer hooks than expected
Albertlarsen Aug 28, 2023
569b63c
Before fixing rendered fewer hooks than expected
Albertlarsen Aug 28, 2023
c77eee6
remove function working
Albertlarsen Aug 29, 2023
41b536f
not-checked-by-default
Albertlarsen Aug 29, 2023
1827417
not-checked-by-default-2
Albertlarsen Aug 29, 2023
31ed051
Finish ChooseRightsPage
Albertlarsen Aug 31, 2023
94b18f0
make buttons disabled
Albertlarsen Aug 31, 2023
0a81746
Merge branch 'main' into feat/single-rights/503-ChooseRightsPage
Albertlarsen Aug 31, 2023
1385087
Code cleanup
Albertlarsen Aug 31, 2023
635d352
add useMemo
Albertlarsen Aug 31, 2023
3abb152
Code cleanup
Albertlarsen Aug 31, 2023
9ddbf8e
update fds tokens
Albertlarsen Aug 31, 2023
d033a65
PR Code improvement
Albertlarsen Sep 1, 2023
dbd12a9
Separate RightsActionBar from ResourceActionBar (#521)
allinox Sep 4, 2023
c2a59df
Make first ActionBar open
Albertlarsen Sep 4, 2023
77301f2
before trying to fix checked chips logic
Albertlarsen Sep 4, 2023
f0d67d8
First part fixing checked toggle state
Albertlarsen Sep 4, 2023
cba3db9
Fixed checked states not working
Albertlarsen Sep 4, 2023
87bb32b
Fix navigationButton placement
Albertlarsen Sep 4, 2023
3249a16
Fix navigation buttons placement
Albertlarsen Sep 4, 2023
4aa3d5f
Filter NotDelegable chips
Albertlarsen Sep 4, 2023
a6bd79b
Merge branch 'main' into feat/single-rights/503-ChooseRightsPage
Albertlarsen Sep 5, 2023
5458750
use moved textCode function
Albertlarsen Sep 5, 2023
687ac1c
fix-remove-service-bug
Albertlarsen Sep 5, 2023
4a761a3
fix lint errors
Albertlarsen Sep 5, 2023
ce0e5c4
Last code improvement
Albertlarsen Sep 6, 2023
1a1062e
Last code improvements
Albertlarsen Sep 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[
{
"rightKey": "ttd-am-k6/access",
"resource": [
{
"id": "urn:altinn:resource",
"value": "appid-502"
}
],
"action": "access",
"status": "Delegable",
"details": [
{
"code": "DelegationAccess",
"description": "The user have access through delegation(s) of the right to the recipient(s)",
"parameters": [
{
"name": "",
"value": ""
}
]
}
]
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
"status": "NotDelegable",
"details": [
{
"code": "Unknown",
"description": "Unknown",
"code": "MissingSrrRightAccess",
"description": "MissingSrrRightAccess",
"parameters": [
{
"name": "RoleAccess",
Expand All @@ -57,8 +57,8 @@
"status": "NotDelegable",
"details": [
{
"code": "Unknown",
"description": "Unknown",
"code": "MissingSrrRightAccess",
"description": "MissingSrrRightAccess",
"parameters": [
{
"name": "RoleAccess",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ private static string DetermineAccessLevel(DelegationRequestDto request)
return "ReadAndWriteAppid506.json";
case "appid-503":
return "AllAccessesAppid503.json";
case "appid-502":
return "OneAccessAppid502.json";
default:
return "AllAccessesAppid503.json";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ export const SummaryPage = ({
nextPath={
'/' + ApiDelegationPath.OfferedApiDelegations + '/' + ApiDelegationPath.Receipt
}
nextText={t('api_delegation.confirm_delegation')}
nextText={t('common.confirm')}
allinox marked this conversation as resolved.
Show resolved Hide resolved
nextDisabled={confirmationButtonDisabled}
nextLoading={confirmationButtonLoading}
nextButtonColor='success'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.popoverButtonContainer {
margin-top: 20px;
display: flex;
justify-content: center;
}

.serviceResources {
margin-top: var(--fds-spacing-2);
display: flex;
flex-direction: column;
gap: var(--fds-spacing-2);
}

.serviceResourceContent {
display: flex;
flex-direction: column;
gap: 14px;
}

.chipContainer {
display: flex;
gap: 5px;
margin-top: 15px;
}

.navigationContainer {
margin-top: 40px;
}

.secondaryText {
margin-top: 20px;
margin-bottom: 40px;
}

.alertContainer {
max-width: 500px;
margin-top: 30px;
}
allinox marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,17 +1,264 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { PersonIcon } from '@navikt/aksel-icons';
import {
Alert,
Button,
Chip,
Heading,
Ingress,
Paragraph,
Popover,
} from '@digdir/design-system-react';
import { useNavigate } from 'react-router-dom';
import { useEffect, useState } from 'react';

import { Page, PageContainer, PageContent, PageHeader } from '@/components';
import { DualElementsContainer, Page, PageContainer, PageContent, PageHeader } from '@/components';
import { useAppDispatch, useAppSelector } from '@/rtk/app/hooks';
import { SingleRightPath } from '@/routes/paths';
import { useMediaQuery } from '@/resources/hooks';
import {
type ServiceWithStatus,
removeServiceResource,
} from '@/rtk/features/singleRights/singleRightsSlice';
import { getSingleRightsErrorCodeTextKey } from '@/resources/utils/errorCodeUtils';

import { RightsActionBar } from './RightsActionBar/RightsActionBar';
import classes from './ChooseRightsPage.module.css';

interface DelegationResourceDTO {
title: string | undefined;
serviceIdentifier: string | undefined;
action: string;
}

export const ChooseRightsPage = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const dispatch = useAppDispatch();
const [selectedRights, setSelectedRights] = useState<DelegationResourceDTO[]>([]);
const servicesWithStatus = useAppSelector((state) => state.singleRightsSlice.servicesWithStatus);
const delegableServices = servicesWithStatus.filter((s) => s.status !== 'NotDelegable');
const isSm = useMediaQuery('(max-width: 768px)');

const initialCheckedRightsList = delegableServices.flatMap(
(cs) =>
cs.accessCheckResponses
?.filter((acr) => acr.status !== 'NotDelegable')
.map((acr) => ({
title: cs.service?.title,
serviceIdentifier: cs.service?.identifier,
action: acr.action,
})),
);

useEffect(() => {
setSelectedRights(initialCheckedRightsList);
}, []);

const onRemove = (identifier: string | undefined) => {
const newList = selectedRights.filter((s) => s.serviceIdentifier !== identifier);

setSelectedRights(newList);

void dispatch(removeServiceResource(identifier));
};

const onConfirm = () => {
console.log('confirm checkedStates', selectedRights);
};

const handleToggleChecked = (title: string, serviceIdentifier: string, action: string) => {
const existsInList = !!selectedRights.find(
(s) => s.serviceIdentifier === serviceIdentifier && s.action === action,
);

let newList: DelegationResourceDTO[] = [...selectedRights];
if (existsInList) {
newList = selectedRights.filter(
(s) => s.serviceIdentifier !== serviceIdentifier || s.action !== action,
);
} else {
newList.push({ title, serviceIdentifier, action });
}

setSelectedRights(newList);
};

const sortedServiceResources = [...delegableServices]?.sort((a, b) => {
const isPartiallyDelegableA = a.status === 'PartiallyDelegable';
const isPartiallyDelegableB = b.status === 'PartiallyDelegable';

if (isPartiallyDelegableA && !isPartiallyDelegableB) {
return -1;
}
if (!isPartiallyDelegableA && isPartiallyDelegableB) {
return 1;
}

return a.service?.title.localeCompare(b.service.title);
});

const chooseRightsActionBars = (sortedServiceResources: ServiceWithStatus[]) => {
return sortedServiceResources?.map((chosenService: ServiceWithStatus, chosenServicesIndex) => {
const serviceResourceContent = (
<div className={classes.serviceResourceContent}>
<Paragraph>{chosenService.service?.description}</Paragraph>
<Paragraph>{chosenService.service?.rightDescription}</Paragraph>
<Paragraph>{t('single_rights.action_bar_adjust_rights_text')}</Paragraph>
<Heading
size={'xxsmall'}
level={5}
>
{t('single_rights.choose_rights_chip_text')}
</Heading>
<div className={classes.chipContainer}>
{chosenService.accessCheckResponses
?.filter((response) => response.status !== 'NotDelegable')
.map((response, index: number) => {
const isChecked = !!selectedRights.find(
(s) =>
s.serviceIdentifier === chosenService.service?.identifier &&
s.action === response.action,
);

return (
<div key={index}>
<Chip.Toggle
checkmark
selected={isChecked}
onClick={() => {
handleToggleChecked(
chosenService.service?.title,
chosenService.service?.identifier,
response.action,
);
}}
>
{t(`common.${response.action}`)}
</Chip.Toggle>
</div>
);
})}
</div>
</div>
);

const alertContainer = chosenService.status === 'PartiallyDelegable' && (
<div className={classes.alertContainer}>
<Alert severity='warning'>
<Heading
size={'xsmall'}
level={6}
spacing
>
{t('single_rights.alert_partially_delegable_header')}
</Heading>
<Paragraph spacing>
{t(`${getSingleRightsErrorCodeTextKey(chosenService.errorCode)}`)}
</Paragraph>
<Paragraph>{t('single_rights.you_cant_delegate_these_rights')}</Paragraph>
<div className={classes.chipContainer}>
{chosenService.accessCheckResponses
?.filter((response) => response.status === 'NotDelegable')
.map((response, index: number) => {
return (
<div key={index}>
<Chip.Toggle>{t(`common.${response.action}`)}</Chip.Toggle>
</div>
);
})}
</div>
</Alert>
</div>
);

return (
<RightsActionBar
key={chosenService.service?.identifier}
title={chosenService.service?.title}
subtitle={chosenService.service?.resourceOwnerName}
status={chosenService.status}
onRemoveClick={() => {
onRemove(chosenService.service?.identifier);
}}
compact={isSm}
initialOpen={chosenServicesIndex === 0}
>
{serviceResourceContent}
{alertContainer}
</RightsActionBar>
);
});
};

const navigationButtons = () => {
return (
<DualElementsContainer
leftElement={
<Button
variant='outline'
color='primary'
fullWidth={true}
onClick={() => {
navigate(
'/' + SingleRightPath.DelegateSingleRights + '/' + SingleRightPath.ChooseService,
);
}}
>
{t('common.previous')}
</Button>
}
rightElement={
<Popover
placement={'top'}
trigger={
<Button
variant='filled'
color='primary'
fullWidth={true}
disabled={selectedRights.length < 1}
>
{t('common.complete')}
</Button>
}
variant={'info'}
>
<Paragraph>
{t('single_rights.confirm_delegation_text', { name: 'ANNEMA FIGMA' })}
</Paragraph>
<div className={classes.popoverButtonContainer}>
<Button
onClick={() => {
onConfirm();
}}
>
{t('common.confirm')}
</Button>
</div>
</Popover>
}
/>
);
};

return (
<PageContainer>
<Page color='light'>
<PageHeader icon={<PersonIcon />}>{t('single_rights.delegate_single_rights')}</PageHeader>
<PageContent></PageContent>
<PageContent>
<Ingress>
{t('single_rights.choose_rights_page_top_text', { name: 'ANNEMA FIGMA' })}
</Ingress>
<div className={classes.secondaryText}>
<Paragraph>{t('single_rights.choose_rights_page_secondary_text')}</Paragraph>
</div>
<div className={classes.serviceResources}>
{chooseRightsActionBars(sortedServiceResources)}
</div>
<div className={classes.navigationContainer}>{navigationButtons()}</div>
</PageContent>
</Page>
</PageContainer>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@media only screen and (max-width: 768px) {
.content {
margin: 10px;
}
}

@media only screen and (min-width: 769px) {
.content {
margin: 20px;
}
}
Loading
Loading