generated from muhtadeetaron/nextjs-template
chore: refactor code for type safety
This commit is contained in:
@ -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>
|
||||||
|
|||||||
@ -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} /> },
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
20
app/page.tsx
20
app/page.tsx
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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
32
lib/leaderboard.ts
Normal 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
8
types/auth.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export interface UserData {
|
||||||
|
name: string;
|
||||||
|
institution: string;
|
||||||
|
sscRoll: string;
|
||||||
|
hscRoll: string;
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user