Skip to content

Commit

Permalink
Merge branch 'feature/access-management' of https://github.com/equino…
Browse files Browse the repository at this point in the history
…r/fusion-project-portal into feature/access-management
  • Loading branch information
kjetilhau committed Dec 5, 2024
2 parents 34e9cc8 + 7dfe530 commit b121a25
Show file tree
Hide file tree
Showing 15 changed files with 259 additions and 45 deletions.
15 changes: 11 additions & 4 deletions client/apps/portal-administration/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { defineAppConfig } from '@equinor/fusion-framework-cli';

const feature = true;
export default defineAppConfig((_env) => ({
environment: {
client: {
baseUri: 'https://backend-fusion-project-portal-test.radix.equinor.com',
defaultScopes: ['api://02f3484c-cad0-4d1d-853d-3a9e604b38f3/access_as_user'],
},
client: feature
? {
baseUri: 'https://backend-fusion-project-portal-feature.radix.equinor.com',
defaultScopes: ['api://7bf96dd1-39fe-47dd-8286-329c730ac76b/access_as_user'],
}
: {
baseUri: 'https://backend-fusion-project-portal-test.radix.equinor.com',
defaultScopes: ['api://02f3484c-cad0-4d1d-853d-3a9e604b38f3/access_as_user'],
},
},
}));
3 changes: 2 additions & 1 deletion client/apps/portal-administration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"react-router-dom": "^6.26.0",
"@equinor/fusion-react-side-sheet": "^1.3.3",
"@equinor/fusion-framework-module-navigation": "^4.0.7",
"@equinor/fusion-framework-module-msal": "^3.1.5",
"@equinor/fusion-framework-react-app": "^5.2.10",
"@ag-grid-community/core": "^32.1.0",
"react": "^18.2.0",
Expand All @@ -43,4 +44,4 @@
"uuidv7": "^1.0.1",
"zod": "^3.23.8"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Label, Icon, Button, Typography } from '@equinor/eds-core-react';
import { PersonSelect, PersonListItem, PersonSelectEvent } from '@equinor/fusion-react-person';
import { FieldErrors, UseFormSetValue, UseFormWatch } from 'react-hook-form';
import styled from 'styled-components';
import { error_filled, close } from '@equinor/eds-icons';
import { PortalInputs } from '../../schema';

const Style = {
AdminError: styled(Typography).withConfig({ displayName: 'AdminError' })`
display: flex;
gap: 0.5rem;
align-items: center;
`,
AdminList: styled.div`
padding-top: 1rem;
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 1rem;
`,
};

export const AddAdmins = ({
watch,
setValue,
errors,
canEdit,
}: {
watch: UseFormWatch<PortalInputs>;
setValue: UseFormSetValue<PortalInputs>;
errors: FieldErrors<PortalInputs>;
canEdit?: boolean;
}) => {
const handlePersonSelect = (e: PersonSelectEvent) => {
const selected = e.nativeEvent.detail.selected?.azureId;
if (!selected) return;
const current = watch().admins;
const selectedItems = [...current, { azureUniqueId: selected }];
setValue('admins', selectedItems, { shouldTouch: true, shouldValidate: true });
};

const handlePersonRemove = (azureUniqueId: string) => {
setValue(
'admins',
watch().admins.filter((a) => a.azureUniqueId !== azureUniqueId),
{ shouldTouch: true }
);
};

const handleOnBlur = () => {
setValue('admins', watch().admins, { shouldTouch: true });
};

return (
<>
{canEdit && (
<div>
<Label htmlFor="#person-select" label={'Search for Portal Admins'}></Label>
<PersonSelect
id="person-select"
variant="page-dense"
selectedPerson={null}
onBlur={handleOnBlur}
onSelect={handlePersonSelect}
placeholder="Search.."
/>
</div>
)}
<Style.AdminList>
{errors.admins && (
<Style.AdminError variant="caption" color="danger">
{errors.admins.message}
<Icon data={error_filled} title="Error" />
</Style.AdminError>
)}
{watch().admins.map((person) => (
<PersonListItem key={person.azureUniqueId} azureId={person.azureUniqueId}>
{canEdit && (
<Button
variant="ghost_icon"
onClick={() => {
handlePersonRemove(person.azureUniqueId);
}}
>
<Icon data={close} title={`Remove admin`} />
</Button>
)}
</PersonListItem>
))}
</Style.AdminList>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ type DescriptionInputProps = {
errors: FieldErrors<{
description?: string | undefined;
}>;
canEdit?: boolean;
};

export const DescriptionInput = ({ register, errors }: DescriptionInputProps) => {
export const DescriptionInput = ({ register, errors, canEdit }: DescriptionInputProps) => {
return (
<TextField
rows={3}
Expand All @@ -22,6 +23,7 @@ export const DescriptionInput = ({ register, errors }: DescriptionInputProps) =>
inputIcon={errors.description && <Icon data={error_filled} title="Error" />}
label="Description"
maxLength={500}
readOnly={!canEdit}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ type IconInputProps = {
icon?: string | undefined;
}>;
icon: string;
canEdit?: boolean;
};

export const IconInput = ({ register, errors, icon }: IconInputProps) => {
export const IconInput = ({ register, errors, icon, canEdit }: IconInputProps) => {
return (
<Style.Row>
<div>
Expand All @@ -54,6 +55,7 @@ export const IconInput = ({ register, errors, icon }: IconInputProps) => {
<TextField
rows={5}
multiline
readOnly={!canEdit}
{...register('icon')}
id="textfield-icon"
variant={errors.icon && 'error'}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ type NameInputProps = {
errors: FieldErrors<{
name?: string | undefined;
}>;
canEdit?: boolean;
};

export const NameInput = ({ register, errors }: NameInputProps) => {
export const NameInput = ({ register, errors, canEdit }: NameInputProps) => {
return (
<TextField
{...register('name')}
id="textfield-name"
readOnly={!canEdit}
variant={errors.name && 'error'}
helperText={errors.name?.message}
inputIcon={errors.name && <Icon data={error_filled} title="Error" />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ type ShortNameInputProps = {
errors: FieldErrors<{
shortName?: string | undefined;
}>;
canEdit?: boolean;
};

export const ShortNameInput = ({ register, errors }: ShortNameInputProps) => {
export const ShortNameInput = ({ register, errors, canEdit }: ShortNameInputProps) => {
return (
<TextField
{...register('shortName')}
id="textfield-short-name"
readOnly={!canEdit}
variant={errors.shortName && 'error'}
helperText={errors.shortName?.message}
inputIcon={errors.shortName && <Icon data={error_filled} title="Error" />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ type SubtextInputProps = {
errors: FieldErrors<{
subtext?: string | undefined;
}>;
canEdit?: boolean;
};

export const SubtextInput = ({ register, errors }: SubtextInputProps) => {
export const SubtextInput = ({ register, errors, canEdit }: SubtextInputProps) => {
return (
<TextField
{...register('subtext')}
id="textfield-subtext"
readOnly={!canEdit}
variant={errors.subtext && 'error'}
helperText={errors.subtext?.message}
inputIcon={errors.subtext && <Icon data={error_filled} title="Error" />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import { useUpdatePortal } from '../../hooks/use-portal-query';
import { PortalInputs, portalEditInputSchema } from '../../schema';
import { ContextType, Portal } from '../../types';
import { DescriptionInput } from '../FormComponents/DescriptionInput';

import { FormActionBar } from './FormActionBar';
import { IdInput } from '../FormComponents/IdInput';
import { NameInput } from '../FormComponents/NameIntput';
import { ShortNameInput } from '../FormComponents/ShortNameInput';
import { SubtextInput } from '../FormComponents/SubTextInput';
import { IconInput } from '../FormComponents/IconInput';
import { AddAdmins } from '../FormComponents/AddAdmins';
import { useCurrentAccount } from '@equinor/fusion-framework-react-app/msal';
import { useAccess } from '../../hooks/use-access';

const Style = {
Wrapper: styled.div`
Expand All @@ -25,6 +27,7 @@ const Style = {
flex-direction: column;
padding-bottom: 2rem;
`,

Row: styled.div`
gap: 1rem;
display: flex;
Expand Down Expand Up @@ -70,17 +73,16 @@ export const EditPortalForm = (props: {
formState: { errors, isSubmitting, touchedFields },
watch,
setValue,
reset,
} = useForm<PortalInputs>({
resolver: zodResolver(portalEditInputSchema),
defaultValues: {
...props.portal,
admins: props.portal.admins || [],
},
});

const onSubmit: SubmitHandler<PortalInputs> = async (editedPortal) => {
await updatePortal(editedPortal);
reset();
};

const [type, setType] = useState(contexts && contexts?.length > 0 ? 'context-portal' : 'app-portal');
Expand All @@ -98,20 +100,32 @@ export const EditPortalForm = (props: {
onDisabled && onDisabled(disabled);
}, [disabled, onDisabled]);

const { data: isAdmin } = useAccess();

const account = useCurrentAccount();
const canEdit = useMemo(
() => watch().admins?.some((admin) => admin.azureUniqueId === account?.localAccountId) || isAdmin,
[watch().admins, account, isAdmin]
);

return (
<Style.Wrapper>
<Style.Card>
<Style.Heading variant="h5">General</Style.Heading>
<Style.From onSubmit={handleSubmit(onSubmit)} id="portal">
<IdInput register={register} errors={errors} />
<NameInput register={register} errors={errors} />
<NameInput register={register} errors={errors} canEdit={canEdit} />
<Style.Row>
<ShortNameInput register={register} errors={errors} />
<SubtextInput register={register} errors={errors} />
</Style.Row>
<DescriptionInput register={register} errors={errors} />
</Style.From>
</Style.Card>
<Style.Card>
<Style.Heading variant="h5">Admins</Style.Heading>
<AddAdmins watch={watch} setValue={setValue} errors={errors} canEdit={canEdit} />
</Style.Card>
<Style.Card>
<Typography variant="h5">Icon</Typography>
<IconInput register={register} errors={errors} icon={watch().icon} />
Expand All @@ -128,12 +142,14 @@ export const EditPortalForm = (props: {
value="app-portal"
checked={type === 'app-portal'}
onChange={onTypeChange}
disabled={!canEdit}
/>
<Radio
label="Context Portal"
value="context-portal"
checked={type === 'context-portal'}
onChange={onTypeChange}
disabled={!canEdit}
/>
</Style.Row>
</Style.Card>
Expand All @@ -144,24 +160,29 @@ export const EditPortalForm = (props: {
<Typography variant="h5">Context</Typography>
</div>

<Autocomplete
id="textfield-context-types"
multiple
variant={errors.contextTypes && 'error'}
helperText={errors.contextTypes?.message}
options={props.contextTypes?.map((ct) => ct.type) || []}
selectedOptions={watch().contextTypes}
onOptionsChange={({ selectedItems }) => {
setValue('contextTypes', selectedItems, { shouldTouch: true });
}}
itemCompare={(item, compare) => {
return item === compare;
}}
label="Context Types"
/>
{canEdit ? (
<Autocomplete
id="textfield-context-types"
multiple
variant={errors.contextTypes && 'error'}
helperText={errors.contextTypes?.message}
options={props.contextTypes?.map((ct) => ct.type) || []}
selectedOptions={watch().contextTypes}
onOptionsChange={({ selectedItems }) => {
setValue('contextTypes', selectedItems, { shouldTouch: true });
}}
itemCompare={(item, compare) => {
return item === compare;
}}
label="Context Types"
/>
) : (
<>{watch().contextTypes?.join(' | ')}</>
)}
</Style.Card>
)}
{!props.isSideSheet && (

{!props.isSideSheet && canEdit && (
<Style.Card>
<Typography variant="overline">Portal Actions</Typography>
<Style.Row>
Expand Down
Loading

0 comments on commit b121a25

Please sign in to comment.