Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 88 additions & 70 deletions app/projects/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ const ProjectPage: React.FC = () => {
const { theme } = useTheme();
const [projects, setProjects] = useState<Project[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);

useEffect(() => {
setLoading(true);
setError(false);

const fetchProjects = async () => {
try {
const res = await fetch("/api/projects-with-members");
Expand All @@ -45,25 +49,103 @@ const ProjectPage: React.FC = () => {
setProjects(data.data as Project[]);
} else {
console.error("Error loading project data");
throw new Error("Network response was not ok");
}
} catch (err) {
console.error("Error fetching projects:", err);
} finally{
setTimeout(() => {
setLoading(false);
}, 500);
} catch (err) {
console.error("Error fetching projects:", err);
setError(true);
setLoading(false);
}
};

fetchProjects();
}, []);

const renderProjects = () => {
if (loading) {
return (
<BentoGrid>
{Array.from({ length: 6 }).map((_, i) => (
<SkeletonLoader key={i} />
))}
Comment on lines +71 to +73
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Note: Related SkeletonLoader component has a bug.

The SkeletonLoader component being used here (from ./skeletonLoader.tsx) contains a bug at line 27 where class is used instead of className in JSX. This will cause the flex styling to not be applied.

Based on the relevant code snippet:

<div class="flex">  // Should be className="flex"

Consider fixing this in a follow-up or as part of this PR.

🤖 Prompt for AI Agents
In app/projects/page.tsx around lines 70 to 72 the page maps SkeletonLoader
components; the referenced bug is in ./skeletonLoader.tsx at line 27 where JSX
uses class instead of className, preventing React from applying the flex styling
— open ./skeletonLoader.tsx and change the <div class="flex"> to <div
className="flex"> (and scan for any other incorrect JSX attribute usages such as
for/htmlFor and fix them similarly), then run the app to confirm the flex layout
is applied.

</BentoGrid>
);
}

if (error) {
return (
<div className="flex items-center justify-center py-40">
<div className="flex items-center gap-4">
<svg
className="h-11 w-11 opacity-80"
fill="none"
stroke="url(#purplePinkGradient)"
strokeWidth="1.5"
viewBox="0 0 24 24"
>
<defs>
<linearGradient id="purplePinkGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="#a855f7" /> {/* purple-500 */}
<stop offset="100%" stopColor="#ec4899" /> {/* pink-500 */}
</linearGradient>
</defs>

<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>

<p className="text-3xl font-semibold tracking-wide text-gray-700 dark:text-gray-300">
Failed to fetch projects
</p>
</div>
</div>
);
}

if (projects.length === 0) {
return (
<div className="flex items-center justify-center py-40">
<p className="text-3xl font-semibold tracking-wide text-gray-700 dark:text-gray-300">
No projects found
</p>
</div>
);
}

return (
<BentoGrid>
{projects.map((project, i) => (
<BentoGridItem
key={project.id}
title={project.name}
header={<Skeleton src={project.imageUrl} alt={project.name} />}
github={project.githubUrl}
live={project.deployUrl}
tooltipItems={project.members?.map((member) => ({
id: member.id,
name: member.name,
image: member.image || "/default-avatar.png",
}))}
className={i === 3 || i === 6 ? "md:col-span-2" : ""}
/>
))}
</BentoGrid>
);
};


return (
<div className={`relative min-h-screen w-full ${theme === "dark" ? "bg-black" : "bg-white"}`}>
<Particles
quantity={500}
color={theme === "dark" ? "#ffffff" : "#000000"}
className="absolute inset-0 z-0"
className="fixed inset-0 z-0"
size={1.5}
staticity={50}
ease={40}
Expand All @@ -89,72 +171,8 @@ const ProjectPage: React.FC = () => {
<div className="bg-gradient-to-r from-purple-500 to-pink-500 h-full w-3/4 rounded-full animate-pulse" />
</div>

{/* Loading State */}
{loading && (
<BentoGrid>
{Array.from({ length: 6 }).map((_, i) => (
<SkeletonLoader key={i} />
))}
</BentoGrid>
)}

{/* Empty State */}
{!loading && projects.length === 0 && (
<div className="flex items-center justify-center py-40">
<div className="flex items-center gap-4">
<svg
className="h-11 w-11 opacity-80"
fill="none"
stroke="url(#purplePinkGradient)"
strokeWidth="1.5"
viewBox="0 0 24 24"
>
<defs>
<linearGradient id="purplePinkGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="#a855f7" /> {/* purple-500 */}
<stop offset="100%" stopColor="#ec4899" /> {/* pink-500 */}
</linearGradient>
</defs>

<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>

<p className="text-3xl font-semibold tracking-wide text-gray-700 dark:text-gray-300">
Failed to fetch projects
</p>
</div>
</div>
)}

{/* Projects Grid */}
{!loading && projects.length > 0 && (
<BentoGrid>
{projects.map((project, i) => (
<BentoGridItem
key={project.id}
title={project.name}
header={
<Skeleton
src={project.imageUrl}
alt={project.name}
/>
}
github={project.githubUrl}
live={project.deployUrl}
tooltipItems={project.members?.map((member) => ({
id: member.id,
name: member.name,
image: member.image || "/default-avatar.png",
}))}
className={i === 3 || i === 6 ? "md:col-span-2" : ""}
/>
))}
</BentoGrid>
)}
{renderProjects()}

</main>
</div>
</div>
Expand Down
103 changes: 65 additions & 38 deletions components/Members-Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@ export default function MembersPage() {
const [founders, setFounders] = useState<DisplayMember[]>([]);
const [loading, setLoading] = useState(true);
const [tabLoading, setTabLoading] = useState(false);
const [tabError, setTabError] = useState<{
present: boolean;
seniors: boolean;
founders: boolean;
}>({
present: false,
seniors: false,
founders: false,
});


const handleTabChange = (idx: number) => {
Expand Down Expand Up @@ -149,6 +158,12 @@ export default function MembersPage() {

useEffect(() => {
const abortController = new AbortController();
setLoading(true);
setTabError({
present: false,
seniors: false,
founders: false,
});

const fetchMembers = async () => {
try {
Expand All @@ -170,6 +185,7 @@ export default function MembersPage() {
setFounders([]);
setSuperSeniors([]);
setPresentMembers([]);
setLoading(false);
return;
}

Expand Down Expand Up @@ -199,16 +215,21 @@ export default function MembersPage() {
setFounders(foundersList);
setSuperSeniors(superSeniorsList);
setPresentMembers(presentList);
setTimeout(() => {
setLoading(false);
}, 500);
} catch (err) {
if (err instanceof Error && err.name === "AbortError") {
return;
}
console.error("Failed to fetch members", err);
} finally{
setTimeout(() => {
setLoading(false);
}, 1000);
}
setTabError({
present: true,
seniors: true,
founders: true,
});
setLoading(false);
}
};

fetchMembers();
Expand Down Expand Up @@ -249,41 +270,47 @@ export default function MembersPage() {
</div>
);

const TAB_LABELS = {
present: "Present Members",
seniors: "Super Seniors",
founders: "Founders",
} as const;

const renderMembersTab = (members: DisplayMember[], tab: "present" | "seniors" | "founders", isFounder = false) => {
if (loading || tabLoading) {
return <MembersSkeletonGrid />;
}

if (tabError[tab]) {
return (
<MembersEmptyState message={`Failed to load ${TAB_LABELS[tab]}`} />
);
}

if (members.length === 0) {
return <MembersEmptyState message="No members found" />;
}

return <MemberGrid members={members} isFounder={isFounder} />;
};


const tabs = [
{
title: "Present Members",
value: "present",
content: loading || tabLoading ? (
<MembersSkeletonGrid />
) : presentMembers.length === 0 ? (
<MembersEmptyState message="Failed to load present members" />
) : (
<MemberGrid members={presentMembers} />
),
},
{
title: "Super Seniors",
value: "seniors",
content: loading || tabLoading ? (
<MembersSkeletonGrid />
) : superSeniors.length === 0 ? (
<MembersEmptyState message="Failed to load super seniors" />
) : (
<MemberGrid members={superSeniors} />
),
},
{
title: "Founders",
value: "founders",
content: loading || tabLoading ? (
<MembersSkeletonGrid />
) : founders.length === 0 ? (
<MembersEmptyState message="Failed to load founders" />
) : (
<MemberGrid members={founders} isFounder />
),
},
{
title: "Present Members",
value: "present",
content: renderMembersTab(presentMembers, "present"),
},
{
title: "Super Seniors",
value: "seniors",
content: renderMembersTab(superSeniors, "seniors"),
},
{
title: "Founders",
value: "founders",
content: renderMembersTab(founders, "founders", true),
},
];


Expand Down