Skip to content

Commit 5e4a62b

Browse files
Develop filtering system (boundlessfi#45)
* fix: callback urls for google auth and github auth * feat: implement voting mechanism * feat: implement voting mechanism * feat: implement voting mechanism * refactor: refactor dashboard * refactor: refactor dashboard * refactor: refactor dashboard * refactor: refactor dashboard * implement voting mechanism * Implement contribution page * fix conflict * implement searching and filter component * implement searching and filter component * refactor: reorder imports for consistency and readability in contribution components * chore: update .env.example with new environment variables and format adjustments --------- Co-authored-by: Collins Ikechukwu <collinschristroa@gmail.com>
1 parent b39564f commit 5e4a62b

13 files changed

Lines changed: 1038 additions & 564 deletions

File tree

.env.example

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,17 @@ GOOGLE_CLIENT_SECRET="YOUR_GOOGLE_CLIENT_SECRET" #neccesary
3131
GITHUB_ID="YOUR_GITHUB_CLIENT_ID" #neccesary
3232
GITHUB_SECRET="YOUR_GITHUB_CLIENT_SECRET" #neccesary
3333

34-
35-
STELLAR_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
34+
PUBLIC_STELLAR_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
35+
PUBLIC_STELLAR_RPC_URL="https://soroban-testnet.stellar.org:443"
3636
STELLAR_RPC_URL="https://soroban-testnet.stellar.org:443"
37+
3738
STELLAR_ACCOUNT="collins"
3839
STELLAR_NETWORK="testnet"
40+
SOROBAN_SECRET_KEY=
41+
STELLAR_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
3942

4043
# Cloudinary
4144
CLOUDINARY_CLOUD_NAME= #neccesary
4245
CLOUDINARY_API_KEY= #neccesary
4346
CLOUDINARY_API_SECRET= #neccesary
44-
CLOUDINARY_URL= #neccesary
47+
CLOUDINARY_URL= #neccesary
Lines changed: 6 additions & 316 deletions
Original file line numberDiff line numberDiff line change
@@ -1,319 +1,9 @@
11
"use client";
2+
import MyContributionsPage from "@/components/contributions";
3+
import React from "react";
24

3-
import { ActiveContributions } from "@/components/contributions/active-contribution";
4-
import { CallToAction } from "@/components/contributions/call-to-action";
5-
import { CommentEditModal } from "@/components/contributions/comment-edit-modal";
6-
import { ContributionFilters } from "@/components/contributions/contribution-filters";
7-
import { ContributionStats as ContributionStatsComponent } from "@/components/contributions/contribution-stats";
8-
import { DeleteConfirmationDialog } from "@/components/contributions/delete-confirmation-dialog";
9-
import { LoadingState } from "@/components/contributions/loading-state";
10-
import { PastContributions } from "@/components/contributions/past-contributions";
11-
import { UserComments } from "@/components/contributions/user-comments";
12-
import {
13-
deleteComment as apiDeleteComment,
14-
editComment as apiEditComment,
15-
fetchActiveProjects,
16-
fetchCategories,
17-
fetchContributionStats,
18-
fetchPastProjects,
19-
fetchUserComments,
20-
} from "@/lib/actions/services";
21-
import {
22-
sortActiveProjects,
23-
sortComments,
24-
sortPastProjects,
25-
} from "@/lib/utils";
26-
import type {
27-
ActiveProject,
28-
ContributionStats,
29-
PastProject,
30-
SortOption,
31-
TabOption,
32-
UserComment,
33-
} from "@/types/contributions";
34-
import { useRouter } from "next/navigation";
35-
import { useEffect, useState } from "react";
36-
import { toast } from "sonner";
5+
const Page = () => {
6+
return <MyContributionsPage />;
7+
};
378

38-
export default function MyContributionsPage() {
39-
const router = useRouter();
40-
const [activeTab, setActiveTab] = useState<TabOption>("all");
41-
const [searchQuery, setSearchQuery] = useState("");
42-
const [sortOption, setSortOption] = useState<SortOption>("newest");
43-
const [categoryFilter, setCategoryFilter] = useState("all");
44-
const [categories, setCategories] = useState<string[]>([]);
45-
46-
// Data states - Fix type definitions
47-
const [stats, setStats] = useState<ContributionStats | null>(null);
48-
const [activeProjects, setActiveProjects] = useState<ActiveProject[]>([]);
49-
const [pastProjects, setPastProjects] = useState<PastProject[]>([]);
50-
const [comments, setComments] = useState<UserComment[]>([]);
51-
52-
// Loading states
53-
const [isLoadingStats, setIsLoadingStats] = useState(true);
54-
const [isLoadingActive, setIsLoadingActive] = useState(true);
55-
const [isLoadingPast, setIsLoadingPast] = useState(true);
56-
const [isLoadingComments, setIsLoadingComments] = useState(true);
57-
58-
// Modal states
59-
const [commentToEdit, setCommentToEdit] = useState<UserComment | null>(null);
60-
const [commentToDelete, setCommentToDelete] = useState<string | null>(null);
61-
62-
// Fetch data on initial load
63-
useEffect(() => {
64-
const fetchData = async () => {
65-
try {
66-
// Fetch categories
67-
const categoriesData = await fetchCategories();
68-
setCategories(categoriesData);
69-
70-
// Fetch stats
71-
setIsLoadingStats(true);
72-
const statsData = await fetchContributionStats();
73-
setStats(statsData);
74-
setIsLoadingStats(false);
75-
76-
// Fetch active projects
77-
setIsLoadingActive(true);
78-
const activeData = await fetchActiveProjects();
79-
setActiveProjects(activeData);
80-
setIsLoadingActive(false);
81-
82-
// Fetch past projects
83-
setIsLoadingPast(true);
84-
const pastData = await fetchPastProjects();
85-
setPastProjects(pastData);
86-
setIsLoadingPast(false);
87-
88-
// Fetch comments
89-
setIsLoadingComments(true);
90-
const commentsData = await fetchUserComments();
91-
setComments(commentsData);
92-
setIsLoadingComments(false);
93-
} catch (error) {
94-
console.error("Error fetching data:", error);
95-
toast.error("Error", {
96-
description: "Failed to load your contributions. Please try again.",
97-
});
98-
}
99-
};
100-
101-
fetchData();
102-
}, []);
103-
104-
// Fetch data when category filter changes
105-
useEffect(() => {
106-
const fetchFilteredData = async () => {
107-
try {
108-
if (activeTab === "all" || activeTab === "votes") {
109-
// Fetch active projects with category filter
110-
setIsLoadingActive(true);
111-
const activeData = await fetchActiveProjects(categoryFilter);
112-
setActiveProjects(activeData);
113-
setIsLoadingActive(false);
114-
115-
// Fetch past projects with category filter
116-
setIsLoadingPast(true);
117-
const pastData = await fetchPastProjects(categoryFilter);
118-
setPastProjects(pastData);
119-
setIsLoadingPast(false);
120-
}
121-
} catch (error) {
122-
console.error("Error fetching filtered data:", error);
123-
toast.error("Error", {
124-
description: "Failed to load filtered data. Please try again.",
125-
});
126-
}
127-
};
128-
129-
fetchFilteredData();
130-
}, [categoryFilter, activeTab]);
131-
132-
// Fetch comments when search query changes
133-
useEffect(() => {
134-
const fetchFilteredComments = async () => {
135-
if (activeTab === "all" || activeTab === "comments") {
136-
try {
137-
setIsLoadingComments(true);
138-
const commentsData = await fetchUserComments(searchQuery);
139-
setComments(commentsData);
140-
setIsLoadingComments(false);
141-
} catch (error) {
142-
console.error("Error fetching comments:", error);
143-
toast.error("Error", {
144-
description: "Failed to load comments. Please try again.",
145-
});
146-
}
147-
}
148-
};
149-
150-
// Debounce search to avoid too many requests
151-
const debounceTimeout = setTimeout(() => {
152-
fetchFilteredComments();
153-
}, 500);
154-
155-
return () => clearTimeout(debounceTimeout);
156-
}, [searchQuery, activeTab]);
157-
158-
// Sort data based on selected option
159-
const sortedActiveProjects = activeProjects
160-
? sortActiveProjects(activeProjects, sortOption)
161-
: [];
162-
const sortedPastProjects = pastProjects
163-
? sortPastProjects(pastProjects, sortOption)
164-
: [];
165-
const sortedComments = comments ? sortComments(comments, sortOption) : [];
166-
167-
// Navigation and action handlers
168-
const navigateToProject = (projectId: string) => {
169-
router.push(`/projects/${projectId}`);
170-
};
171-
172-
// Fix the type to match what UserComments component expects
173-
const handleEditComment = (commentId: string) => {
174-
const comment = comments.find((c) => c.id === commentId);
175-
if (comment) {
176-
setCommentToEdit(comment);
177-
}
178-
};
179-
180-
const handleDeleteComment = (commentId: string) => {
181-
setCommentToDelete(commentId);
182-
};
183-
184-
const saveEditedComment = async (id: string, content: string) => {
185-
try {
186-
await apiEditComment(id, content);
187-
188-
// Update local state
189-
setComments((prevComments) =>
190-
prevComments.map((comment) =>
191-
comment.id === id ? { ...comment, content } : comment,
192-
),
193-
);
194-
195-
toast.success("Success", {
196-
description: "Comment updated successfully",
197-
});
198-
} catch (error) {
199-
console.error("Error updating comment:", error);
200-
toast.error("Error", {
201-
description: "Failed to update comment. Please try again.",
202-
});
203-
throw error; // Re-throw to handle in the modal
204-
}
205-
};
206-
207-
const confirmDeleteComment = async () => {
208-
if (!commentToDelete) return;
209-
210-
try {
211-
await apiDeleteComment(commentToDelete);
212-
213-
// Update local state
214-
setComments((prevComments) =>
215-
prevComments.filter((comment) => comment.id !== commentToDelete),
216-
);
217-
218-
toast.success("Success", {
219-
description: "Comment deleted successfully",
220-
});
221-
} catch (error) {
222-
console.error("Error deleting comment:", error);
223-
toast.error("Error", {
224-
description: "Failed to delete comment. Please try again.",
225-
});
226-
throw error; // Re-throw to handle in the dialog
227-
}
228-
};
229-
230-
return (
231-
<div className="container mx-auto py-8 max-w-7xl">
232-
<h1 className="text-3xl font-bold mb-6">My Contributions</h1>
233-
234-
{/* Summary Section */}
235-
{isLoadingStats ? (
236-
<LoadingState type="stats" />
237-
) : stats ? (
238-
<ContributionStatsComponent stats={stats} />
239-
) : null}
240-
241-
{/* Tabs and Filters */}
242-
<ContributionFilters
243-
activeTab={activeTab}
244-
setActiveTab={setActiveTab}
245-
searchQuery={searchQuery}
246-
setSearchQuery={setSearchQuery}
247-
sortOption={sortOption}
248-
setSortOption={setSortOption}
249-
categoryFilter={categoryFilter}
250-
setCategoryFilter={setCategoryFilter}
251-
categories={categories}
252-
/>
253-
254-
{/* Active Contributions Section */}
255-
{(activeTab === "all" || activeTab === "votes") && (
256-
<>
257-
<h2 className="text-2xl font-semibold mb-4">Ongoing Contributions</h2>
258-
{isLoadingActive ? (
259-
<LoadingState type="cards" count={3} />
260-
) : (
261-
<ActiveContributions
262-
projects={sortedActiveProjects}
263-
navigateToProject={navigateToProject}
264-
/>
265-
)}
266-
</>
267-
)}
268-
269-
{/* Past Contributions Section */}
270-
{(activeTab === "all" || activeTab === "votes") && (
271-
<>
272-
<h2 className="text-2xl font-semibold mb-4">Past Contributions</h2>
273-
{isLoadingPast ? (
274-
<LoadingState type="table" count={4} />
275-
) : (
276-
<PastContributions
277-
projects={sortedPastProjects}
278-
navigateToProject={navigateToProject}
279-
/>
280-
)}
281-
</>
282-
)}
283-
284-
{/* Comments Section */}
285-
{(activeTab === "all" || activeTab === "comments") && (
286-
<>
287-
<h2 className="text-2xl font-semibold mb-4">My Comments</h2>
288-
{isLoadingComments ? (
289-
<LoadingState type="comments" count={3} />
290-
) : (
291-
<UserComments
292-
comments={sortedComments}
293-
navigateToProject={navigateToProject}
294-
handleEditComment={handleEditComment}
295-
handleDeleteComment={handleDeleteComment}
296-
/>
297-
)}
298-
</>
299-
)}
300-
301-
{/* Call-to-Action Section */}
302-
<CallToAction />
303-
304-
{/* Modals */}
305-
<CommentEditModal
306-
comment={commentToEdit}
307-
isOpen={!!commentToEdit}
308-
onClose={() => setCommentToEdit(null)}
309-
onSave={saveEditedComment}
310-
/>
311-
312-
<DeleteConfirmationDialog
313-
isOpen={!!commentToDelete}
314-
onClose={() => setCommentToDelete(null)}
315-
onConfirm={confirmDeleteComment}
316-
/>
317-
</div>
318-
);
319-
}
9+
export default Page;

0 commit comments

Comments
 (0)