Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Get LEVEL Creation to work #78

Merged
merged 17 commits into from
May 15, 2024
Merged
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
53 changes: 37 additions & 16 deletions app/(dashboard)/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { DashboardHeader } from "@/components/header";
import { DashboardShell } from "@/components/shell";
import PointsCard from "@/components/ui/pointsCard";
import UserPointsAndLevelCard from "@/components/ui/userPointsAndLevelCard";
import { authOptions } from "@/lib/auth";
import { getEnrolledRepositories } from "@/lib/enrollment/service";
import { getCurrentUser } from "@/lib/session";
import {
calculateAssignabelNonAssignableIssuesForUserInALevel,
findCurrentAndNextLevelOfCurrentUser,
} from "@/lib/utils/levelUtils";
import { TLevel } from "@/types/level";
import { TPointTransaction } from "@/types/pointTransaction";
import Link from "next/link";
import { redirect } from "next/navigation";
Expand Down Expand Up @@ -46,14 +51,17 @@ export default async function DashboardPage() {

const repositoriesUserIsEnrolledIn = await getEnrolledRepositories(user.id);

const calculateTotalPointsForCurrentUser = (currentUserId: string, pointTransactions: TPointTransaction[]) => {
const calculateTotalPointsForCurrentUser = (
currentUserId: string,
pointTransactions: TPointTransaction[]
) => {
return pointTransactions.reduce((acc, transaction) => {
if (transaction.userId === currentUserId) {
return acc + transaction.points;
}
return acc;
}, 0);
}
};
const calculateRankOfCurrentUser = (currentUserId: string, pointTransactions: TPointTransaction[]) => {
// Create an object to store the total points for each user enrolled in the repositories that the current user is in.
const totalPointsOfAllUsersInTheRepo = {};
Expand Down Expand Up @@ -84,38 +92,51 @@ export default async function DashboardPage() {
return userRank;
};

// Calculate total points and rank for the current user in repositories they are enrolled in.
const pointsPerRepository = repositoriesUserIsEnrolledIn.map((repository) => {
// Calculate total points,rank,current level for the current user in repositories they are enrolled in.
const pointsAndLevelsPerRepository = repositoriesUserIsEnrolledIn.map((repository) => {
const pointTransactions = repository.pointTransactions || [];

const totalPoints = calculateTotalPointsForCurrentUser(user.id, pointTransactions);
const rank = calculateRankOfCurrentUser(user.id, pointTransactions);
const { currentLevelOfUser, nextLevelForUser } = findCurrentAndNextLevelOfCurrentUser(
repository,
totalPoints
);
const levels = (repository?.levels as TLevel[]) || [];
const modifiedTagsArray = calculateAssignabelNonAssignableIssuesForUserInALevel(levels); //gets all assignable tags be it from the current level and from lower levels.

const assignableTags = modifiedTagsArray.find((item) => item.levelId === currentLevelOfUser?.id); //finds the curent level in the modifiedTagsArray.

return {
id: repository.id,
repositoryName: repository.name,
points: totalPoints,
repositoryLogo: repository.logoUrl,
rank: rank,
currentLevelOfUser: currentLevelOfUser,
nextLevelForUser: nextLevelForUser,
assignableTags: assignableTags?.assignableIssues || [],
};
});

return (
<DashboardShell>
<DashboardHeader heading="Shall we play a game?"></DashboardHeader>

<div className=" grid gap-4 md:grid-cols-2">
{pointsPerRepository.map((point, index) => {
const isLastItem = index === pointsPerRepository.length - 1;
const isSingleItemInLastRow = pointsPerRepository.length % 2 !== 0 && isLastItem;
<div className=" grid gap-8">
{pointsAndLevelsPerRepository.map((repositoryData, index) => {
return (
<div key={point.id} className={`${isSingleItemInLastRow ? "md:col-span-2" : "md:col-span-1"}`}>
<PointsCard
key={point.id}
repositoryName={point.repositoryName}
points={point.points}
rank={point.rank}
repositoryLogo={point.repositoryLogo}
<div key={repositoryData.id}>
<UserPointsAndLevelCard
key={repositoryData.id}
repositoryId={repositoryData.id}
repositoryName={repositoryData.repositoryName}
points={repositoryData.points}
rank={repositoryData.rank}
repositoryLogo={repositoryData.repositoryLogo}
currentLevelOfUser={repositoryData.currentLevelOfUser}
nextLevelForUser={repositoryData.nextLevelForUser}
assignableTags={repositoryData.assignableTags}
/>
</div>
);
Expand Down
6 changes: 4 additions & 2 deletions app/(dashboard)/enroll/[repositoryId]/issues/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import GitHubIssue from "@/components/ui/githubIssue";
import { getAllOssGgIssuesOfRepo } from "@/lib/github/service";
import { getRepositoryById } from "@/lib/repository/service";
import { TLevel } from "@/types/level";

export default async function OpenIssuesPage({ params }) {
const repository = await getRepositoryById(params.repositoryId);
Expand All @@ -9,14 +10,15 @@ export default async function OpenIssuesPage({ params }) {
}

const issues = await getAllOssGgIssuesOfRepo(repository.githubId);
const levelsInRepo: TLevel[] = repository.levels as TLevel[];

return (
<>
<div className="space-y-2">
<div className="mt-4">
{issues.length === 0 ? (
<p>Currently, there are no open oss.gg issues available.</p>
) : (
issues.map((issue) => <GitHubIssue issue={issue} key={issue.title} />)
issues.map((issue) => <GitHubIssue key={issue.title} issue={issue} levelsInRepo={levelsInRepo} />)
)}
</div>
</>
Expand Down
17 changes: 15 additions & 2 deletions app/(dashboard)/enroll/[repositoryId]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { DashboardHeader } from "@/components/header";
import { DashboardShell } from "@/components/shell";
import { getRepositoryById } from "@/lib/repository/service";
import type { Metadata } from "next";

import LayoutTabs from "./layoutTabs";
import LayoutTabs from "../../../../components/ui/layoutTabs";
import EnrollmentStatusBar from "./enrollmentStatusBar";

interface MetadataProps {
params: { repositoryId: string };
Expand All @@ -21,9 +24,19 @@ export default async function RepoDetailPageLayout({ params, children }) {
if (!repository) {
throw new Error("Repository not found");
}
const tabsData = [
{ href: `/enroll/${repository.id}/details`, value: "details", label: "Project Details" },
{ href: `/enroll/${repository.id}/levels`, value: "levels", label: "Levels" },
{ href: `/enroll/${repository.id}/leaderboard`, value: "leaderboard", label: "Leaderboard" },
{ href: `/enroll/${repository.id}/issues`, value: "issues", label: "Open Issues" },
];
return (
<>
<LayoutTabs repository={repository} />
<DashboardShell>
<DashboardHeader heading={repository?.name} text={repository?.description} />
<EnrollmentStatusBar repositoryId={repository.id} />
<LayoutTabs tabsData={tabsData} tabNumberInUrlPathSegment={3} defaultTab={"details"} />
</DashboardShell>
<main>{children}</main>
</>
);
Expand Down
34 changes: 0 additions & 34 deletions app/(dashboard)/enroll/[repositoryId]/layoutTabs.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
"use client";

import { Avatar, AvatarImage } from "@/components/ui/avatar";
import UserProfileSummary from "@/components/ui/userProfileSummary";
import { capitalizeEachWord } from "@/lib/utils/textformat";
import { TLevel } from "@/types/level";
import { TPointTransactionWithUser } from "@/types/pointTransaction";
import { useCallback, useEffect, useRef, useState } from "react";

Expand All @@ -10,12 +13,14 @@ interface LeaderboardProps {
leaderboardProfiles: TPointTransactionWithUser[];
repositoryId: string;
itemPerPage: number;
sortedLevels: TLevel[];
}

export default function Leaderboard({
leaderboardProfiles,
repositoryId,
itemPerPage,
sortedLevels,
}: LeaderboardProps): JSX.Element {
const loadingRef = useRef(null);
const [page, setPage] = useState<number>(1);
Expand Down Expand Up @@ -56,18 +61,69 @@ export default function Leaderboard({
};
}, [fetchNextPage, hasMore]);

let runningIndex = 1;
return (
<>
{profiles.map((profile, idx) => (
<UserProfileSummary
key={profile.login}
name={profile.name || profile.login}
points={profile.points}
avatarUrl={profile.avatarUrl || ""}
userGitHubId={profile.login}
index={idx + 1}
/>
))}
{sortedLevels.length > 0
? sortedLevels.map((level, levelIndex) => {
// Filter profiles for this level
const eligibleProfiles = profiles.filter((profile) => {
// Check if profile is eligible for this level
const isEligible = profile.points >= level.pointThreshold;

// Check if the profile has not been assigned to any lower level
const lowerLevels = sortedLevels.slice(0, levelIndex); // Lower levels
const assignedLowerLevel = lowerLevels.some(
(lowerLevel) => profile.points >= lowerLevel.pointThreshold
);

return isEligible && !assignedLowerLevel;
});

return (
<div key={level.id}>
<div className="mt-6 border-b border-foreground/25 py-3">
<div className="flex items-center gap-6">
<Avatar className="h-12 w-12">
<AvatarImage src={level.iconUrl} alt="level icon" />
</Avatar>
<div className="font-medium text-foreground">
Level {sortedLevels.length - levelIndex}: {capitalizeEachWord(level.name)}
</div>
</div>
</div>
{eligibleProfiles.length > 0 ? (
eligibleProfiles.map((profile) => (
<>
<UserProfileSummary
key={`${profile.login}-${level.id}`}
name={profile.name || profile.login}
points={profile.points}
avatarUrl={profile.avatarUrl || ""}
userGitHubId={profile.login}
index={runningIndex++}
/>
</>
))
) : (
<p key={`${level.id}-no-players`} className="py-4 text-foreground/50">
No player levelled up here yet. Who&prime;s gonna be the first?
</p>
)}
</div>
);
})
: profiles.map((profile, idx) => (
<UserProfileSummary
key={profile.login}
name={profile.name || profile.login}
points={profile.points}
avatarUrl={profile.avatarUrl || ""}
userGitHubId={profile.login}
index={idx + 1}
/>
))}

<div ref={loadingRef}></div>
</>
);
Expand Down
7 changes: 6 additions & 1 deletion app/(dashboard)/enroll/[repositoryId]/leaderboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ITEMS_PER_PAGE } from "@/lib/constants";
import { getPointsOfUsersInRepoByRepositoryId } from "@/lib/points/service";
import { getRepositoryById } from "@/lib/repository/service";
import { TLevel } from "@/types/level";

import Leaderboard from "./components/Leaderboard";

Expand All @@ -12,15 +13,19 @@ export default async function LeaderboardPage({ params }) {

const leaderboardProfiles = await getPointsOfUsersInRepoByRepositoryId(repository.id, 1);

const levels: TLevel[] = repository.levels as TLevel[];
const sortedLevels = levels.sort((a, b) => b.pointThreshold - a.pointThreshold); //descending by threshold

return (
<>
{leaderboardProfiles.length === 0 ? (
<p>No users found in the leaderboard.</p>
<p className="mt-4">No users found in the leaderboard.</p>
) : (
<Leaderboard
leaderboardProfiles={leaderboardProfiles}
itemPerPage={ITEMS_PER_PAGE}
repositoryId={repository.id}
sortedLevels={sortedLevels}
/>
)}
</>
Expand Down
61 changes: 61 additions & 0 deletions app/(dashboard)/enroll/[repositoryId]/levels/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import LevelDetailCard from "@/components/ui/levelDetailCard";
import { authOptions } from "@/lib/auth";
import { getPointsForUserInRepoByRepositoryId } from "@/lib/points/service";
import { getRepositoryById } from "@/lib/repository/service";
import { getCurrentUser } from "@/lib/session";
import {
ModifiedTagsArray,
calculateAssignabelNonAssignableIssuesForUserInALevel,
findCurrentAndNextLevelOfCurrentUser,
} from "@/lib/utils/levelUtils";
import { TLevel } from "@/types/level";
import { redirect } from "next/navigation";

export default async function Levels({ params: { repositoryId } }) {
const repository = await getRepositoryById(repositoryId);

if (!repository) {
throw new Error("Repository not found");
}

const user = await getCurrentUser();

if (!user) {
redirect(authOptions?.pages?.signIn || "/login");
}

const levels: TLevel[] = repository.levels as TLevel[];

// Sort levels based on point threshold in ascending order to get tags in correct order of levels.
const sortedLevels = levels.sort((a, b) => a.pointThreshold - b.pointThreshold);

const modifiedTagsArray: ModifiedTagsArray[] =
calculateAssignabelNonAssignableIssuesForUserInALevel(sortedLevels);

const totalPointsForUserInThisRepo: number = await getPointsForUserInRepoByRepositoryId(
repositoryId,
user.id
);

const { currentLevelOfUser, nextLevelForUser } = findCurrentAndNextLevelOfCurrentUser(
repository,
totalPointsForUserInThisRepo
);

return sortedLevels.length ? (
sortedLevels.map((level, idx) => (
<LevelDetailCard
key={level.id}
level={level} // this an item of the sortedLevels array.
idx={idx + 1}
modifiedTagsArray={modifiedTagsArray}
totalPoints={totalPointsForUserInThisRepo}
currentLevelOfUser={currentLevelOfUser}
nextLevelForUser={nextLevelForUser}
sortedLevels={sortedLevels}
/>
))
) : (
<div className="mt-4">No Levels yet.</div>
);
}
Loading
Loading