Skip to content

Commit 559539a

Browse files
committed
refactor: redesign settings page and improve code quality
Edit Project Page: - Redesign with 3-card layout (Project Details, Team Access, Danger Zone) - Add Project ID badge in header with copy button - Team Access: show current user with (you) badge, move Add button to header - Add icon edit modal with hover overlay Mobile Navigation: - Add MobileNavDrawer component - Improve responsive header and layout Code Quality Fixes: - Fix memory leak in icon preview (revoke blob URLs) - Fix cache key mismatch in project mutations (use invalidateQueries) - Fix empty name fallback in user avatar - Rename EditProjectParticipan to EditProjectParticipant - Fix typo isMathLocation to isMatchLocation - Add projectId null guard
1 parent 28ae1da commit 559539a

File tree

32 files changed

+884
-430
lines changed

32 files changed

+884
-430
lines changed

src/app/providers/with-chakra/theme/breakpoints/breakpoints.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export default {
22
sm: '320px',
33
md: '640px',
4+
tablet: '800px',
45
lg: '1024px',
56
xl: '1240px',
67
'2xl': '1536px'

src/entities/project/ui/AddProjectParticipantModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const AddProjectParticipantModal: FC<{
3737
const formId = 'add-project-participant-form';
3838
const projectId = useProjectId();
3939

40-
const { data: participants = [] } = useProjectParticipantsQuery(projectId, undefined);
40+
const { data: participants = [] } = useProjectParticipantsQuery(projectId);
4141
const addParticipant = useAddProjectParticipantMutation(projectId);
4242

4343
const { handleSubmit, register, formState, reset } = useForm<AddProjectParticipantFormValues>();

src/entities/project/ui/EditProjectParticipan.tsx

Lines changed: 0 additions & 131 deletions
This file was deleted.
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import {
2+
Avatar,
3+
Box,
4+
BoxProps,
5+
Button,
6+
Flex,
7+
FlexProps,
8+
Text,
9+
useDisclosure
10+
} from '@chakra-ui/react';
11+
import { FC } from 'react';
12+
import { DTOParticipant, IconButton, PlusIcon16, Span, TrashIcon16 } from 'src/shared';
13+
import { ConfirmationDialog } from 'src/entities';
14+
import { useProjectId } from 'src/shared/contexts/ProjectContext';
15+
import {
16+
useDeleteProjectParticipantMutation,
17+
useProjectParticipantsQuery
18+
} from 'src/shared/queries/projects';
19+
import { useUserQuery } from 'src/entities/user/queries';
20+
21+
const getParticipantName = (participant: DTOParticipant) => {
22+
return [participant.first_name, participant.last_name].filter(Boolean).join(' ') || 'User';
23+
};
24+
25+
const RemoveConfirmationModal: FC<{
26+
isOpen: boolean;
27+
onClose: () => void;
28+
participant: DTOParticipant;
29+
projectId: number;
30+
}> = ({ isOpen, onClose, participant, projectId }) => {
31+
const deleteParticipant = useDeleteProjectParticipantMutation(projectId);
32+
33+
const handleDelete = () => {
34+
deleteParticipant.mutate(participant.id, {
35+
onSuccess: onClose
36+
});
37+
};
38+
39+
return (
40+
<ConfirmationDialog
41+
isOpen={isOpen}
42+
onClose={onClose}
43+
onConfirm={handleDelete}
44+
title="Confirm you want to remove this user?"
45+
description={
46+
<Text>
47+
Once removed, <b>{getParticipantName(participant)}</b> will no longer have
48+
direct access to this project.
49+
</Text>
50+
}
51+
confirmButtonText="Remove"
52+
/>
53+
);
54+
};
55+
56+
const Participant: FC<
57+
FlexProps & {
58+
participant: DTOParticipant;
59+
projectId: number;
60+
isCurrentUser: boolean;
61+
}
62+
> = ({ participant, projectId, isCurrentUser, ...rest }) => {
63+
const { isOpen, onOpen, onClose } = useDisclosure();
64+
65+
const name = getParticipantName(participant);
66+
67+
return (
68+
<Flex
69+
align="center"
70+
gap={2}
71+
h={12}
72+
fontWeight={600}
73+
borderRadius={8}
74+
_hover={{ bg: 'background.contentTint' }}
75+
{...rest}
76+
>
77+
<Avatar ml={2} name={name} size="sm" src={participant.avatar} />
78+
<Box fontWeight={600}>
79+
{name}
80+
{isCurrentUser && (
81+
<Span color="text.secondary" ml={1}>
82+
(you)
83+
</Span>
84+
)}
85+
{!isCurrentUser && (
86+
<Span color="text.secondary" fontSize={14} ml={1}>
87+
#{participant.id}
88+
</Span>
89+
)}
90+
</Box>
91+
92+
{!isCurrentUser && (
93+
<>
94+
<IconButton
95+
aria-label="Remove"
96+
icon={<TrashIcon16 />}
97+
ml="auto"
98+
onClick={onOpen}
99+
p={3}
100+
/>
101+
<RemoveConfirmationModal
102+
isOpen={isOpen}
103+
onClose={onClose}
104+
participant={participant}
105+
projectId={projectId}
106+
/>
107+
</>
108+
)}
109+
</Flex>
110+
);
111+
};
112+
113+
export const EditProjectParticipant: FC<
114+
BoxProps & {
115+
onAddParticipant: () => void;
116+
}
117+
> = ({ onAddParticipant, ...rest }) => {
118+
const projectId = useProjectId();
119+
const { data: user } = useUserQuery();
120+
const { data: participants = [] } = useProjectParticipantsQuery(projectId);
121+
122+
// Sort: current user first, then others alphabetically
123+
const sortedParticipants = [...participants].sort((a, b) => {
124+
const aIsCurrentUser = a.id === user?.id;
125+
const bIsCurrentUser = b.id === user?.id;
126+
127+
if (aIsCurrentUser) return -1;
128+
if (bIsCurrentUser) return 1;
129+
130+
const nameA = getParticipantName(a).toLowerCase();
131+
const nameB = getParticipantName(b).toLowerCase();
132+
return nameA.localeCompare(nameB);
133+
});
134+
135+
return (
136+
<Box {...rest}>
137+
{/* Header with title and Add button */}
138+
<Flex align="center" justify="space-between" mb={4}>
139+
<Text textStyle="label1">Team Access</Text>
140+
<Button
141+
leftIcon={<PlusIcon16 />}
142+
onClick={onAddParticipant}
143+
size="sm"
144+
variant="secondary"
145+
>
146+
<Box as="span" display={{ base: 'none', sm: 'inline' }}>
147+
Add user
148+
</Box>
149+
</Button>
150+
</Flex>
151+
152+
{/* Participants list */}
153+
{sortedParticipants.length > 0 && projectId && (
154+
<Box>
155+
{sortedParticipants.map(participant => (
156+
<Participant
157+
key={participant.id}
158+
isCurrentUser={participant.id === user?.id}
159+
participant={participant}
160+
projectId={projectId}
161+
/>
162+
))}
163+
</Box>
164+
)}
165+
</Box>
166+
);
167+
};

0 commit comments

Comments
 (0)