Skip to content

Commit aafb080

Browse files
committed
Adds project refresh functionality to context and UI
Introduces a `refreshProjects` method to the ProjectsContext, enabling manual and programmatic refresh of project data. Updates the ProjectsContextProvider to include this method and refactors the initial refresh logic. Integrates the refresh functionality into the ProjectListItem component to trigger a refresh when a project is selected. Also includes minor code style improvements for consistency.
1 parent 3570836 commit aafb080

File tree

3 files changed

+84
-84
lines changed

3 files changed

+84
-84
lines changed

src/common/context/ProjectsContext.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ export const SidebarTogglableContext = createContext<boolean>(true)
88
type ProjectsContextValue = {
99
refreshing: boolean,
1010
projects: Project[],
11+
refreshProjects: () => void,
1112
}
1213

1314
export const ProjectsContext = createContext<ProjectsContextValue>({
1415
refreshing: false,
1516
projects: [],
17+
refreshProjects: () => {},
1618
})

src/features/projects/view/ProjectsContextProvider.tsx

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,43 +19,34 @@ const ProjectsContextProvider = ({
1919
setProjects(value);
2020
};
2121

22+
const refreshProjects = () => {
23+
if (isLoadingRef.current) return;
24+
isLoadingRef.current = true;
25+
setRefreshing(true);
26+
fetch("/api/refresh-projects", { method: "POST" })
27+
.then((res) => res.json())
28+
.then(({ projects }) => {
29+
if (projects) setProjectsAndRefreshed(projects);
30+
})
31+
.catch((error) => console.error("Failed to refresh projects", error))
32+
.finally(() => {
33+
isLoadingRef.current = false;
34+
setRefreshing(false);
35+
});
36+
};
37+
2238
// Trigger background refresh after initial mount
2339
useEffect(() => {
24-
const refreshProjects = () => {
25-
if (isLoadingRef.current) {
26-
return;
27-
}
28-
isLoadingRef.current = true;
29-
setRefreshing(true);
30-
fetch("/api/refresh-projects", { method: "POST" })
31-
.then((res) => res.json())
32-
.then(({ projects }) => {
33-
if (projects) setProjectsAndRefreshed(projects);
34-
})
35-
.catch((error) => console.error("Failed to refresh projects", error))
36-
.finally(() => {
37-
isLoadingRef.current = false;
38-
setRefreshing(false);
39-
});
40-
};
41-
// Initial refresh
4240
refreshProjects();
4341
const handleVisibilityChange = () => {
4442
if (!document.hidden) refreshProjects();
4543
};
46-
4744
document.addEventListener("visibilitychange", handleVisibilityChange);
48-
return () =>
49-
document.removeEventListener("visibilitychange", handleVisibilityChange);
45+
return () => document.removeEventListener("visibilitychange", handleVisibilityChange);
5046
}, []);
5147

5248
return (
53-
<ProjectsContext.Provider
54-
value={{
55-
projects,
56-
refreshing,
57-
}}
58-
>
49+
<ProjectsContext.Provider value={{ projects, refreshing,refreshProjects }}>
5950
{children}
6051
</ProjectsContext.Provider>
6152
);
Lines changed: 64 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"use client"
1+
"use client";
22

33
import {
44
Box,
@@ -7,26 +7,33 @@ import {
77
ListItemButton,
88
Skeleton as MuiSkeleton,
99
Stack,
10-
Typography
11-
} from "@mui/material"
12-
import MenuItemHover from "@/common/ui/MenuItemHover"
13-
import { Project } from "@/features/projects/domain"
14-
import { useProjectSelection } from "@/features/projects/data"
15-
import ProjectAvatar, { Squircle as ProjectAvatarSquircle } from "./ProjectAvatar"
16-
import { useCloseSidebarOnSelection } from "@/features/sidebar/data"
10+
Typography,
11+
} from "@mui/material";
12+
import MenuItemHover from "@/common/ui/MenuItemHover";
13+
import { Project } from "@/features/projects/domain";
14+
import { useProjectSelection } from "@/features/projects/data";
15+
import { useContext } from "react";
16+
import { ProjectsContext } from "@/common";
17+
import ProjectAvatar, {
18+
Squircle as ProjectAvatarSquircle,
19+
} from "./ProjectAvatar";
20+
import { useCloseSidebarOnSelection } from "@/features/sidebar/data";
1721

18-
const AVATAR_SIZE = { width: 40, height: 40 }
22+
const AVATAR_SIZE = { width: 40, height: 40 };
1923

2024
const ProjectListItem = ({ project }: { project: Project }) => {
21-
const { project: selectedProject, selectProject } = useProjectSelection()
22-
const selected = project.id === selectedProject?.id
23-
const { closeSidebarIfNeeded } = useCloseSidebarOnSelection()
25+
const { project: selectedProject, selectProject } = useProjectSelection();
26+
const { refreshProjects } = useContext(ProjectsContext);
27+
const selected = project.id === selectedProject?.id;
28+
const { closeSidebarIfNeeded } = useCloseSidebarOnSelection();
29+
2430
return (
2531
<Template
2632
selected={selected}
2733
onSelect={() => {
28-
closeSidebarIfNeeded()
29-
selectProject(project)
34+
closeSidebarIfNeeded();
35+
selectProject(project);
36+
refreshProjects();
3037
}}
3138
avatar={
3239
<ProjectAvatar
@@ -37,55 +44,55 @@ const ProjectListItem = ({ project }: { project: Project }) => {
3744
}
3845
title={project.displayName}
3946
/>
40-
)
41-
}
47+
);
48+
};
4249

43-
export default ProjectListItem
50+
export default ProjectListItem;
4451

4552
export const Skeleton = () => {
4653
return (
47-
<Template disabled avatar={
48-
<ProjectAvatarSquircle width={AVATAR_SIZE.width} height={AVATAR_SIZE.height}>
49-
<MuiSkeleton
50-
variant="rectangular"
51-
animation="wave"
52-
sx={{ width: "100%", height: "100%" }}
53-
/>
54-
</ProjectAvatarSquircle>
55-
}>
54+
<Template
55+
disabled
56+
avatar={
57+
<ProjectAvatarSquircle
58+
width={AVATAR_SIZE.width}
59+
height={AVATAR_SIZE.height}
60+
>
61+
<MuiSkeleton
62+
variant="rectangular"
63+
animation="wave"
64+
sx={{ width: "100%", height: "100%" }}
65+
/>
66+
</ProjectAvatarSquircle>
67+
}
68+
>
5669
<MuiSkeleton variant="text" animation="wave" width={100} />
5770
</Template>
58-
)
59-
}
71+
);
72+
};
6073

6174
export const Template = ({
6275
disabled,
6376
selected,
6477
onSelect,
6578
avatar,
6679
title,
67-
children
80+
children,
6881
}: {
69-
disabled?: boolean
70-
selected?: boolean
71-
onSelect?: () => void
72-
avatar: React.ReactNode
73-
title?: string
74-
children?: React.ReactNode
82+
disabled?: boolean;
83+
selected?: boolean;
84+
onSelect?: () => void;
85+
avatar: React.ReactNode;
86+
title?: string;
87+
children?: React.ReactNode;
7588
}) => {
7689
return (
7790
<ListItem disablePadding>
78-
<Button
79-
disabled={disabled}
80-
selected={selected}
81-
onSelect={onSelect}
82-
>
91+
<Button disabled={disabled} selected={selected} onSelect={onSelect}>
8392
<MenuItemHover disabled={disabled}>
8493
<Stack direction="row" alignItems="center" spacing={1.5}>
85-
<Box sx={{ width: 40, height: 40 }}>
86-
{avatar}
87-
</Box>
88-
{title &&
94+
<Box sx={{ width: 40, height: 40 }}>{avatar}</Box>
95+
{title && (
8996
<ListItemText
9097
primary={
9198
<Typography
@@ -95,37 +102,37 @@ export const Template = ({
95102
letterSpacing: 0.1,
96103
whiteSpace: "nowrap",
97104
overflow: "hidden",
98-
textOverflow: "ellipsis"
105+
textOverflow: "ellipsis",
99106
}}
100107
>
101108
{title}
102109
</Typography>
103110
}
104111
/>
105-
}
112+
)}
106113
{children}
107114
</Stack>
108115
</MenuItemHover>
109116
</Button>
110117
</ListItem>
111-
)
112-
}
118+
);
119+
};
113120

114121
const Button = ({
115122
disabled,
116123
selected,
117124
onSelect,
118-
children
125+
children,
119126
}: {
120-
disabled?: boolean
121-
selected?: boolean
122-
onSelect?: () => void
123-
children?: React.ReactNode
127+
disabled?: boolean;
128+
selected?: boolean;
129+
onSelect?: () => void;
130+
children?: React.ReactNode;
124131
}) => {
125132
return (
126133
<>
127134
{disabled && children}
128-
{!disabled &&
135+
{!disabled && (
129136
<ListItemButton
130137
disabled={disabled}
131138
onClick={onSelect}
@@ -135,7 +142,7 @@ const Button = ({
135142
>
136143
{children}
137144
</ListItemButton>
138-
}
145+
)}
139146
</>
140-
)
141-
}
147+
);
148+
};

0 commit comments

Comments
 (0)