Skip to content

Commit b049bf4

Browse files
authored
Merge pull request #95 from refactor-group/88-feature-add-member-management-page-delete-users
Add Delete User
2 parents d6a3af9 + dfa4cb3 commit b049bf4

File tree

15 files changed

+857
-407
lines changed

15 files changed

+857
-407
lines changed

package-lock.json

Lines changed: 638 additions & 399 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/organizations/[id]/members/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ export default function MembersPage({
6666

6767
return (
6868
<div className="container mx-auto p-6 space-y-8">
69-
<h1 className="text-3xl font-bold">Members</h1>
7069
<MemberContainer
7170
users={users}
7271
relationships={relationships}
Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,74 @@
1+
import { useOrganizationStateStore } from "@/lib/providers/organization-state-store-provider";
2+
import { useAuthStore } from "@/lib/providers/auth-store-provider";
3+
import { useUserMutation } from "@/lib/api/organizations/users";
4+
import { Button } from "@/components/ui/button";
5+
import { Trash2 } from "lucide-react";
6+
import { CoachingRelationshipWithUserNames } from "@/types/coaching_relationship_with_user_names";
7+
import { OrganizationStateStore } from "@/lib/stores/organization-state-store";
8+
import { AuthStore } from "@/lib/stores/auth-store";
9+
import { Id } from "@/types/general";
10+
111
interface MemberCardProps {
212
firstName: string;
313
lastName: string;
4-
email?: string;
14+
email: string;
15+
userId: Id;
16+
userRelationships: CoachingRelationshipWithUserNames[];
17+
onRefresh: () => void;
518
}
619

7-
export function MemberCard({ firstName, lastName, email }: MemberCardProps) {
20+
export function MemberCard({
21+
firstName,
22+
lastName,
23+
email,
24+
userId,
25+
userRelationships,
26+
onRefresh,
27+
}: MemberCardProps) {
28+
const currentOrganizationId = useOrganizationStateStore(
29+
(state: OrganizationStateStore) => state.currentOrganizationId
30+
);
31+
const { userSession } = useAuthStore((state: AuthStore) => state);
32+
const { deleteNested: deleteUser } = useUserMutation(currentOrganizationId);
33+
34+
// Check if current user is a coach in any of this user's relationships
35+
// and make sure we can't delete ourselves
36+
const canDeleteUser = userRelationships.some(
37+
(rel) => rel.coach_id === userSession.id && userId !== userSession.id
38+
);
39+
40+
const handleDelete = async () => {
41+
if (!confirm("Are you sure you want to delete this member?")) {
42+
return;
43+
}
44+
45+
try {
46+
await deleteUser(currentOrganizationId, userId);
47+
onRefresh();
48+
} catch (error) {
49+
console.error("Error deleting user:", error);
50+
// TODO: Show an error toast here once we start using toasts for showing operation results.
51+
}
52+
};
53+
854
return (
9-
<div className="flex items-center p-4 hover:bg-accent/50 transition-colors cursor-pointer">
55+
<div className="flex items-center p-4 hover:bg-accent/50 transition-colors">
1056
<div className="flex-1">
1157
<h3 className="font-medium">
1258
{firstName} {lastName}
1359
</h3>
1460
{email && <p className="text-sm text-muted-foreground">{email}</p>}
1561
</div>
62+
{canDeleteUser && (
63+
<Button
64+
variant="ghost"
65+
size="icon"
66+
onClick={handleDelete}
67+
className="text-destructive hover:text-destructive"
68+
>
69+
<Trash2 className="h-4 w-4" />
70+
</Button>
71+
)}
1672
</div>
1773
);
1874
}

src/components/ui/members/member-container.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export function MemberContainer({
5252
return (
5353
<div className="space-y-6">
5454
<div className="flex justify-between items-center">
55-
<h2 className="text-2xl font-semibold">Members</h2>
55+
<h3 className="text-2xl font-semibold">Members</h3>
5656
{/* Only show the button if user is a coach to _some_ user within the
5757
scope of the organization. We may come back and add this directly to user
5858
data. */}
@@ -63,7 +63,11 @@ export function MemberContainer({
6363
/>
6464
)}
6565
</div>
66-
<MemberList users={associatedUsers} />
66+
<MemberList
67+
users={associatedUsers}
68+
relationships={userRelationships}
69+
onRefresh={onRefresh}
70+
/>
6771
</div>
6872
);
6973
}

src/components/ui/members/member-list.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
11
import { Card, CardContent } from "@/components/ui/card";
22
import { User } from "@/types/user";
33
import { MemberCard } from "./member-card";
4+
import { CoachingRelationshipWithUserNames } from "@/types/coaching_relationship_with_user_names";
5+
import { Id } from "@/types/general";
46

57
interface MemberListProps {
68
users: User[];
9+
relationships: CoachingRelationshipWithUserNames[];
10+
onRefresh: () => void;
711
}
812

9-
export function MemberList({ users }: MemberListProps) {
13+
export function MemberList({
14+
users,
15+
relationships,
16+
onRefresh,
17+
}: MemberListProps) {
18+
// Create a mapping of user IDs to their associated relationships
19+
const userRelationshipsMap = users.reduce((accumulator_map, user) => {
20+
accumulator_map[user.id] = relationships.filter(
21+
(rel) => rel.coach_id === user.id || rel.coachee_id === user.id
22+
);
23+
return accumulator_map;
24+
}, {} as Record<Id, CoachingRelationshipWithUserNames[]>);
25+
1026
return (
1127
<Card className="w-full">
1228
<CardContent className="p-6">
@@ -17,6 +33,9 @@ export function MemberList({ users }: MemberListProps) {
1733
firstName={user.first_name}
1834
lastName={user.last_name}
1935
email={user.email}
36+
userId={user.id}
37+
userRelationships={userRelationshipsMap[user.id]}
38+
onRefresh={onRefresh}
2039
/>
2140
))}
2241
</div>

src/lib/api/actions.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,17 @@ export const ActionApi = {
6565
*/
6666
delete: async (id: Id): Promise<Action> =>
6767
EntityApi.deleteFn<null, Action>(`${ACTIONS_BASEURL}/${id}`),
68+
69+
/**
70+
* Deletes an action nested under another entity (foreign key relationship).
71+
*
72+
* @param entityId The ID of the entity under which to delete the action
73+
* @param actionId The ID of the action to delete
74+
* @returns Promise resolving to the deleted Action object
75+
*/
76+
deleteNested: async (_entityId: Id, _actionId: Id): Promise<Action> => {
77+
throw new Error("Delete nested operation not implemented");
78+
},
6879
};
6980

7081
/**
@@ -174,5 +185,6 @@ export const useActionMutation = () => {
174185
createNested: ActionApi.createNested,
175186
update: ActionApi.update,
176187
delete: ActionApi.delete,
188+
deleteNested: ActionApi.deleteNested,
177189
});
178190
};

src/lib/api/agreements.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ export const AgreementApi = {
6969
*/
7070
delete: async (id: Id): Promise<Agreement> =>
7171
EntityApi.deleteFn<null, Agreement>(`${AGREEMENTS_BASEURL}/${id}`),
72+
73+
/**
74+
* Deletes an agreement nested under another entity (foreign key relationship).
75+
*
76+
* @param entityId The ID of the entity under which to delete the agreement
77+
* @param agreementId The ID of the agreement to delete
78+
* @returns Promise resolving to the deleted Agreement object
79+
*/
80+
deleteNested: async (_entityId: Id, agreementId: Id): Promise<Agreement> => {
81+
throw new Error("Delete nested operation not implemented");
82+
},
7283
};
7384

7485
/**
@@ -175,5 +186,6 @@ export const useAgreementMutation = () => {
175186
createNested: AgreementApi.createNested,
176187
update: AgreementApi.update,
177188
delete: AgreementApi.delete,
189+
deleteNested: AgreementApi.deleteNested,
178190
});
179191
};

src/lib/api/coaching-relationships.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,20 @@ export const CoachingRelationshipApi = {
9191
delete: async (_id: Id) => {
9292
throw new Error("Delete operation not implemented");
9393
},
94+
95+
/**
96+
* Deletes a coaching relationship nested under another entity (foreign key relationship).
97+
*
98+
* @param entityId The ID of the entity under which to delete the coaching relationship
99+
* @param relationshipId The ID of the coaching relationship to delete
100+
* @returns Promise resolving to the deleted CoachingRelationshipWithUserNames object
101+
*/
102+
deleteNested: async (
103+
_organizationId: Id,
104+
_relationshipId: Id
105+
): Promise<CoachingRelationshipWithUserNames> => {
106+
throw new Error("Delete nested operation not implemented");
107+
},
94108
};
95109

96110
/**
@@ -185,6 +199,7 @@ export const useCoachingRelationshipMutation = (organizationId: Id) => {
185199
createNested: CoachingRelationshipApi.createNested,
186200
update: CoachingRelationshipApi.update,
187201
delete: CoachingRelationshipApi.delete,
202+
deleteNested: CoachingRelationshipApi.deleteNested,
188203
}
189204
);
190205
};

src/lib/api/coaching-sessions.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,20 @@ export const CoachingSessionApi = {
9595
EntityApi.deleteFn<null, CoachingSession>(
9696
`${COACHING_SESSIONS_BASEURL}/${id}`
9797
),
98+
99+
/**
100+
* Deletes a coaching session nested under another entity (foreign key relationship).
101+
*
102+
* @param entityId The ID of the entity under which to delete the coaching session
103+
* @param coachingSessionId The ID of the coaching session to delete
104+
* @returns Promise resolving to the deleted CoachingSession object
105+
*/
106+
deleteNested: async (
107+
_entityId: Id,
108+
coachingSessionId: Id
109+
): Promise<CoachingSession> => {
110+
throw new Error("Delete nested operation not implemented");
111+
},
98112
};
99113

100114
/**
@@ -195,6 +209,7 @@ export const useCoachingSessionMutation = () => {
195209
createNested: CoachingSessionApi.createNested,
196210
update: CoachingSessionApi.update,
197211
delete: CoachingSessionApi.delete,
212+
deleteNested: CoachingSessionApi.deleteNested,
198213
}
199214
);
200215
};

src/lib/api/entity-api.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ export namespace EntityApi {
374374
createNested: (id: Id, entity: T) => Promise<U>;
375375
update: (id: Id, entity: T) => Promise<U>;
376376
delete: (id: Id) => Promise<U>;
377+
deleteNested: (entityId: Id, nestedEntityId: Id) => Promise<U>;
377378
}
378379
) => {
379380
const [isLoading, setIsLoading] = useState(false);
@@ -444,6 +445,17 @@ export namespace EntityApi {
444445
* @returns Promise resolving to the deleted entity
445446
*/
446447
delete: (id: Id) => executeWithState(() => api.delete(id)),
448+
449+
/**
450+
* Deletes an entity nested under another entity (foreign key relationship).
451+
*
452+
* @param entityId The entity's id under which to delete entity
453+
* @param nestedEntityId The nested entity's id to delete
454+
* @returns Promise resolving to the deleted entity
455+
*/
456+
deleteNested: (entityId: Id, nestedEntityId: Id) =>
457+
executeWithState(() => api.deleteNested(entityId, nestedEntityId)),
458+
447459
/** Indicates if any operation is currently in progress */
448460
isLoading,
449461
/** Contains the error if the last operation failed, null otherwise */

0 commit comments

Comments
 (0)