chore: refactor code for type safety

This commit is contained in:
shafin-r
2025-07-27 03:19:25 +06:00
parent 0ea199c0fe
commit 3ef526ec1a
10 changed files with 135 additions and 155 deletions

View File

@ -1,24 +1,29 @@
"use client"; "use client";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, ReactNode } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link";
import Header from "@/components/Header"; import Header from "@/components/Header";
import SlidingGallery from "@/components/SlidingGallery"; import SlidingGallery from "@/components/SlidingGallery";
import BackgroundWrapper from "@/components/BackgroundWrapper"; import BackgroundWrapper from "@/components/BackgroundWrapper";
import DestructibleAlert from "@/components/DestructibleAlert"; import DestructibleAlert from "@/components/DestructibleAlert";
import { ChevronRight } from "lucide-react"; // Using Lucide React for icons import { ChevronRight } from "lucide-react"; // Using Lucide React for icons
import styles from "@/css/Home.module.css"; import styles from "@/css/Home.module.css";
import facebookStyles from "@/css/SlidingGallery.module.css";
import { API_URL } from "@/lib/auth"; import { API_URL } from "@/lib/auth";
import { Avatar, AvatarFallback } from "@radix-ui/react-avatar"; import { Avatar } from "@/components/ui/avatar";
import { getLinkedViews } from "@/lib/gallery-views";
import { getTopThree } from "@/lib/leaderboard";
interface LinkedView {
id: string;
content: ReactNode;
}
const page = () => { const page = () => {
const profileImg = "/images/static/avatar.jpg";
const router = useRouter(); const router = useRouter();
const [boardData, setBoardData] = useState([]); const [boardData, setBoardData] = useState([]);
const [boardError, setBoardError] = useState(null); const [boardError, setBoardError] = useState(null);
const [linkedViews, setLinkedViews] = useState<LinkedView[]>();
const performanceData = [ const performanceData = [
{ label: "Mock Test", progress: 20 }, { label: "Mock Test", progress: 20 },
@ -31,38 +36,6 @@ const page = () => {
{ label: "Chemistry", progress: 57 }, { label: "Chemistry", progress: 57 },
]; ];
const facebookViews = [
{
id: "1",
content: (
<Link
href="https://www.facebook.com/share/g/15jdqESvWV/?mibextid=wwXIfr"
className="w-full h-full block text-inherit box-border"
>
<div className="w-full h-full p-6 flex text-black bg-blue-50 rounded-4xl border-[0.5px] border-[#113768]/30">
<div className="">
<h3 className="text-2xl text-[#113768] font-black">
Meet, Share, and Learn!
</h3>
<p className="font-bold text-sm text-[#113768] ">
Join Facebook Community
</p>
</div>
<div className={facebookStyles.logoView}>
<Image
src="/images/static/facebook-logo.png"
alt="Facebook Logo"
width={150}
height={150}
/>
</div>
</div>
</Link>
),
},
];
// Fetch function for leaderboard data
useEffect(() => { useEffect(() => {
let isMounted = true; let isMounted = true;
async function fetchBoardData() { async function fetchBoardData() {
@ -77,32 +50,21 @@ const page = () => {
if (isMounted) setBoardError(error.message || "An error occurred"); if (isMounted) setBoardError(error.message || "An error occurred");
} }
} }
fetchBoardData(); const fetchedLinkedViews = getLinkedViews();
setLinkedViews(fetchedLinkedViews);
return () => { return () => {
isMounted = false; isMounted = false;
}; };
}, []); }, []);
const getTopThree = (boardData) => {
if (!boardData || boardData.length === 0) return [];
return boardData
.slice()
.sort((a, b) => b.points - a.points)
.slice(0, 3)
.map((player, index) => ({
...player,
rank: index + 1,
height: index === 0 ? 250 : index === 1 ? 200 : 170,
}));
};
return ( return (
<BackgroundWrapper> <BackgroundWrapper>
<div className={styles.container}> <div className={styles.container}>
<Header displayTabTitle={null} displayUser image={profileImg} /> <Header displayUser />
<div className={styles.scrollContainer}> <div className={styles.scrollContainer}>
<div className={styles.contentWrapper}> <div className={styles.contentWrapper}>
<SlidingGallery views={facebookViews} height="23vh" /> <SlidingGallery views={linkedViews} height="23vh" />
<div className={styles.mainContent}> <div className={styles.mainContent}>
{/* Categories Section */} {/* Categories Section */}
<div> <div>

View File

@ -5,13 +5,7 @@ import Link from "next/link";
import { ReactNode } from "react"; import { ReactNode } from "react";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import clsx from "clsx"; import clsx from "clsx";
import { import { House, LayoutGrid, Bookmark, Settings } from "lucide-react";
House,
LayoutGrid,
Bookmark,
CircleUser,
Settings,
} from "lucide-react";
const tabs = [ const tabs = [
{ name: "Home", href: "/home", component: <House size={30} /> }, { name: "Home", href: "/home", component: <House size={30} /> },

View File

@ -4,15 +4,39 @@ import BackgroundWrapper from "@/components/BackgroundWrapper";
import Header from "@/components/Header"; import Header from "@/components/Header";
import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { API_URL, getToken } from "@/lib/auth"; import { API_URL, getToken } from "@/lib/auth";
import { BoardData, getLeaderboard, getUserData } from "@/lib/leaderboard";
import { UserData } from "@/types/auth";
import Image from "next/image"; import Image from "next/image";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
const page = () => { const LeaderboardPage = () => {
const [boardError, setBoardError] = useState<string | null>(null); const [boardError, setBoardError] = useState<string | null>(null);
const [boardData, setBoardData] = useState([]); const [boardData, setBoardData] = useState<BoardData[]>([]);
const [userData, setUserData] = useState(null); const [userData, setUserData] = useState<UserData>();
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchUser() {
try {
const token = await getToken();
if (!token) throw new Error("User is not authenticated");
const response = await fetch(`${API_URL}/me`, {
method: "get",
headers: { Authorization: `Bearer ${token}` },
});
if (!response.ok) throw new Error("Failed to fetch user data");
const fetchedUserData = await response.json();
setLoading(false);
setUserData(fetchedUserData);
} catch (error) {
console.error(error);
setUserData(undefined);
}
}
async function fetchBoardData() { async function fetchBoardData() {
try { try {
const boardResponse = await fetch(`${API_URL}/leaderboard`, { const boardResponse = await fetch(`${API_URL}/leaderboard`, {
@ -37,33 +61,11 @@ const page = () => {
} }
} }
useEffect(() => {
async function fetchUser() {
try {
const token = await getToken();
if (!token) throw new Error("User is not authenticated");
const response = await fetch(`${API_URL}/me`, {
method: "get",
headers: { Authorization: `Bearer ${token}` },
});
if (!response.ok) throw new Error("Failed to fetch user data");
const fetchedUserData = await response.json();
setLoading(false);
setUserData(fetchedUserData);
} catch (error) {
console.error(error);
setUserData(null);
}
}
fetchUser(); fetchUser();
fetchBoardData(); fetchBoardData();
}, []); }, []);
const getTopThree = (boardData) => { const getTopThree = (boardData: BoardData[]) => {
if (!boardData || !Array.isArray(boardData)) return []; if (!boardData || !Array.isArray(boardData)) return [];
const sortedData = boardData const sortedData = boardData
.filter((player) => player?.points !== undefined) // Ensure `points` exists .filter((player) => player?.points !== undefined) // Ensure `points` exists
@ -78,24 +80,10 @@ const page = () => {
return [topThree[1], topThree[0], topThree[2]].filter(Boolean); // Handle missing players return [topThree[1], topThree[0], topThree[2]].filter(Boolean); // Handle missing players
}; };
const getLeaderboard = (boardData) => {
return boardData.slice().sort((a, b) => b.points - a.points);
};
const getUserData = (boardData, name) => {
if (!boardData || !Array.isArray(boardData)) return [];
const sortedData = boardData
.filter((player) => player?.name && player?.points !== undefined)
.sort((a, b) => b.points - a.points);
const result = sortedData.find((player) => player.name === name);
return result ? [{ ...result, rank: sortedData.indexOf(result) + 1 }] : [];
};
return ( return (
<BackgroundWrapper> <BackgroundWrapper>
<section> <section>
<Header displaySubject={"Leaderboard"} displayTabTitle={null} /> <Header displayTabTitle={"Leaderboard"} />
<section className="flex flex-col mx-10 pt-10 space-y-4"> <section className="flex flex-col mx-10 pt-10 space-y-4">
<section className="flex justify-evenly items-end"> <section className="flex justify-evenly items-end">
{getTopThree(boardData).map((student, idx) => {getTopThree(boardData).map((student, idx) =>
@ -124,7 +112,7 @@ const page = () => {
<div className="w-full border-[0.5px] border-[#c5dbf8] bg-[#c5dbf8]"></div> <div className="w-full border-[0.5px] border-[#c5dbf8] bg-[#c5dbf8]"></div>
<section className="border-[1px] border-[#c0dafc] w-full rounded-3xl p-6 space-y-4 mb-20"> <section className="border-[1px] border-[#c0dafc] w-full rounded-3xl p-6 space-y-4 mb-20">
<section> <section>
{getUserData(boardData, userData?.name).map((user, idx) => ( {getUserData(boardData, userData!.name).map((user, idx) => (
<div <div
key={idx} key={idx}
className="flex bg-[#113768] rounded-[8] py-2 px-4 justify-between items-center" className="flex bg-[#113768] rounded-[8] py-2 px-4 justify-between items-center"
@ -179,4 +167,4 @@ const page = () => {
); );
}; };
export default page; export default LeaderboardPage;

View File

@ -6,15 +6,7 @@ import { getToken, API_URL } from "@/lib/auth";
import { ChevronLeft, Edit2, Lock, Save } from "lucide-react"; import { ChevronLeft, Edit2, Lock, Save } from "lucide-react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { UserData } from "@/types/auth";
interface UserData {
name: string;
institution: string;
sscRoll: string;
hscRoll: string;
email: string;
phone: string;
}
const ProfilePage = () => { const ProfilePage = () => {
const router = useRouter(); const router = useRouter();

View File

@ -1,9 +1,7 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Montserrat } from "next/font/google"; import { Montserrat } from "next/font/google";
import "./globals.css"; import "./globals.css";
import { AuthProvider } from "@/context/AuthContext";
import { TimerProvider } from "@/context/TimerContext";
import { ExamProvider } from "@/context/ExamContext";
import { Providers } from "./providers"; import { Providers } from "./providers";
const montserrat = Montserrat({ const montserrat = Montserrat({

View File

@ -3,30 +3,10 @@
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import Image from "next/image"; import Image from "next/image";
import { useState, useEffect } from "react";
import BackgroundWrapper from "@/components/BackgroundWrapper"; import BackgroundWrapper from "@/components/BackgroundWrapper";
export default function Home() { export default function Home() {
const router = useRouter(); const router = useRouter();
const [windowDimensions, setWindowDimensions] = useState({
width: 0,
height: 0,
});
useEffect(() => {
function handleResize() {
setWindowDimensions({
width: window.innerWidth,
height: window.innerHeight,
});
}
// Set initial dimensions
handleResize();
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return ( return (
<BackgroundWrapper> <BackgroundWrapper>

View File

@ -1,33 +1,27 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import Image from "next/image";
import { ChevronLeft, Layers } from "lucide-react"; import { ChevronLeft, Layers } from "lucide-react";
import { useTimer } from "@/context/TimerContext"; import { useTimer } from "@/context/TimerContext";
import styles from "@/css/Header.module.css"; import styles from "@/css/Header.module.css";
import { useExam } from "@/context/ExamContext"; import { useExam } from "@/context/ExamContext";
import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { useModal } from "@/context/ModalContext"; import { useModal } from "@/context/ModalContext";
import { API_URL, getToken } from "@/lib/auth";
import { UserData } from "@/types/auth";
const API_URL = "https://examjam-api.pptx704.com"; interface HeaderProps {
displayUser?: boolean;
// You'll need to implement getToken for Next.js - could use cookies, localStorage, etc. displaySubject?: string;
const getToken = async () => { displayTabTitle: string;
if (typeof window === "undefined") { examDuration?: string;
return null; }
}
// Extract authToken from cookies
const match = document.cookie.match(/(?:^|;\s*)authToken=([^;]*)/);
return match ? decodeURIComponent(match[1]) : null;
};
const Header = ({ const Header = ({
image,
displayUser, displayUser,
displaySubject, displaySubject,
displayTabTitle, displayTabTitle,
examDuration, examDuration,
}) => { }: HeaderProps) => {
const router = useRouter(); const router = useRouter();
const { open } = useModal(); const { open } = useModal();
const { clearExam } = useExam(); const { clearExam } = useExam();
@ -35,7 +29,7 @@ const Header = ({
examDuration ? parseInt(examDuration) * 60 : 0 examDuration ? parseInt(examDuration) * 60 : 0
); );
const { timeRemaining, stopTimer } = useTimer(); const { timeRemaining, stopTimer } = useTimer();
const [userData, setUserData] = useState(null); const [userData, setUserData] = useState<UserData>();
useEffect(() => { useEffect(() => {
if (!examDuration) return; if (!examDuration) return;

View File

@ -1,4 +1,5 @@
// lib/resultViews.tsx // lib/gallery-views.tsx
import { Link } from "lucide-react";
import Image from "next/image"; import Image from "next/image";
interface ExamResults { interface ExamResults {
@ -97,3 +98,34 @@ export const getResultViews = (examResults: ExamResults | null) => [
), ),
}, },
]; ];
export const getLinkedViews = () => [
{
id: "1",
content: (
<Link
href="https://www.facebook.com/share/g/15jdqESvWV/?mibextid=wwXIfr"
className="w-full h-full block text-inherit box-border"
>
<div className="w-full h-full p-6 flex text-black bg-blue-50 rounded-4xl border-[0.5px] border-[#113768]/30">
<div className="">
<h3 className="text-2xl text-[#113768] font-black">
Meet, Share, and Learn!
</h3>
<p className="font-bold text-sm text-[#113768] ">
Join Facebook Community
</p>
</div>
<div className="flex justify-center items-center shrink-0">
<Image
src="/images/static/facebook-logo.png"
alt="Facebook Logo"
width={150}
height={150}
/>
</div>
</div>
</Link>
),
},
];

32
lib/leaderboard.ts Normal file
View File

@ -0,0 +1,32 @@
export interface BoardData {
id: string;
name: string;
points: number;
}
export const getTopThree = (boardData: BoardData[]) => {
if (!boardData || boardData.length === 0) return [];
return boardData
.slice()
.sort((a, b) => b.points - a.points)
.slice(0, 3)
.map((player, index) => ({
...player,
rank: index + 1,
height: index === 0 ? 250 : index === 1 ? 200 : 170,
}));
};
export const getLeaderboard = (boardData: BoardData[]) => {
return boardData.slice().sort((a, b) => b.points - a.points);
};
export const getUserData = (boardData: BoardData[], name: string) => {
if (!boardData || !Array.isArray(boardData)) return [];
const sortedData = boardData
.filter((player) => player?.name && player?.points !== undefined)
.sort((a, b) => b.points - a.points);
const result = sortedData.find((player) => player.name === name);
return result ? [{ ...result, rank: sortedData.indexOf(result) + 1 }] : [];
};

8
types/auth.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
export interface UserData {
name: string;
institution: string;
sscRoll: string;
hscRoll: string;
email: string;
phone: string;
}