Skip to content

Commit 1a591e2

Browse files
committed
change product
1 parent 4e96524 commit 1a591e2

32 files changed

+1146
-1416
lines changed

frontend/src/api/lookups.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useQuery } from "@tanstack/react-query";
2+
import { http } from "./config";
3+
4+
export const useCategories = () =>
5+
useQuery({
6+
queryKey: ["categories"],
7+
queryFn: async ({ signal }) => {
8+
const { data } = await http.get("/api/categories", { signal });
9+
return data;
10+
},
11+
});
12+
13+
export const useTechnologies = () =>
14+
useQuery({
15+
queryKey: ["technologies"],
16+
queryFn: async ({ signal }) => {
17+
const { data } = await http.get("/api/technologies", { signal });
18+
return data;
19+
},
20+
});
21+

frontend/src/api/products.js

Lines changed: 0 additions & 109 deletions
This file was deleted.

frontend/src/api/projects.js

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import {
2+
useInfiniteQuery,
3+
useMutation,
4+
useQuery,
5+
useQueryClient,
6+
} from "@tanstack/react-query";
7+
import { http } from "./config";
8+
9+
const DEFAULT_POPULATE = "developerId,category,technologies,awards";
10+
11+
const normalizeArrayParam = (value) => {
12+
if (!value) return undefined;
13+
if (Array.isArray(value)) return value.filter(Boolean).join(",");
14+
return value;
15+
};
16+
17+
const buildParams = ({ search, page, limit, start, populate, filters } = {}) => {
18+
const params = {};
19+
if (search) params.q = search;
20+
if (typeof start === "number") params._start = start;
21+
if (typeof limit === "number") params._limit = limit;
22+
if (typeof page === "number") params.page = page;
23+
24+
const populateValue = normalizeArrayParam(populate);
25+
if (populateValue) params.populate = populateValue;
26+
27+
if (filters && typeof filters === "object") {
28+
Object.entries(filters).forEach(([key, value]) => {
29+
if (value === undefined || value === null || value === "") return;
30+
params[key] = normalizeArrayParam(value) ?? value;
31+
});
32+
}
33+
34+
return params;
35+
};
36+
37+
const fetchProjects = async ({
38+
search,
39+
page,
40+
limit,
41+
start,
42+
populate,
43+
filters,
44+
signal,
45+
}) => {
46+
const { data } = await http.get("/api/projects", {
47+
params: buildParams({ search, page, limit, start, populate, filters }),
48+
signal,
49+
});
50+
return data;
51+
};
52+
53+
export const useProjects = ({
54+
search = "",
55+
page,
56+
limit = 20,
57+
start,
58+
populate = DEFAULT_POPULATE,
59+
filters,
60+
enabled = true,
61+
} = {}) =>
62+
useQuery({
63+
queryKey: ["projects", { search, page, limit, start, populate, filters }],
64+
queryFn: ({ signal }) =>
65+
fetchProjects({ search, page, limit, start, populate, filters, signal }),
66+
enabled,
67+
});
68+
69+
export const useInfiniteProjects = ({
70+
search = "",
71+
limit = 6,
72+
populate = DEFAULT_POPULATE,
73+
filters,
74+
} = {}) =>
75+
useInfiniteQuery({
76+
queryKey: ["projects", { search, limit, populate, filters, mode: "infinite" }],
77+
queryFn: ({ pageParam = 0, signal }) =>
78+
fetchProjects({
79+
search,
80+
limit,
81+
start: pageParam,
82+
populate,
83+
filters,
84+
signal,
85+
}),
86+
getNextPageParam: (lastPage, allPages) => {
87+
if (!Array.isArray(lastPage)) return undefined;
88+
if (lastPage.length < limit) return undefined;
89+
return allPages.length * limit;
90+
},
91+
initialPageParam: 0,
92+
});
93+
94+
export const useProject = (id, { populate = DEFAULT_POPULATE } = {}) =>
95+
useQuery({
96+
queryKey: ["project", id, populate],
97+
queryFn: async ({ signal }) => {
98+
const { data } = await http.get(`/api/projects/${id}`, {
99+
params: buildParams({ populate }),
100+
signal,
101+
});
102+
return data;
103+
},
104+
enabled: Boolean(id),
105+
});
106+
107+
export const useCreateProject = () => {
108+
const queryClient = useQueryClient();
109+
return useMutation({
110+
mutationFn: async (payload) => {
111+
const { data } = await http.post("/api/projects", payload);
112+
return data;
113+
},
114+
onSuccess: (created) => {
115+
queryClient.invalidateQueries({ queryKey: ["projects"] });
116+
if (created?.id || created?._id) {
117+
queryClient.setQueryData(
118+
["project", created.id || created._id, DEFAULT_POPULATE],
119+
created
120+
);
121+
}
122+
},
123+
});
124+
};
125+
126+
export const useUpdateProject = () => {
127+
const queryClient = useQueryClient();
128+
return useMutation({
129+
mutationFn: async ({ id, payload }) => {
130+
const { data } = await http.patch(`/api/projects/${id}`, payload);
131+
return data;
132+
},
133+
onSuccess: (updated, variables) => {
134+
if (variables?.id) {
135+
queryClient.setQueryData(
136+
["project", variables.id, DEFAULT_POPULATE],
137+
updated
138+
);
139+
}
140+
queryClient.invalidateQueries({ queryKey: ["projects"] });
141+
},
142+
});
143+
};
144+
145+
export const useDeleteProject = () => {
146+
const queryClient = useQueryClient();
147+
return useMutation({
148+
mutationFn: async (id) => {
149+
const { data } = await http.delete(`/api/projects/${id}`);
150+
return data;
151+
},
152+
onSuccess: (_data, id) => {
153+
queryClient.removeQueries({ queryKey: ["project", id] });
154+
queryClient.invalidateQueries({ queryKey: ["projects"] });
155+
},
156+
});
157+
};
158+

frontend/src/components/cart/CartItem.jsx

Lines changed: 18 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,40 +14,22 @@ const Carts = () => {
1414
console.log("Redux User:", user);
1515
}, [user]);
1616

17-
const AddHandler = (index) => {
17+
const removeHandler = (index) => {
1818
if (!user || !user.cart || !user.cart[index]) return;
1919

2020
const copyUser = { ...user, cart: [...user.cart] };
21-
copyUser.cart[index] = {
22-
...copyUser.cart[index],
23-
quantity: copyUser.cart[index].quantity + 1,
24-
};
21+
copyUser.cart.splice(index, 1);
2522
dispatch(asyncupdateuser(copyUser.id, copyUser));
2623
};
2724

28-
const SubstractHandler = (index) => {
29-
if (!user || !user.cart || !user.cart[index]) return;
30-
31-
const copyUser = { ...user, cart: [...user.cart] };
32-
33-
if (copyUser.cart[index].quantity <= 1) {
34-
copyUser.cart.splice(index, 1);
35-
} else {
36-
copyUser.cart[index] = {
37-
...copyUser.cart[index],
38-
quantity: copyUser.cart[index].quantity - 1,
39-
};
40-
}
41-
dispatch(asyncupdateuser(copyUser.id, copyUser));
42-
};
43-
44-
// Guard for empty cart or missing product data
25+
// Guard for empty saved list or missing project data
4526
const cartlist =
4627
user?.cart?.length > 0 ? (
4728
user.cart.map((item, index) => {
4829
if (!item?.product) return null;
4930

50-
const { image, title, price, id } = item.product;
31+
const project = item.product;
32+
const { image, title, id, category } = project;
5133

5234
return (
5335
<div
@@ -60,38 +42,31 @@ const Carts = () => {
6042
alt={title}
6143
/>
6244
<h1 className="w-1/4 truncate">{title}</h1>
63-
<h1>${price}</h1>
64-
<div className="space-x-3 flex items-center">
65-
<button
66-
onClick={() => AddHandler(index)}
67-
className="px-2 py-1 bg-green-300 rounded"
68-
>
69-
+
70-
</button>
71-
<span>{item.quantity}</span>
72-
<button
73-
onClick={() => SubstractHandler(index)}
74-
className="px-2 py-1 bg-red-300 rounded"
75-
>
76-
-
77-
</button>
78-
</div>
45+
<p className="text-sm text-gray-600">
46+
{category?.name || category || "Uncategorized"}
47+
</p>
48+
<button
49+
onClick={() => removeHandler(index)}
50+
className="px-3 py-1 bg-red-300 rounded"
51+
>
52+
Remove
53+
</button>
7954
<Link
80-
to={`/product-info/${id}`}
55+
to={`/projects/${id}`}
8156
className="text-cyan-800 underline ml-4"
8257
>
83-
Back
58+
View Project
8459
</Link>
8560
</div>
8661
);
8762
})
8863
) : (
89-
<p className="text-gray-500">Your cart is empty.</p>
64+
<p className="text-gray-500">No saved projects yet.</p>
9065
);
9166

9267
return (
9368
<div className="p-4">
94-
<h2 className="text-2xl font-bold mb-5">Your Shopping Cart</h2>
69+
<h2 className="text-2xl font-bold mb-5">Saved Projects</h2>
9570
{cartlist}
9671
</div>
9772
);

0 commit comments

Comments
 (0)