Skip to content

Commit 9018963

Browse files
authored
Merge branch 'main' into add_tiptap_doc_loading_indicator
2 parents 628913f + c626022 commit 9018963

13 files changed

+146
-47
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { siteConfig } from "@/site.config.ts";
55
import { SiteHeader } from "@/components/ui/site-header";
66
import { AppSidebar } from "@/components/ui/app-sidebar";
77
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
8+
import { Toaster } from "@/components/ui/sonner";
89
export const metadata: Metadata = {
910
title: siteConfig.name,
1011
description: "Manage coaching members",
@@ -22,6 +23,7 @@ export default function MembersLayout({
2223
<SidebarInset>
2324
<SiteHeader />
2425
<main className="flex-1 p-6">{children}</main>
26+
<Toaster />
2527
</SidebarInset>
2628
</div>
2729
</SidebarProvider>

src/components/ui/coaching-relationship-selector.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export default function CoachingRelationshipSelector({
7575
(action) => action
7676
);
7777

78-
const { setIsCoach } = useAuthStore((state) => state);
78+
const { setIsCurrentCoach } = useAuthStore((state) => state);
7979

8080
const handleSetCoachingRelationship = (relationshipId: Id) => {
8181
setCurrentCoachingRelationshipId(relationshipId);
@@ -91,7 +91,7 @@ export default function CoachingRelationshipSelector({
9191
? getCurrentCoachingRelationship(currentCoachingRelationshipId)
9292
: null;
9393
if (currentRelationship) {
94-
setIsCoach(currentRelationship.coach_id);
94+
setIsCurrentCoach(currentRelationship.coach_id);
9595
}
9696
}, [currentCoachingRelationshipId]);
9797

src/components/ui/coaching-session.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const CoachingSession: React.FC<CoachingSessionProps> = ({
3232
const { setCurrentCoachingSessionId } = useCoachingSessionStateStore(
3333
(state) => state
3434
);
35-
const { isCoach } = useAuthStore((state) => state);
35+
const { isCurrentCoach } = useAuthStore((state) => state);
3636

3737
return (
3838
<Card>
@@ -64,7 +64,7 @@ const CoachingSession: React.FC<CoachingSessionProps> = ({
6464
<DropdownMenuItem onClick={onUpdate}>
6565
Edit
6666
</DropdownMenuItem>
67-
{isCoach && (
67+
{isCurrentCoach && (
6868
<DropdownMenuItem onClick={onDelete} className="text-destructive">
6969
Delete
7070
</DropdownMenuItem>

src/components/ui/dashboard/add-entities.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export default function AddEntities({
1818
}: AddEntitiesProps) {
1919
const router = useRouter();
2020
const { currentOrganizationId } = useOrganizationStateStore((state) => state);
21-
const { isCoach } = useAuthStore((state) => state);
21+
const { isCurrentCoach } = useAuthStore((state) => state);
2222

2323
const onMemberButtonClicked = () => {
2424
router.push(`/organizations/${currentOrganizationId}/members`);
@@ -31,12 +31,12 @@ export default function AddEntities({
3131
</h3>
3232
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
3333
<AddCoachingSessionButton
34-
disabled={!isCoach || !currentOrganizationId}
34+
disabled={!isCurrentCoach || !currentOrganizationId}
3535
onClick={onCreateSession}
3636
/>
3737

3838
<AddMemberButton
39-
disabled={!isCoach || !currentOrganizationId}
39+
disabled={!isCurrentCoach || !currentOrganizationId}
4040
onClick={onMemberButtonClicked}
4141
/>
4242
</div>

src/components/ui/members/add-member-dialog.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { Input } from "@/components/ui/input";
1717
import { useUserMutation } from "@/lib/api/organizations/users";
1818
import { NewUser } from "@/types/user";
1919
import { useOrganizationStateStore } from "@/lib/providers/organization-state-store-provider";
20+
import { toast } from "sonner";
2021

2122
interface AddMemberDialogProps {
2223
open: boolean;
@@ -85,10 +86,11 @@ export function AddMemberDialog({
8586
});
8687
setPasswordError("");
8788
onMemberAdded();
89+
toast.success(`New Member ${formData.firstName} ${formData.lastName} added successfully`);
8890
onOpenChange(false);
8991
} catch (error) {
9092
console.error("Error creating user:", error);
91-
// Handle error appropriately
93+
toast.error("There was an error adding the member");
9294
}
9395
};
9496

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

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { MoreHorizontal, Trash2 } from "lucide-react";
1414
import {
1515
Dialog,
1616
DialogContent,
17+
DialogDescription,
1718
DialogHeader,
1819
DialogFooter,
1920
DialogTitle,
@@ -25,11 +26,13 @@ import {
2526
SelectContent,
2627
SelectItem,
2728
} from "@/components/ui/select";
28-
import { CoachingRelationshipWithUserNames } from "@/types/coaching_relationship_with_user_names";
29+
import { CoachingRelationshipWithUserNames } from "@/types/coaching_relationship";
2930
import { OrganizationStateStore } from "@/lib/stores/organization-state-store";
3031
import { AuthStore } from "@/lib/stores/auth-store";
3132
import { Id } from "@/types/general";
3233
import { User, Role } from "@/types/user";
34+
import { useCoachingRelationshipMutation } from "@/lib/api/coaching-relationships";
35+
import { toast } from "sonner";
3336

3437
interface MemberCardProps {
3538
firstName: string;
@@ -41,6 +44,12 @@ interface MemberCardProps {
4144
users: User[];
4245
}
4346

47+
interface Member {
48+
id: Id;
49+
first_name: string;
50+
last_name: string;
51+
}
52+
4453
export function MemberCard({
4554
firstName,
4655
lastName,
@@ -53,27 +62,79 @@ export function MemberCard({
5362
const currentOrganizationId = useOrganizationStateStore(
5463
(state: OrganizationStateStore) => state.currentOrganizationId
5564
);
56-
const { userSession } = useAuthStore((state: AuthStore) => state);
57-
const { deleteNested: deleteUser } = useUserMutation(currentOrganizationId);
65+
const { isACoach, userSession } = useAuthStore((state: AuthStore) => state);
66+
const { error: deleteError, deleteNested: deleteUser } = useUserMutation(currentOrganizationId);
67+
const { error: createError, createNested: createRelationship } = useCoachingRelationshipMutation(currentOrganizationId);
68+
69+
console.log("is a coach", isACoach);
5870

5971
// Check if current user is a coach in any of this user's relationships
6072
// and make sure we can't delete ourselves. Admins can delete any user.
61-
const canDeleteUser = userRelationships?.some(
73+
const canDeleteUser = (userRelationships?.some(
6274
(rel) => rel.coach_id === userSession.id && userId !== userSession.id
63-
) || (userSession.role === Role.Admin && userSession.id !== userId);
75+
) || (userSession.role === Role.Admin)) && userSession.id !== userId;
6476

6577
const handleDelete = async () => {
6678
if (!confirm("Are you sure you want to delete this member?")) {
6779
return;
6880
}
81+
await deleteUser(currentOrganizationId, userId);
82+
onRefresh();
6983

70-
try {
71-
await deleteUser(currentOrganizationId, userId);
84+
if (deleteError) {
85+
console.error("Error deleting member:", deleteError);
86+
toast.error("Error deleting member");
7287
onRefresh();
73-
} catch (error) {
74-
console.error("Error deleting user:", error);
75-
// TODO: Show an error toast here once we start using toasts for showing operation results.
88+
return;
7689
}
90+
toast.success("Member deleted successfully");
91+
onRefresh();
92+
};
93+
94+
const handleAssignMember = (val: string) => {
95+
const user = users.find((m) => m.id === val);
96+
if (!user) return;
97+
const member: Member = {
98+
id: user.id,
99+
first_name: user.first_name,
100+
last_name: user.last_name,
101+
};
102+
setAssignedMember(member);
103+
};
104+
105+
// Placeholder – actual UI flows will be implemented later
106+
const [assignDialogOpen, setAssignDialogOpen] = useState(false);
107+
const [assignMode, setAssignMode] = useState<"coach" | "coachee">("coach");
108+
const [selectedMember, setSelectedMember] = useState<Member | null>(null);
109+
const [assignedMember, setAssignedMember] = useState<Member | null>(null);
110+
111+
const handleCreateCoachingRelationship = () => {
112+
if (!selectedMember || !assignedMember) return;
113+
114+
if (assignMode === "coach") {
115+
console.log("Assign", selectedMember.id, "as coach for", userId);
116+
createRelationship(currentOrganizationId, {
117+
coach_id: assignedMember.id,
118+
coachee_id: selectedMember.id,
119+
});
120+
} else {
121+
console.log("Assign", selectedMember.id, "as coachee for", userId);
122+
createRelationship(currentOrganizationId, {
123+
coach_id: selectedMember.id,
124+
coachee_id: assignedMember.id,
125+
});
126+
}
127+
128+
if (createError) {
129+
toast.error(`Error assigning ${assignMode}`);
130+
return;
131+
}
132+
133+
toast.success(`Successfully assigned ${assignedMember.first_name} ${assignedMember.last_name} as ${assignMode} for ${selectedMember.first_name} ${selectedMember.last_name}`);
134+
onRefresh();
135+
setAssignDialogOpen(false);
136+
setSelectedMember(null);
137+
setAssignedMember(null);
77138
};
78139

79140
// Placeholder – actual UI flows will be implemented later
@@ -101,6 +162,7 @@ export function MemberCard({
101162
</h3>
102163
{email && <p className="text-sm text-muted-foreground">{email}</p>}
103164
</div>
165+
{(isACoach || userSession.role === Role.Admin) && (
104166
<DropdownMenu>
105167
<DropdownMenuTrigger asChild>
106168
<Button variant="ghost" size="icon" className="text-muted-foreground">
@@ -114,6 +176,7 @@ export function MemberCard({
114176
onClick={() => {
115177
setAssignMode("coach");
116178
setAssignDialogOpen(true);
179+
setSelectedMember({id: userId, first_name: firstName, last_name: lastName});
117180
}}
118181
>
119182
Assign Coach
@@ -122,6 +185,7 @@ export function MemberCard({
122185
onClick={() => {
123186
setAssignMode("coachee");
124187
setAssignDialogOpen(true);
188+
setSelectedMember({id: userId, first_name: firstName, last_name: lastName});
125189
}}
126190
>
127191
Assign Coachee
@@ -141,6 +205,7 @@ export function MemberCard({
141205
)}
142206
</DropdownMenuContent>
143207
</DropdownMenu>
208+
)}
144209

145210
{/* Assign Coach/Coachee Modal */}
146211
<Dialog open={assignDialogOpen} onOpenChange={setAssignDialogOpen}>
@@ -149,10 +214,13 @@ export function MemberCard({
149214
<DialogTitle>
150215
{assignMode === "coach" ? "Assign Coach" : "Assign Coachee"}
151216
</DialogTitle>
217+
<DialogDescription>
218+
Select a member to be their {assignMode === "coach" ? "coach" : "coachee"}
219+
</DialogDescription>
152220
</DialogHeader>
153221
<Select
154-
onValueChange={(val) => setSelectedMemberId(val as Id)}
155-
value={selectedMemberId ?? undefined}
222+
onValueChange={(val) => handleAssignMember(val)}
223+
value={assignedMember?.id?.toString()}
156224
>
157225
<SelectTrigger className="w-full">
158226
<SelectValue placeholder="Select a member" />

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import { MemberList } from "./member-list";
22
import { AddMemberButton } from "./add-member-button";
33
import { User } from "@/types/user";
44
import { Role } from "@/types/user";
5-
import { CoachingRelationshipWithUserNames } from "@/types/coaching_relationship_with_user_names";
5+
import { CoachingRelationshipWithUserNames } from "@/types/coaching_relationship";
66
import { UserSession } from "@/types/user-session";
7+
import { useAuthStore } from "@/lib/providers/auth-store-provider";
8+
import { useEffect } from "react";
79

810
interface MemberContainerProps {
911
users: User[];
@@ -23,10 +25,14 @@ export function MemberContainer({
2325
/// Force the AddMemberDialog to open
2426
openAddMemberDialog,
2527
}: MemberContainerProps) {
28+
const { setIsACoach, isACoach } = useAuthStore((state) => state);
29+
2630
// Check if current user is a coach in any relationship
27-
const isCoachInAnyRelationship = relationships.some(
28-
(rel) => rel.coach_id === userSession.id
29-
);
31+
useEffect(() => {
32+
setIsACoach(
33+
relationships.some((rel) => rel.coach_id === userSession.id)
34+
);
35+
}, [relationships, userSession.id, setIsACoach]);
3036

3137
// Find relationships where current user is either coach or coachee
3238
const userRelationships = relationships.filter(
@@ -58,7 +64,7 @@ export function MemberContainer({
5864
{/* Only show the button if user is a coach to _some_ user within the
5965
scope of the organization or if user is an admin. We may come back and add this directly to user
6066
data. */}
61-
{(isCoachInAnyRelationship || userSession.role === Role.Admin) && (
67+
{(isACoach || userSession.role === Role.Admin) && (
6268
<AddMemberButton
6369
onMemberAdded={onRefresh}
6470
openAddMemberDialog={openAddMemberDialog}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
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";
4+
import { CoachingRelationshipWithUserNames } from "@/types/coaching_relationship";
55
import { Id } from "@/types/general";
66

77
interface MemberListProps {

src/lib/api/coaching-relationships.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
import { siteConfig } from "@/site.config";
44
import { Id } from "@/types/general";
55
import {
6+
NewCoachingRelationship,
67
CoachingRelationshipWithUserNames,
78
defaultCoachingRelationshipWithUserNames,
8-
} from "@/types/coaching_relationship_with_user_names";
9+
} from "@/types/coaching_relationship";
910
import { EntityApi } from "./entity-api";
1011

1112
const ORGANIZATIONS_BASEURL: string = `${siteConfig.env.backendServiceURL}/organizations`;
@@ -53,7 +54,7 @@ export const CoachingRelationshipApi = {
5354
* Unimplemented
5455
*/
5556
create: async (
56-
_relationship: CoachingRelationshipWithUserNames
57+
_relationship: NewCoachingRelationship
5758
): Promise<CoachingRelationshipWithUserNames> => {
5859
throw new Error("Create operation not implemented");
5960
},
@@ -67,10 +68,10 @@ export const CoachingRelationshipApi = {
6768
*/
6869
createNested: async (
6970
organizationId: Id,
70-
entity: CoachingRelationshipWithUserNames
71+
entity: NewCoachingRelationship
7172
): Promise<CoachingRelationshipWithUserNames> => {
7273
return EntityApi.createFn<
73-
CoachingRelationshipWithUserNames,
74+
NewCoachingRelationship,
7475
CoachingRelationshipWithUserNames
7576
>(
7677
`${ORGANIZATIONS_BASEURL}/${organizationId}/${COACHING_RELATIONSHIPS_BASEURL}`,
@@ -81,7 +82,7 @@ export const CoachingRelationshipApi = {
8182
/**
8283
* Unimplemented
8384
*/
84-
update: async (_id: Id, entity: CoachingRelationshipWithUserNames) => {
85+
update: async (_id: Id, entity: NewCoachingRelationship) => {
8586
throw new Error("Update operation not implemented");
8687
},
8788

@@ -192,7 +193,7 @@ export const useCoachingRelationship = (
192193
* Provides methods to create, update, and delete coaching relationships.
193194
*/
194195
export const useCoachingRelationshipMutation = (organizationId: Id) => {
195-
return EntityApi.useEntityMutation<CoachingRelationshipWithUserNames>(
196+
return EntityApi.useEntityMutation<NewCoachingRelationship, CoachingRelationshipWithUserNames>(
196197
`${ORGANIZATIONS_BASEURL}/${organizationId}/${COACHING_RELATIONSHIPS_BASEURL}`,
197198
{
198199
create: CoachingRelationshipApi.create,

0 commit comments

Comments
 (0)