Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
92 changes: 78 additions & 14 deletions src/app/_components/Profile/ProfileCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { useState } from "react";
import { api } from "~/trpc/react";
import { AiOutlineLoading } from "react-icons/ai";
import Link from "next/link";
import { IoWarning } from "react-icons/io5";
import { IoCheckmarkCircle } from "react-icons/io5";

interface ProfileCardProps {
idUser: string;
Expand All @@ -20,10 +22,12 @@ export default function ProfileCard({
}: ProfileCardProps) {
const updateUser = api.profile.update.useMutation();
const [NewLeetcodeUser, setInputValue] = useState("");
const { data, isLoading, error, refetch, isRefetching } = api.profile.getById.useQuery(idUser);

const { data, isLoading, error, refetch, isRefetching } =
api.profile.getById.useQuery(idUser);
if (isLoading || error || !data) {
return (
<div className="p-6 w-80 bg-gray-700 rounded-xl text-center text-white">
<div className="w-80 rounded-xl bg-gray-700 p-6 text-center text-white">
Loading…
</div>
);
Expand All @@ -35,7 +39,7 @@ export default function ProfileCard({
<div
className={cn(
"w-96 bg-gradient-to-br from-gray-800 to-gray-900",
"p-6 rounded-2xl shadow-2xl text-white flex flex-col gap-4"
"flex flex-col gap-4 rounded-2xl p-6 text-white shadow-2xl",
)}
>
{/* Encabezado: foto y nombre */}
Expand All @@ -44,10 +48,10 @@ export default function ProfileCard({
<img
src={image}
alt={`${name}'s profile`}
className="w-20 h-20 rounded-full border-2 border-accent object-cover"
className="h-20 w-20 rounded-full border-2 border-accent object-cover"
/>
) : (
<FaUser className="w-20 h-20 text-accent" />
<FaUser className="h-20 w-20 text-accent" />
)}
<div>
<h2 className="text-2xl font-bold leading-snug">{name}</h2>
Expand All @@ -64,7 +68,7 @@ export default function ProfileCard({
placeholder="New LeetCode Handle"
value={NewLeetcodeUser}
onChange={(e) => setInputValue(e.target.value)}
className="flex-1 bg-gray-700 placeholder-gray-500 text-white px-4 py-2 rounded-lg focus:ring-2 focus:ring-accent outline-none transition"
className="flex-1 rounded-lg bg-gray-700 px-4 py-2 text-white placeholder-gray-500 outline-none transition focus:ring-2 focus:ring-accent"
/>
<button
onClick={async () => {
Expand All @@ -74,12 +78,12 @@ export default function ProfileCard({
});
await refetch();
}}
disabled={isRefetching}
disabled={isRefetching || NewLeetcodeUser.length === 0}
className={cn(
"p-3 rounded-full",
isRefetching
? "bg-gray-600 cursor-not-allowed"
: "bg-accent hover:bg-accent-dark transition"
"rounded-full p-3",
isRefetching || NewLeetcodeUser.length === 0
? "cursor-not-allowed bg-gray-600"
: "hover:bg-accent-dark bg-accent transition",
)}
>
{isRefetching ? (
Expand All @@ -90,16 +94,76 @@ export default function ProfileCard({
</button>
</div>

<UserStatus leetcodeUser={leetcodeUser ?? ""} returnSuccess={true} />

{/* Botón de cierre de sesión */}
<div className="gap-2">
<Link
href="/api/auth/signout"
className="block w-full bg-red-600 hover:bg-red-700 text-white font-semibold py-2 rounded-lg transition text-center"
className="block w-full rounded-lg bg-red-600 py-2 text-center font-semibold text-white transition hover:bg-red-700"
>
Sign out
</Link>
</div>

</div>
);
}
}
const UserStatus = ({
leetcodeUser,
returnSuccess = true,
}: {
leetcodeUser: string;
returnSuccess?: boolean;
}) => {
const { data: leetcodeData, isLoading } =
api.leetcode.getLeetcodeUserData.useQuery(
{
username: leetcodeUser ?? "",
},
{
enabled: leetcodeUser.length > 0,
},
);

if (leetcodeUser?.length == 0) {
return (
<div className="flex flex-row items-center justify-center gap-3 text-red-500">
<IoWarning size={30} />
<p>No LeetCode username set.</p>
</div>
);
}
if (isLoading) {
return (
<div className="flex flex-row items-center justify-center gap-3 text-blue-500">
<AiOutlineLoading className="animate-spin" size={30} />
<p>Validating leetcode handle...</p>
</div>
);
}

if (!leetcodeData) {
return (
<div className="flex flex-row items-center justify-center gap-3 text-orange-500">
<IoWarning size={30} />
<p>
Unable to fetch LeetCode user data. Please check your username or try
again later.
</p>
</div>
);
}

if (returnSuccess)
return (
<div
title={`Leetcode name: ${leetcodeData.matchedUser.profile.realName}. Description: ${leetcodeData.matchedUser.profile.aboutMe}`}
className="flex flex-row items-center justify-center gap-3 text-green-400"
>
<IoCheckmarkCircle size={30} />
<p>Leetcode handle validated!</p>
</div>
);

return <></>;
};
115 changes: 94 additions & 21 deletions src/app/_components/week/weekInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@ import Unauthorized from "../unauthorized";
import SolvedToggle from "./solvedToggle";
import { IoIosStar } from "react-icons/io";
import Link from "next/link";
import { IoWarning } from "react-icons/io5";
import { IoCheckmarkCircle } from "react-icons/io5";

const getLevelStyles = (level: string) => {
switch (level) {
case 'WARMUP':
return 'bg-teal-500/60 text-teal-100 border-teal-400/50';
case 'MEDIUM':
return 'bg-yellow-500/60 text-yellow-100 border-yellow-400/50';
case 'HARDER':
return 'bg-orange-500/60 text-orange-100 border-orange-400/50';
case 'INSANE':
return 'bg-red-500/60 text-red-100 border-red-400/50';
case "WARMUP":
return "bg-teal-500/60 text-teal-100 border-teal-400/50";
case "MEDIUM":
return "bg-yellow-500/60 text-yellow-100 border-yellow-400/50";
case "HARDER":
return "bg-orange-500/60 text-orange-100 border-orange-400/50";
case "INSANE":
return "bg-red-500/60 text-red-100 border-red-400/50";
default:
return 'bg-gray-500/60 text-gray-100 border-gray-400/50';
return "bg-gray-500/60 text-gray-100 border-gray-400/50";
}
};

Expand All @@ -38,6 +40,7 @@ const WeekInfo = async ({ id }: { id: string }) => {
});
}
const week = await api.week.getWeekPublic({ id: id });

return (
<div>
{week ? (
Expand All @@ -51,20 +54,27 @@ const WeekInfo = async ({ id }: { id: string }) => {
<div className="font-main text-primary-foreground">
{week.description}
</div>
<ul className="text-white list-disc pl-4">
<ul className="list-disc pl-4 text-white">
{week.resources.map((resource, index) => (
<li key={index} className="text-primary-foreground ">
<li key={index} className="text-primary-foreground">
{resource}
</li>
))}
</ul>
<UserStatus leetcodeUser={leetcodeUser ?? ""} />
</div>

<div className="rounded-xl bg-primary-light p-4 w-max">
<div className="w-max rounded-xl bg-primary-light p-4">
<Subtitle label="Resources" />
<div className="font-main text-sm text-primary-foreground flex flex-col pr-5">
<div className="flex flex-col pr-5 font-main text-sm text-primary-foreground">
{week.detailResources.map((resource, index) => (
<Link href={resource.url} key={index} target="_blank" rel="noopener noreferrer" className="underline hover:text-gray-100 text-nowrap">
<Link
href={resource.url}
key={index}
target="_blank"
rel="noopener noreferrer"
className="text-nowrap underline hover:text-gray-100"
>
{resource.title}
</Link>
))}
Expand Down Expand Up @@ -93,25 +103,36 @@ const WeekInfo = async ({ id }: { id: string }) => {
rel="noopener noreferrer"
className="hover:underline"
>
<div className="flex flex-row items-center gap-2">
{problem.recommended && (
<IoIosStar />
)}
<div
className="flex flex-row items-center gap-2"
title={
problem.recommended
? "Recommended problem. Try to complete this challenge!"
: ""
}
>
{problem.recommended && <IoIosStar />}
{problem.name}
</div>
</a>
</td>
<td>
<span className={`px-2 py-1 rounded-full text-xs font-medium border ${getLevelStyles(problem.level)}`}>
{problem.level.charAt(0).toUpperCase() + problem.level.slice(1).toLowerCase()}
<span
className={`rounded-full border px-2 py-1 text-xs font-medium ${getLevelStyles(problem.level)}`}
>
{problem.level.charAt(0).toUpperCase() +
problem.level.slice(1).toLowerCase()}
</span>
</td>
<td>{problem.solvedBy?.length ?? 0}</td>
<td>
{userId ? (
<SolvedToggle
problemId={problem.id}
initialSolved={problem.solvedBy?.some((u) => u.id === userId) ?? false}
initialSolved={
problem.solvedBy?.some((u) => u.id === userId) ??
false
}
userId={userId}
/>
) : (
Expand All @@ -132,4 +153,56 @@ const WeekInfo = async ({ id }: { id: string }) => {
);
};

const UserStatus = async ({
leetcodeUser,
returnSuccess = true,
}: {
leetcodeUser: string;
returnSuccess?: boolean;
}) => {
const leetcodeUserData =
leetcodeUser && leetcodeUser.length > 0
? await api.leetcode.getLeetcodeUserData({
username: leetcodeUser ?? "",
})
: null;

if (leetcodeUser?.length == 0) {
return (
<div className="mt-5 flex flex-row items-center gap-3 text-red-500">
<IoWarning size={30} />
<p>
You have not set your LeetCode username in your profile. Please do so
to track your progress.
</p>
</div>
);
}

if (!leetcodeUserData) {
return (
<div className="mt-5 flex flex-row items-center gap-3 text-orange-500">
<IoWarning size={30} />
<p>
Unable to fetch LeetCode user data. Please check your username or try
again later.
</p>
</div>
);
}

if (returnSuccess)
return (
<div
title={`Leetcode name: ${leetcodeUserData.matchedUser.profile.realName}. Description: ${leetcodeUserData.matchedUser.profile.aboutMe}`}
className="mt-5 flex flex-row items-center gap-3 text-green-400"
>
<IoCheckmarkCircle size={30} />
<p>Leetcode handle validated successfully. </p>
</div>
);

return <></>;
};

export default WeekInfo;
Loading