Skip to content

Commit beff425

Browse files
committed
mergeroni
2 parents 2f01592 + cb238be commit beff425

11 files changed

Lines changed: 150 additions & 93 deletions

File tree

app/src/app/components/Toolbar/PasswordPromptModal.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React, {useEffect} from 'react';
33
import {Cross2Icon} from '@radix-ui/react-icons';
44
import {Button, Flex, Text, Dialog, Box, TextField} from '@radix-ui/themes';
55
import {useRouter, useSearchParams} from 'next/navigation';
6+
import {getLoadPlanFromShare} from '@/app/utils/api/apiHandlers/getLoadPlanFromPublicId';
67

78
export const PasswordPromptModal = () => {
89
const router = useRouter();
@@ -21,14 +22,14 @@ export const PasswordPromptModal = () => {
2122
// remove pw from url
2223
router.replace(window.location.pathname);
2324
} else if (mapDocument?.public_id) {
24-
// const res = await getLoadPlanFromPublicId({
25-
// public_id: mapDocument?.public_id,
26-
// password: password,
27-
// });
28-
// if (res.document_id && res.document_id !== mapDocument?.document_id) {
29-
// // go to map/edit/res.document_id
30-
// router.replace(`/map/edit/${res.document_id}`);
31-
// }
25+
const res = await getLoadPlanFromShare({
26+
password: password,
27+
public_id: mapDocument?.public_id,
28+
});
29+
if (res && res.document_id && res.document_id !== mapDocument?.document_id) {
30+
// go to map/edit/res.document_id
31+
router.push(`/map/edit/${res.document_id}`);
32+
}
3233
}
3334
};
3435

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import {DocumentMetadata} from '@/app/utils/api/apiHandlers/types';
2-
import {Box, Flex, Grid, Heading, Text, TextArea, TextField} from '@radix-ui/themes';
2+
import {Box, Button, Flex, Grid, Heading, Text, TextArea, TextField} from '@radix-ui/themes';
33
import {MapStatusButtons} from '../../Topbar/MapStatus';
4+
import * as Accordion from '@radix-ui/react-accordion';
5+
import {useState} from 'react';
6+
import {ChevronDownIcon, ChevronUpIcon} from '@radix-ui/react-icons';
47

58
export const MapDetailsSection: React.FC<{
69
mapMetadata: DocumentMetadata;
710
onChange: (updates: Partial<DocumentMetadata>) => any;
811
isEditing: boolean;
912
}> = ({mapMetadata, onChange, isEditing}) => {
13+
const [accordionOpen, setAccordionOpen] = useState<string>('');
1014
return (
1115
<Flex direction="column" gap="2">
12-
<Heading as="h3" size="5">
13-
Map details
14-
</Heading>
1516
<Box>
1617
<Text>Name</Text>
1718
<TextField.Root
@@ -35,37 +36,56 @@ export const MapDetailsSection: React.FC<{
3536
/>
3637
</Flex>
3738
</Box>
38-
<Grid columns="2" gap="2">
39-
<Box>
40-
<Text>Group</Text>
41-
<TextField.Root
42-
className="flex-1 w-full"
43-
value={mapMetadata.group ?? ''}
44-
placeholder="Group"
45-
disabled={!isEditing}
46-
onChange={e => onChange({group: e.target.value})}
47-
/>
48-
</Box>
49-
<Box>
50-
<Text>Tags (coming soon)</Text>
51-
<TextField.Root
52-
className="flex-1 w-full"
53-
value={mapMetadata.tags ?? ''}
54-
placeholder="Tags"
55-
disabled={true}
56-
/>
57-
</Box>
58-
</Grid>
59-
<Box>
60-
<Text>Comments</Text>
61-
<TextArea
62-
className="flex-1 w-full"
63-
value={mapMetadata.description ?? ''}
64-
placeholder="Comments or description"
65-
onChange={e => onChange({description: e.target.value})}
66-
disabled={!isEditing}
67-
/>
68-
</Box>
39+
<Accordion.Root
40+
type="single"
41+
collapsible
42+
value={accordionOpen}
43+
onValueChange={value => setAccordionOpen(value)}
44+
>
45+
<Accordion.Item value="group">
46+
<Accordion.AccordionTrigger className="AccordionTrigger w-full mt-2" asChild>
47+
<Button className="w-full" variant="ghost">
48+
<Flex direction="row" gap="2" align="center" justify="start" className="w-full">
49+
<Text>Advanced</Text>
50+
{accordionOpen === 'group' ? <ChevronUpIcon /> : <ChevronDownIcon />}
51+
</Flex>
52+
</Button>
53+
</Accordion.AccordionTrigger>
54+
<Accordion.AccordionContent className="AccordionContent">
55+
<Grid columns="2" gap="2">
56+
<Box>
57+
<Text>Group</Text>
58+
<TextField.Root
59+
className="flex-1 w-full"
60+
value={mapMetadata.group ?? ''}
61+
placeholder="Group"
62+
disabled={!isEditing}
63+
onChange={e => onChange({group: e.target.value})}
64+
/>
65+
</Box>
66+
<Box>
67+
<Text>Tags (coming soon)</Text>
68+
<TextField.Root
69+
className="flex-1 w-full"
70+
value={mapMetadata.tags ?? ''}
71+
placeholder="Tags"
72+
disabled={true}
73+
/>
74+
</Box>
75+
</Grid>
76+
<Box>
77+
<Text>Comments</Text>
78+
<TextArea
79+
className="flex-1 w-full"
80+
value={mapMetadata.description ?? ''}
81+
placeholder="Comments or description"
82+
onChange={e => onChange({description: e.target.value})}
83+
disabled={!isEditing}
84+
/>
85+
</Box>
86+
</Accordion.AccordionContent>
87+
</Accordion.Item>
88+
</Accordion.Root>
6989
</Flex>
7090
);
7191
};

app/src/app/components/Toolbar/SaveShareModal/SaveShareModal.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ import {useSaveShareStore} from '@/app/store/saveShareStore';
99
import {Link1Icon} from '@radix-ui/react-icons';
1010
import {useMapMetadata} from '@/app/hooks/useMapMetadata';
1111
import {DEFAULT_MAP_METADATA} from '@/app/utils/language';
12+
import {useRouter} from 'next/navigation';
1213

1314
export const SaveShareModal: React.FC<{
1415
open: boolean;
1516
onClose: () => void;
1617
}> = ({open, onClose}) => {
18+
const router = useRouter();
1719
const mapDocument = useMapStore(state => state.mapDocument);
1820
const mapMetadata = useMapMetadata(mapDocument?.document_id);
1921
const [innerFormState, setInnerFormState] = useState<DocumentMetadata>(
@@ -23,28 +25,35 @@ export const SaveShareModal: React.FC<{
2325
const isEditing = useMapStore(
2426
state => state.mapDocument?.access === 'edit' && state.mapDocument?.status === 'checked_out'
2527
);
26-
2728
const generateLink = useSaveShareStore(state => state.generateLink);
29+
30+
const handleSave = async () => {
31+
const newMapDocument = await saveMap({...mapMetadata, ...innerFormState});
32+
if (newMapDocument) {
33+
router.push(`/map/edit/${newMapDocument.document_id}`);
34+
onClose();
35+
}
36+
};
37+
2838
const handleMetadataChange = (updates: Partial<DocumentMetadata>) =>
2939
setInnerFormState(prev => ({...prev, ...updates}));
30-
const handleSave = () => saveMap({...mapMetadata, ...innerFormState}).then(_ => onClose());
3140
useEffect(() => handleMetadataChange(mapMetadata ?? DEFAULT_MAP_METADATA), [mapMetadata]);
41+
3242
useEffect(() => {
3343
if (linkCopied) {
3444
setTimeout(() => setLinkCopied(false), 2000);
3545
}
3646
}, [linkCopied]);
37-
useEffect(() => {
38-
const isReadyToShare = mapMetadata?.draft_status === 'ready_to_share';
39-
const isPublicIdNotSet = mapDocument?.public_id !== -999;
40-
if (isReadyToShare && isPublicIdNotSet) {
41-
generateLink();
42-
}
43-
}, [mapDocument?.public_id, mapMetadata?.draft_status]);
4447

4548
return (
4649
<Dialog.Root open={open} onOpenChange={onClose}>
4750
<Dialog.Content>
51+
<Dialog.Title>Map Details</Dialog.Title>
52+
{/* Hidden description for screen readers */}
53+
<Dialog.Description className="invisible h-0 overflow-hidden">
54+
A dialog menu to update the map name, status (scratch work, in progress, or ready to
55+
share), advanced map details, and share link.
56+
</Dialog.Description>
4857
<Flex direction="column" gap="2">
4958
<MapDetailsSection
5059
mapMetadata={innerFormState}

app/src/app/components/Topbar/MapHeader.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,20 @@ import {MapStatus} from './MapStatus';
77
import {DocumentMetadata} from '@/app/utils/api/apiHandlers/types';
88
import {DEFAULT_MAP_METADATA} from '@/app/utils/language';
99
import {saveMap} from '@/app/utils/api/apiHandlers/saveMap';
10+
import {useRouter} from 'next/navigation';
1011

1112
export const MapHeader: React.FC = () => {
1213
const mapDocument = useMapStore(state => state.mapDocument);
1314
const mapMetadata = useMapMetadata(mapDocument?.document_id);
14-
15+
const router = useRouter();
1516
const handleMetadataChange = async (updates: Partial<DocumentMetadata>) => {
16-
await saveMap({
17+
const newDocument = await saveMap({
1718
...(mapMetadata || DEFAULT_MAP_METADATA),
1819
...updates,
1920
});
21+
if (newDocument) {
22+
router.push(`/map/edit/${newDocument.document_id}`);
23+
}
2024
};
2125

2226
return (

app/src/app/components/Topbar/MapStatus.tsx

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
'use client';
2-
import {Button, Text, Flex, IconButton, Box, Tooltip, Popover} from '@radix-ui/themes';
2+
import {Text, Flex, IconButton, Box, Tooltip, Popover, SegmentedControl} from '@radix-ui/themes';
33
import {useState} from 'react';
44
import {DocumentMetadata, DocumentObject, DraftStatus} from '@/app/utils/api/apiHandlers/types';
55
import {InProgressIcon, ScratchWorkIcon, ReadyIcon} from './Icons';
@@ -49,7 +49,7 @@ export const MapStatus: React.FC<{
4949
</Tooltip>
5050
</Box>
5151
</Popover.Trigger>
52-
<Popover.Content>
52+
<Popover.Content className="max-w-none">
5353
<Flex direction="column" gap="2">
5454
<MapStatusButtons draftStatus={draftStatus} onChange={handleChangeStatus} />
5555
</Flex>
@@ -63,21 +63,15 @@ export const MapStatusButtons: React.FC<{
6363
onChange: (draftStatus: DraftStatus) => Promise<void>;
6464
}> = ({draftStatus, onChange}) => {
6565
return (
66-
<>
66+
<SegmentedControl.Root value={draftStatus as string} onValueChange={onChange} size="2">
6767
{iconOrder.map(status => (
68-
<Button
69-
key={status}
70-
variant={status === draftStatus ? 'soft' : 'outline'}
71-
color="gray"
72-
onClick={() => onChange(status)}
73-
className="justify-start"
74-
>
68+
<SegmentedControl.Item key={status} value={status}>
7569
<Flex direction="row" gap="2" align="center" justify="start">
7670
{statusIcons[status]({})}
7771
<Text>{statusText[status]}</Text>
7872
</Flex>
79-
</Button>
73+
</SegmentedControl.Item>
8074
))}
81-
</>
75+
</SegmentedControl.Root>
8276
);
8377
};

app/src/app/store/mapStore.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,7 @@ export var useMapStore = createWithMiddlewares<MapStore>((set, get) => ({
789789
},
790790
shatterMappings: {},
791791
upsertUserMap: ({mapDocument, userMapData, userMapDocumentId}) => {
792+
if (!mapDocument?.document_id || mapDocument.access === 'read') return;
792793
let userMaps = [...get().userMaps];
793794
const mapViews = get().mapViews.data;
794795
if (mapDocument?.document_id && mapViews) {

app/src/app/store/saveShareStore.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ export const useSaveShareStore = create<SaveShareStore>((set, get) => ({
3333

3434
let shareableLink = new URL(`${window.location.origin}/map/${publicId}`);
3535
if (sharingMode === 'read') {
36-
shareableLink.pathname = `/map/view/${publicId}`;
36+
// Do nothing!
3737
} else if (sharingMode === 'edit' && password === null) {
38+
// Direct link to edit page
3839
shareableLink.pathname = `/map/edit/${mapDocument.document_id}`;
3940
} else {
41+
// Password protected link
4042
shareableLink.searchParams.set('pw', 'true');
4143
}
4244

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import axios from 'axios';
2+
import {useMapStore} from '@store/mapStore';
3+
import {DocumentObject} from './types';
4+
import {API_URL} from '../constants';
5+
6+
export const getLoadPlanFromShare = async ({
7+
password,
8+
public_id,
9+
}: {
10+
public_id: string | number;
11+
password?: string | null;
12+
}) => {
13+
const res = await axios.post<DocumentObject | null>(
14+
`${API_URL}/api/share/load_plan_from_share`,
15+
{
16+
user_id: useMapStore.getState().userID,
17+
public_id: public_id,
18+
password: password ?? null,
19+
},
20+
{headers: {'Content-Type': 'application/json'}}
21+
);
22+
return res.data; // failure is handled in mutations.ts
23+
};

app/src/app/utils/api/apiHandlers/saveMap.ts

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,43 @@ import {useMapStore} from '@/app/store/mapStore';
22
import {document, metadata} from '../mutations';
33
import {handleCreateBlankMetadataObject} from '../../helpers';
44
import {DocumentMetadata} from '../apiHandlers/types';
5-
import {useRouter} from 'next/navigation';
65

76
export const saveMap = async (latestMetadata: DocumentMetadata | null) => {
87
const {mapDocument, mapStatus, setMapStatus, upsertUserMap, setShareMapMessage} =
98
useMapStore.getState();
10-
const router = useRouter();
119
if (!mapDocument?.document_id) return;
1210

1311
if (mapStatus?.status === 'locked' || mapStatus?.access === 'read') {
1412
// atp doesn't matter that it's locked, even with pw; should be able to copy map
1513
// what we need is a pw entry field to open if there's a pw required in the url
16-
await document
17-
.mutate({
14+
try {
15+
const newDocumentData = await document.mutate({
1816
districtr_map_slug: mapDocument?.districtr_map_slug ?? '',
1917
metadata: latestMetadata ?? handleCreateBlankMetadataObject(),
2018
user_id: useMapStore.getState().userID,
2119
copy_from_doc:
2220
mapDocument?.access === 'read' && mapDocument?.public_id
2321
? mapDocument?.public_id
2422
: mapDocument?.document_id,
25-
})
26-
.then(data => {
27-
const updatedMetadata = latestMetadata ?? handleCreateBlankMetadataObject();
28-
metadata.mutate({document_id: data.document_id, metadata: updatedMetadata});
29-
const updatedMapDoc = {...data, map_metadata: {...updatedMetadata}};
30-
31-
upsertUserMap({documentId: data.document_id, mapDocument: updatedMapDoc});
32-
// TODO Neither of these two settings seem to properly tell the client that the document can be edited
33-
setMapStatus({
34-
access: updatedMapDoc.access,
35-
status: updatedMapDoc.status,
36-
});
23+
});
3724

38-
router.push(`/map/edit/${data.document_id}`);
39-
})
40-
.catch(err => {
41-
setShareMapMessage(
42-
`Unable to copy map. Reference: ${mapDocument?.document_id}|${mapDocument?.districtr_map_slug}`
43-
);
25+
const updatedMetadata = latestMetadata ?? handleCreateBlankMetadataObject();
26+
metadata.mutate({document_id: newDocumentData.document_id, metadata: updatedMetadata});
27+
const updatedMapDoc = {...newDocumentData, map_metadata: {...updatedMetadata}};
28+
upsertUserMap({documentId: newDocumentData.document_id, mapDocument: updatedMapDoc});
29+
// TODO Neither of these two settings seem to properly tell the client that the document can be edited
30+
setMapStatus({
31+
access: updatedMapDoc.access,
32+
status: updatedMapDoc.status,
4433
});
34+
return newDocumentData;
35+
} catch (err) {
36+
console.error('Error saving map: ', err);
37+
setShareMapMessage(
38+
`Unable to copy map. Reference: ${mapDocument?.document_id}|${mapDocument?.districtr_map_slug}`
39+
);
40+
return null;
41+
}
4542
} else {
4643
await metadata.mutate({
4744
document_id: mapDocument?.document_id,
@@ -54,5 +51,6 @@ export const saveMap = async (latestMetadata: DocumentMetadata | null) => {
5451
map_metadata: latestMetadata ?? handleCreateBlankMetadataObject(),
5552
},
5653
});
54+
return mapDocument;
5755
}
5856
};

0 commit comments

Comments
 (0)