Skip to content

Commit 9a06ec0

Browse files
ms2/share modal improvements (#437)
* fix(ms2/process-page): throw UnauthorizedError -> correct error message * feat(ms2/share-modal): select version of process in share modal * fix(ms2/legacy-store): parse dates of processes * fix(ms2/process-documentation): don't mutate state variable directly * quickfix(ms2/documentation-page): creatorId not defined on legacy store * fix(ms2/process-document): if processHierarchy changes pages should start as an empty array * Fixed type error --------- Co-authored-by: Janis Joderi Shoferi <janis.joderi@web.de>
1 parent 143f92d commit 9a06ec0

File tree

7 files changed

+123
-93
lines changed

7 files changed

+123
-93
lines changed

src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/modeler-share-modal-option-public-link.tsx

Lines changed: 97 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
'use client';
2-
import { App, Button, Checkbox, Col, Flex, Input, QRCode, Row } from 'antd';
2+
import { App, Button, Checkbox, Col, Flex, Input, QRCode, Row, Select, Space } from 'antd';
33
import { DownloadOutlined, CopyOutlined } from '@ant-design/icons';
44
import { useEffect, useRef, useState } from 'react';
55
import { useParams, useSearchParams } from 'next/navigation';
@@ -10,21 +10,24 @@ import {
1010
import { useEnvironment } from '@/components/auth-can';
1111

1212
import styles from './modeler-share-modal-option-public-link.module.scss';
13+
import { Process } from '@/lib/data/process-schema';
1314

1415
type ModelerShareModalOptionPublicLinkProps = {
1516
sharedAs: 'public' | 'protected';
1617
shareTimestamp: number;
1718
refresh: () => void;
19+
processVersions: Process['versions'];
1820
};
1921

2022
const ModelerShareModalOptionPublicLink = ({
2123
sharedAs,
2224
shareTimestamp,
2325
refresh,
26+
processVersions,
2427
}: ModelerShareModalOptionPublicLinkProps) => {
2528
const { processId } = useParams();
2629
const query = useSearchParams();
27-
const selectedVersionId = query.get('version');
30+
const [selectedVersionId, setSelectedVersionId] = useState<string | null>(query.get('version'));
2831
const environment = useEnvironment();
2932

3033
const [shareLink, setShareLink] = useState('');
@@ -185,95 +188,104 @@ const ModelerShareModalOptionPublicLink = ({
185188
};
186189

187190
return (
188-
<>
189-
<div style={{ marginBottom: '5px' }}>
191+
<Space direction="vertical">
192+
<Select
193+
defaultValue={selectedVersionId || '-1'}
194+
options={[
195+
{ value: '-1', label: 'Latest Version' },
196+
...processVersions.map((version) => ({ value: version.id, label: version.name })),
197+
]}
198+
onChange={(value) => {
199+
setSelectedVersionId(value === '-1' ? null : value);
200+
}}
201+
/>
202+
203+
<div>
190204
<Checkbox checked={isShareLinkChecked} onChange={handleShareLinkChecked}>
191205
Share Process with Public Link
192206
</Checkbox>
193207
</div>
194-
<div>
195-
<Row>
196-
<Col span={18} style={{ paddingBottom: '10px', paddingLeft: '25px' }}>
197-
<Flex vertical gap="small" justify="left" align="left">
198-
<Checkbox
199-
checked={registeredUsersonlyChecked}
200-
onChange={handlePermissionChanged}
201-
disabled={isShareLinkEmpty}
202-
>
203-
Visible only for registered user
204-
</Checkbox>
205-
</Flex>
206-
</Col>
207-
<Col span={18}>
208-
<Input
209-
type={'text'}
210-
value={shareLink}
208+
<Row>
209+
<Col span={18} style={{ paddingBottom: '10px', paddingLeft: '25px' }}>
210+
<Flex vertical gap="small" justify="left" align="left">
211+
<Checkbox
212+
checked={registeredUsersonlyChecked}
213+
onChange={handlePermissionChanged}
211214
disabled={isShareLinkEmpty}
212-
name="generated share link"
213-
style={{ border: '1px solid #000' }}
214-
/>
215-
</Col>
216-
<Col span={12}>
217-
<Flex
218-
vertical={false}
219-
style={{ paddingTop: '10px', flexWrap: 'wrap-reverse' }}
220-
justify="center"
221-
align="center"
222215
>
223-
{isShareLinkChecked && (
224-
<div id="qrcode" ref={canvasRef}>
225-
<QRCode
226-
style={{
227-
border: '1px solid #000',
228-
}}
229-
value={shareLink}
230-
size={130}
231-
/>
232-
</div>
233-
)}
234-
</Flex>
235-
</Col>
236-
<Col span={6} style={{ paddingTop: '10px' }}>
237-
<Flex vertical gap={10}>
238-
<Button
239-
className={styles.OptionButton}
240-
onClick={handleCopyLink}
241-
disabled={isShareLinkEmpty}
242-
>
243-
Copy link
244-
</Button>
245-
<Button
246-
className={styles.OptionButton}
247-
onClick={handleOpenSharedPage}
248-
disabled={isShareLinkEmpty}
249-
>
250-
Open
251-
</Button>
252-
<Button
253-
icon={<DownloadOutlined />}
254-
title="Save as PNG"
255-
className={styles.OptionButton}
256-
hidden={isShareLinkEmpty}
257-
onClick={() => handleQRCodeAction('download')}
258-
disabled={isShareLinkEmpty}
259-
>
260-
Save QR Code
261-
</Button>
262-
<Button
263-
icon={<CopyOutlined />}
264-
title="Copy as PNG"
265-
className={styles.OptionButton}
266-
hidden={isShareLinkEmpty}
267-
onClick={() => handleQRCodeAction('copy')}
268-
disabled={isShareLinkEmpty}
269-
>
270-
Copy QR Code
271-
</Button>
272-
</Flex>
273-
</Col>
274-
</Row>
275-
</div>
276-
</>
216+
Visible only for registered user
217+
</Checkbox>
218+
</Flex>
219+
</Col>
220+
<Col span={18}>
221+
<Input
222+
type={'text'}
223+
value={shareLink}
224+
disabled={isShareLinkEmpty}
225+
name="generated share link"
226+
style={{ border: '1px solid #000' }}
227+
/>
228+
</Col>
229+
<Col span={12}>
230+
<Flex
231+
vertical={false}
232+
style={{ paddingTop: '10px', flexWrap: 'wrap-reverse' }}
233+
justify="center"
234+
align="center"
235+
>
236+
{isShareLinkChecked && (
237+
<div id="qrcode" ref={canvasRef}>
238+
<QRCode
239+
style={{
240+
border: '1px solid #000',
241+
}}
242+
value={shareLink}
243+
size={130}
244+
/>
245+
</div>
246+
)}
247+
</Flex>
248+
</Col>
249+
<Col span={6} style={{ paddingTop: '10px' }}>
250+
<Flex vertical gap={10}>
251+
<Button
252+
className={styles.OptionButton}
253+
onClick={handleCopyLink}
254+
disabled={isShareLinkEmpty}
255+
>
256+
Copy link
257+
</Button>
258+
<Button
259+
className={styles.OptionButton}
260+
onClick={handleOpenSharedPage}
261+
disabled={isShareLinkEmpty}
262+
>
263+
Open
264+
</Button>
265+
<Button
266+
icon={<DownloadOutlined />}
267+
title="Save as PNG"
268+
className={styles.OptionButton}
269+
hidden={isShareLinkEmpty}
270+
onClick={() => handleQRCodeAction('download')}
271+
disabled={isShareLinkEmpty}
272+
>
273+
Save QR Code
274+
</Button>
275+
<Button
276+
icon={<CopyOutlined />}
277+
title="Copy as PNG"
278+
className={styles.OptionButton}
279+
hidden={isShareLinkEmpty}
280+
onClick={() => handleQRCodeAction('copy')}
281+
disabled={isShareLinkEmpty}
282+
>
283+
Copy QR Code
284+
</Button>
285+
</Flex>
286+
</Col>
287+
</Row>
288+
</Space>
277289
);
278290
};
279291

src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/modeler-share-modal.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,15 @@ import { set } from 'zod';
3232
type ShareModalProps = {
3333
onExport: () => void;
3434
onExportMobile: (type: ProcessExportOptions['type']) => void;
35+
versions: Process['versions'];
3536
};
3637
type SharedAsType = 'public' | 'protected';
3738

38-
const ModelerShareModalButton: FC<ShareModalProps> = ({ onExport, onExportMobile }) => {
39+
const ModelerShareModalButton: FC<ShareModalProps> = ({
40+
onExport,
41+
onExportMobile,
42+
versions: processVersions,
43+
}) => {
3944
const { processId } = useParams();
4045
const environment = useEnvironment();
4146
const [isOpen, setIsOpen] = useState(false);
@@ -276,6 +281,7 @@ const ModelerShareModalButton: FC<ShareModalProps> = ({ onExport, onExportMobile
276281
sharedAs={sharedAs as SharedAsType}
277282
shareTimestamp={shareTimestamp}
278283
refresh={checkIfProcessShared}
284+
processVersions={processVersions}
279285
/>
280286
),
281287
},

src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/modeler-toolbar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ type ModelerToolbarProps = {
3939
onOpenXmlEditor: () => void;
4040
canUndo: boolean;
4141
canRedo: boolean;
42-
versions: { id: string; name: string; description: string }[];
42+
versions: { id: string; name: string; description: string; createdOn: Date }[];
4343
};
4444
const ModelerToolbar = ({
4545
processId,
@@ -317,6 +317,7 @@ const ModelerToolbar = ({
317317
<ModelerShareModalButton
318318
onExport={handleProcessExportModalToggle}
319319
onExportMobile={handleProcessExportModalToggleMobile}
320+
versions={versions}
320321
/>
321322
<Tooltip title="Open Documentation">
322323
<Button icon={<FilePdfOutlined />} onClick={handleOpenDocumentation} />

src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/modeler.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { useSession } from 'next-auth/react';
2727
type ModelerProps = React.HTMLAttributes<HTMLDivElement> & {
2828
versionName?: string;
2929
process: { name: string; id: string; bpmn: string };
30-
versions: { id: string; name: string; description: string }[];
30+
versions: { id: string; name: string; description: string; createdOn: Date }[];
3131
};
3232

3333
const Modeler = ({ versionName, process, versions, ...divProps }: ModelerProps) => {

src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { toCaslResource } from '@/lib/ability/caslAbility';
66
import AddUserControls from '@/components/add-user-controls';
77
import { getProcess, getProcesses } from '@/lib/data/DTOs';
88
import { getProcessBPMN } from '@/lib/data/processes';
9+
import { UnauthorizedError } from '@/lib/ability/abilityHelper';
910

1011
type ProcessProps = {
1112
params: { processId: string; environmentId: string };
@@ -24,7 +25,7 @@ const Process = async ({ params: { processId, environmentId }, searchParams }: P
2425
const processes = await getProcesses(activeEnvironment.spaceId, ability, false);
2526

2627
if (!ability.can('view', toCaslResource('Process', process))) {
27-
throw new Error('Forbidden.');
28+
throw new UnauthorizedError();
2829
}
2930

3031
const selectedVersionBpmn = selectedVersionId

src/management-system-v2/app/shared-viewer/process-document.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ const ProcessDocument: React.FC<ProcessDocumentProps> = ({
267267
<div className={cn(styles.Title, { [styles.TitlePage]: settings.titlepage })}>
268268
<Title>{processData.name}</Title>
269269
<div className={styles.TitleInfos}>
270-
<div>Owner: {processData.creatorId.split('|').pop()}</div>
270+
<div>Owner: {processData.creatorId?.split('|').pop()}</div>
271271
{version.id ? (
272272
<>
273273
<div>Version: {version.name || version.id}</div>

src/management-system-v2/lib/data/legacy/_process.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,23 @@ export function getProcessMetaObjects() {
5757
return processMetaObjects;
5858
}
5959

60+
function parseDates<T extends ProcessMetadata>(process: T) {
61+
// The type says these fields are dates, but they're turned into a string when stored
62+
process.lastEditedOn = new Date(process.lastEditedOn);
63+
process.createdOn = new Date(process.createdOn);
64+
65+
return process;
66+
}
67+
6068
/** Returns all processes for a user */
6169
export async function getProcesses(environmentId: string, ability?: Ability, includeBPMN = false) {
6270
const spaceProcesses = Object.values(processMetaObjects).filter(
6371
(process) => process.environmentId === environmentId,
6472
);
6573

66-
const processes = ability ? ability.filter('view', 'Process', spaceProcesses) : spaceProcesses;
74+
const processes = (
75+
ability ? ability.filter('view', 'Process', spaceProcesses) : spaceProcesses
76+
).map(parseDates);
6777

6878
if (!includeBPMN) return processes;
6979
return processes.map((process) => ({ ...process, bpmn: getProcessBpmn(process.id) }));
@@ -76,7 +86,7 @@ export async function getProcess(processDefinitionsId: string, includeBPMN = fal
7686
}
7787

7888
const bpmn = includeBPMN ? await getProcessBpmn(processDefinitionsId) : null;
79-
return { ...process, bpmn };
89+
return parseDates({ ...process, bpmn });
8090
}
8191

8292
/**
@@ -236,7 +246,7 @@ export async function updateProcessMetaData(
236246

237247
const newMetaData = {
238248
...processMetaObjects[processDefinitionsId],
239-
lastEdited: new Date().toUTCString(),
249+
lastEditedOn: new Date(),
240250
};
241251

242252
mergeIntoObject(newMetaData, metaChanges, true, true, true);

0 commit comments

Comments
 (0)