Skip to content

Commit 133ee7e

Browse files
authored
Merge pull request #131 from refactor-group/add_tiptap_doc_loading_indicator
Add tiptap doc loading indicator.
2 parents c626022 + 4b85cc9 commit 133ee7e

File tree

6 files changed

+179
-58
lines changed

6 files changed

+179
-58
lines changed

package-lock.json

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

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@radix-ui/react-label": "^2.1.1",
2222
"@radix-ui/react-navigation-menu": "^1.2.3",
2323
"@radix-ui/react-popover": "^1.1.4",
24+
"@radix-ui/react-progress": "^1.1.7",
2425
"@radix-ui/react-scroll-area": "^1.2.2",
2526
"@radix-ui/react-select": "^2.1.4",
2627
"@radix-ui/react-separator": "^1.1.2",

src/components/ui/coaching-sessions/coaching-notes.tsx

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { useCollaborationToken } from "@/lib/api/collaboration-token";
88
import { useAuthStore } from "@/lib/providers/auth-store-provider";
99
import { useCoachingSessionStateStore } from "@/lib/providers/coaching-session-state-store-provider";
1010
import { Extensions } from "@/components/ui/coaching-sessions/coaching-notes/extensions";
11+
import { Progress } from "@/components/ui/progress";
1112
import { Toolbar } from "@/components/ui/coaching-sessions/coaching-notes/toolbar";
1213
import { siteConfig } from "@/site.config";
1314
import "@/styles/styles.scss";
@@ -83,23 +84,65 @@ const useCollaborationProvider = (doc: Y.Doc) => {
8384
}, [jwt, providerRef.current]);
8485

8586
return {
86-
isLoading: isLoading || isSyncing,
87+
// isSyncing indicates whether a first handshake with the server has been established
88+
// which is exactly the right thing to indicate if this hook isLoading or not.
89+
isLoading: isSyncing,
8790
isError,
8891
extensions,
8992
};
9093
};
9194

9295
const CoachingNotes = () => {
9396
const [doc] = useState(() => new Y.Doc());
97+
const [loadingProgress, setLoadingProgress] = useState(0);
9498
const { isLoading, isError, extensions } = useCollaborationProvider(doc);
9599

96-
if (isLoading) return <div>Loading coaching notes...</div>;
97-
if (isError)
100+
// Simulate loading progress
101+
useEffect(() => {
102+
if (isLoading) {
103+
setLoadingProgress(0);
104+
const interval = setInterval(() => {
105+
setLoadingProgress((prev) => {
106+
if (prev >= 90) {
107+
clearInterval(interval);
108+
return 90; // Stop at 90% until actually loaded
109+
}
110+
return prev + Math.random() * 15;
111+
});
112+
}, 150);
113+
114+
return () => clearInterval(interval);
115+
} else {
116+
// Complete the progress (100%) when loading is done
117+
setLoadingProgress(100);
118+
}
119+
}, [isLoading]);
120+
121+
if (isLoading) {
122+
return (
123+
<div className="flex flex-col items-center justify-center space-y-4 p-8 min-h-[440px] lg:min-h-[440px] sm:min-h-[200px] md:min-h-[350px]">
124+
<div className="w-full max-w-md">
125+
<div className="flex items-center justify-between mb-2">
126+
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
127+
Loading coaching notes...
128+
</span>
129+
<span className="text-sm text-gray-500 dark:text-gray-400">
130+
{Math.round(loadingProgress)}%
131+
</span>
132+
</div>
133+
<Progress value={loadingProgress} className="h-2" />
134+
</div>
135+
</div>
136+
);
137+
}
138+
139+
if (isError) {
98140
return (
99141
<div>
100142
We could not retrieve your coaching notes. Please try again later.
101143
</div>
102144
);
145+
}
103146

104147
return (
105148
<div className="border rounded">

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

Lines changed: 79 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,22 @@ export function MemberCard({
6363
(state: OrganizationStateStore) => state.currentOrganizationId
6464
);
6565
const { isACoach, userSession } = useAuthStore((state: AuthStore) => state);
66-
const { error: deleteError, deleteNested: deleteUser } = useUserMutation(currentOrganizationId);
67-
const { error: createError, createNested: createRelationship } = useCoachingRelationshipMutation(currentOrganizationId);
66+
const { error: deleteError, deleteNested: deleteUser } = useUserMutation(
67+
currentOrganizationId
68+
);
69+
const { error: createError, createNested: createRelationship } =
70+
useCoachingRelationshipMutation(currentOrganizationId);
6871

6972
console.log("is a coach", isACoach);
7073

7174
// Check if current user is a coach in any of this user's relationships
7275
// and make sure we can't delete ourselves. Admins can delete any user.
73-
const canDeleteUser = (userRelationships?.some(
74-
(rel) => rel.coach_id === userSession.id && userId !== userSession.id
75-
) || (userSession.role === Role.Admin)) && userSession.id !== userId;
76+
const canDeleteUser =
77+
(userRelationships?.some(
78+
(rel) => rel.coach_id === userSession.id && userId !== userSession.id
79+
) ||
80+
userSession.role === Role.Admin) &&
81+
userSession.id !== userId;
7682

7783
const handleDelete = async () => {
7884
if (!confirm("Are you sure you want to delete this member?")) {
@@ -129,8 +135,10 @@ export function MemberCard({
129135
toast.error(`Error assigning ${assignMode}`);
130136
return;
131137
}
132-
133-
toast.success(`Successfully assigned ${assignedMember.first_name} ${assignedMember.last_name} as ${assignMode} for ${selectedMember.first_name} ${selectedMember.last_name}`);
138+
139+
toast.success(
140+
`Successfully assigned ${assignedMember.first_name} ${assignedMember.last_name} as ${assignMode} for ${selectedMember.first_name} ${selectedMember.last_name}`
141+
);
134142
onRefresh();
135143
setAssignDialogOpen(false);
136144
setSelectedMember(null);
@@ -146,49 +154,61 @@ export function MemberCard({
146154
{email && <p className="text-sm text-muted-foreground">{email}</p>}
147155
</div>
148156
{(isACoach || userSession.role === Role.Admin) && (
149-
<DropdownMenu>
150-
<DropdownMenuTrigger asChild>
151-
<Button variant="ghost" size="icon" className="text-muted-foreground">
152-
<MoreHorizontal className="h-4 w-4" />
153-
</Button>
154-
</DropdownMenuTrigger>
155-
<DropdownMenuContent align="end">
156-
{userSession.role === Role.Admin && (
157-
<>
158-
<DropdownMenuItem
159-
onClick={() => {
160-
setAssignMode("coach");
161-
setAssignDialogOpen(true);
162-
setSelectedMember({id: userId, first_name: firstName, last_name: lastName});
163-
}}
164-
>
165-
Assign Coach
166-
</DropdownMenuItem>
167-
<DropdownMenuItem
168-
onClick={() => {
169-
setAssignMode("coachee");
170-
setAssignDialogOpen(true);
171-
setSelectedMember({id: userId, first_name: firstName, last_name: lastName});
172-
}}
173-
>
174-
Assign Coachee
175-
</DropdownMenuItem>
176-
</>
177-
)}
178-
{canDeleteUser && (
179-
<>
180-
<DropdownMenuSeparator />
181-
<DropdownMenuItem
182-
onClick={handleDelete}
183-
className="text-destructive focus:text-destructive"
184-
>
185-
<Trash2 className="mr-2 h-4 w-4" /> Delete
186-
</DropdownMenuItem>
187-
</>
188-
)}
189-
</DropdownMenuContent>
190-
</DropdownMenu>
191-
)}
157+
<DropdownMenu>
158+
<DropdownMenuTrigger asChild>
159+
<Button
160+
variant="ghost"
161+
size="icon"
162+
className="text-muted-foreground"
163+
>
164+
<MoreHorizontal className="h-4 w-4" />
165+
</Button>
166+
</DropdownMenuTrigger>
167+
<DropdownMenuContent align="end">
168+
{userSession.role === Role.Admin && (
169+
<>
170+
<DropdownMenuItem
171+
onClick={() => {
172+
setAssignMode("coach");
173+
setAssignDialogOpen(true);
174+
setSelectedMember({
175+
id: userId,
176+
first_name: firstName,
177+
last_name: lastName,
178+
});
179+
}}
180+
>
181+
Assign Coach
182+
</DropdownMenuItem>
183+
<DropdownMenuItem
184+
onClick={() => {
185+
setAssignMode("coachee");
186+
setAssignDialogOpen(true);
187+
setSelectedMember({
188+
id: userId,
189+
first_name: firstName,
190+
last_name: lastName,
191+
});
192+
}}
193+
>
194+
Assign Coachee
195+
</DropdownMenuItem>
196+
</>
197+
)}
198+
{canDeleteUser && (
199+
<>
200+
<DropdownMenuSeparator />
201+
<DropdownMenuItem
202+
onClick={handleDelete}
203+
className="text-destructive focus:text-destructive"
204+
>
205+
<Trash2 className="mr-2 h-4 w-4" /> Delete
206+
</DropdownMenuItem>
207+
</>
208+
)}
209+
</DropdownMenuContent>
210+
</DropdownMenu>
211+
)}
192212

193213
{/* Assign Coach/Coachee Modal */}
194214
<Dialog open={assignDialogOpen} onOpenChange={setAssignDialogOpen}>
@@ -198,7 +218,8 @@ export function MemberCard({
198218
{assignMode === "coach" ? "Assign Coach" : "Assign Coachee"}
199219
</DialogTitle>
200220
<DialogDescription>
201-
Select a member to be their {assignMode === "coach" ? "coach" : "coachee"}
221+
Select a member to be their{" "}
222+
{assignMode === "coach" ? "coach" : "coachee"}
202223
</DialogDescription>
203224
</DialogHeader>
204225
<Select
@@ -212,12 +233,17 @@ export function MemberCard({
212233
{users
213234
.filter((m) => m.id !== userId)
214235
.map((m) => (
215-
<SelectItem key={m.id} value={m.id.toString()}>{`${m.first_name} ${m.last_name}`}</SelectItem>
236+
<SelectItem
237+
key={m.id}
238+
value={m.id.toString()}
239+
>{`${m.first_name} ${m.last_name}`}</SelectItem>
216240
))}
217241
</SelectContent>
218242
</Select>
219243
<DialogFooter>
220-
<Button onClick={handleCreateCoachingRelationship}>{assignMode === "coach" ? "Assign as Coach" : "Assign as Coachee"}</Button>
244+
<Button onClick={handleCreateCoachingRelationship}>
245+
{assignMode === "coach" ? "Assign as Coach" : "Assign as Coachee"}
246+
</Button>
221247
</DialogFooter>
222248
</DialogContent>
223249
</Dialog>

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ export function MemberContainer({
3333
relationships.some((rel) => rel.coach_id === userSession.id)
3434
);
3535
}, [relationships, userSession.id, setIsACoach]);
36-
37-
3836

3937
// Find relationships where current user is either coach or coachee
4038
const userRelationships = relationships.filter(

src/components/ui/progress.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"use client";
2+
3+
import * as React from "react";
4+
import * as ProgressPrimitive from "@radix-ui/react-progress";
5+
6+
import { cn } from "@/components/lib/utils";
7+
8+
const Progress = React.forwardRef<
9+
React.ElementRef<typeof ProgressPrimitive.Root>,
10+
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
11+
>(({ className, value, ...props }, ref) => (
12+
<ProgressPrimitive.Root
13+
ref={ref}
14+
className={cn(
15+
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
16+
className
17+
)}
18+
{...props}
19+
>
20+
<ProgressPrimitive.Indicator
21+
className="h-full w-full flex-1 bg-primary transition-all"
22+
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
23+
/>
24+
</ProgressPrimitive.Root>
25+
));
26+
Progress.displayName = ProgressPrimitive.Root.displayName;
27+
28+
export { Progress };

0 commit comments

Comments
 (0)