Skip to content

Commit

Permalink
Merge pull request #203 from Nexusrex18/prod
Browse files Browse the repository at this point in the history
Refactor Credits Page #193
SkySingh04 authored Jan 26, 2025
2 parents 74b4133 + 9bdcc5a commit bd853dd
Showing 10 changed files with 229 additions and 172 deletions.
25 changes: 6 additions & 19 deletions app/(default)/Credits/addcredit/page.jsx
Original file line number Diff line number Diff line change
@@ -5,8 +5,7 @@ import {useRouter} from 'next/navigation';

export default function AddCreditPage() {
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const [linkedinUrl, setLinkedinUrl] = useState('');
const [githubUrl, setGithubUrl] = useState('');
const [image,setImage] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState(null);
@@ -15,7 +14,7 @@ export default function AddCreditPage() {

const handleSubmit = async(e) => {
e.preventDefault();
if(!name || !description || !linkedinUrl || !image){
if(!name || !githubUrl || !image){
setError('Please fill in all fields');
return;
}
@@ -26,8 +25,7 @@ export default function AddCreditPage() {
try {
const formData = new FormData();
formData.append('name' , name);
formData.append('description' , description);
formData.append('linkedinUrl' , linkedinUrl);
formData.append('githubUrl' , githubUrl);
formData.append('image' , image);

const response = await fetch('/api/credits/new' , {
@@ -70,25 +68,14 @@ export default function AddCreditPage() {
/>
</div>

{/* Description */}
<div className="mb-4">
<label className="block text-slate-100 mb-2">Description</label>
<textarea
className="w-full p-2 rounded bg-gray-700 text-white"
rows="4"
value={description}
onChange={(e) => setDescription(e.target.value)}
></textarea>
</div>

{/* LinkedIn URL */}
<div className="mb-4">
<label className="block text-slate-100 mb-2">LinkedIn URL</label>
<label className="block text-slate-100 mb-2">GitHub URL</label>
<input
type="url"
className="w-full p-2 rounded bg-gray-700 text-white"
value={linkedinUrl}
onChange={(e) => setLinkedinUrl(e.target.value)}
value={githubUrl}
onChange={(e) => setGithubUrl(e.target.value)}
/>
</div>

68 changes: 29 additions & 39 deletions app/(default)/Credits/page.jsx
Original file line number Diff line number Diff line change
@@ -79,11 +79,10 @@ export default function PinPage() {
const handleFormSubmit = async (e) => {
e.preventDefault();

const { name, description, linkedinUrl } = e.target.elements;
const { name, githubUrl } = e.target.elements;
const updatedCredit = {
name: name.value,
description: description.value,
linkedinUrl: linkedinUrl.value,
githubUrl: githubUrl.value,
};

try {
@@ -123,11 +122,10 @@ export default function PinPage() {
setShowEditIcons((prev) => !prev);
};


return (
<div className="w-full flex flex-col items-center justify-center">
<h1 className="text-3xl sm:text-4xl font-bold text-slate-100 mt-14 mb-6">
Website Contributors
Website <span className="text-green-500">Contributors</span>
</h1>

{isLoading && <p className="text-slate-100">Loading...</p>}
@@ -153,20 +151,13 @@ export default function PinPage() {
className="w-full p-2 rounded bg-gray-700 text-white"
/>
</div>

<div className="mb-4">
<label className="block mb-2 font-medium">Description</label>
<textarea
name="description"
defaultValue={selectedCredit.description}
className="w-full p-2 rounded bg-gray-700 text-white"
></textarea>
</div>
<div className="mb-4">
<label className="block mb-2 font-medium">LinkedIn URL</label>
<label className="block mb-2 font-medium">GitHub URL</label>
<input
type="url"
name="linkedinUrl"
defaultValue={selectedCredit.linkedinUrl}
defaultValue={selectedCredit.githubUrl}
className="w-full p-2 rounded bg-gray-700 text-white"
/>
</div>
@@ -190,16 +181,18 @@ export default function PinPage() {

{/* Grid container */}

<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mt-[10px]">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mt-[10px] items-start">
{!isLoading &&
!error &&
contributors.map((contributor) => (
<div className="relative" key={contributor._id}>
[...contributors]
.sort((a,b) => a.name.localeCompare(b.name))
.map((contributor) => (
<div
className="relative flex flex-col items-center"
key={contributor._id}
>
{/* PinContainer */}
<PinContainer
title="Visit Linkedin"
href={contributor.linkedinUrl}
>
<PinContainer title="Visit GitHub" href={contributor.githubUrl}>
<div className="flex flex-col p-4 tracking-tight text-slate-100/50 w-[20rem] h-[20rem] relative bg-gray-900 rounded-md">
{/* Image wrapper */}
<div className="relative h-full w-full">
@@ -215,9 +208,6 @@ export default function PinPage() {
<h3 className="font-bold text-base text-slate-100">
{contributor.name}
</h3>
<p className="text-base text-slate-300 mt-2">
{contributor.description}
</p>
</div>
</div>
</div>
@@ -255,21 +245,21 @@ export default function PinPage() {
</div>

{isAdmin && (
<div className="w-full flex justify-center mt-10">
<button
className="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-500 transition"
onClick={() => router.push("/Credits/addcredit")}
>
Add Contributor
</button>
<div className="w-full flex justify-center mt-10">
<button
className="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-500 transition"
onClick={() => router.push("/Credits/addcredit")}
>
Add Contributor
</button>

<button
className="bg-white text-green-600 px-4 py-2 rounded hover:bg-gray-500 transition ml-4"
onClick={toggleEditIcons}
>
Edit
</button>
</div>
<button
className="bg-white text-green-600 px-4 py-2 rounded hover:bg-gray-500 transition ml-4"
onClick={toggleEditIcons}
>
Edit
</button>
</div>
)}
</div>
);
8 changes: 3 additions & 5 deletions app/(default)/api/credits/new/route.ts
Original file line number Diff line number Diff line change
@@ -33,11 +33,10 @@ export async function POST(request: NextRequest) {

const formData = await request.formData();
const name = formData.get('name') as string;
const description = formData.get('description') as string;
const linkedinUrl = formData.get('linkedinUrl') as string;
const githubUrl = formData.get('githubUrl') as string;
const file = formData.get('image') as File;

if(!name || !description || !file || !linkedinUrl) {
if(!name || !file || !githubUrl) {
return NextResponse.json({error: 'Missing required fields'}, {status: 400});
}

@@ -47,8 +46,7 @@ export async function POST(request: NextRequest) {

const newCredit = new Credit({
name,
description,
linkedinUrl,
githubUrl,
imageUrl: secure_url,
publicId: public_id,
});
80 changes: 80 additions & 0 deletions app/(default)/api/credits/newCollaborator/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { NextRequest, NextResponse } from "next/server";
import connectMongoDB from "@/lib/dbConnect";
import cloudinary from 'cloudinary';
import Credit from "@/models/Credit";

cloudinary.v2.config({
cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
api_key: process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY,
api_secret: process.env.NEXT_PUBLIC_CLOUDINARY_API_SECRET,
});

function uploadToCloudinary(fileBuffer: Buffer): Promise<{ secure_url: string; public_id: string }> {
return new Promise((resolve, reject) => {
const uploadStream = cloudinary.v2.uploader.upload_stream(
{ folder: "credits" },
(error, result) => {
if (error) return reject(error);
if (!result) return reject(new Error("Cloudinary upload failed"));
resolve({
secure_url: result.secure_url,
public_id: result.public_id,
});
}
);
uploadStream.end(fileBuffer); // Write buffer data and end the stream
});
}

export async function POST(request: NextRequest) {
try{
await connectMongoDB();

const repoUrl = "https://api.github.com/repos/pbdsce/PB_Website/contributors";

const contributorsResponse = await fetch(repoUrl);
if(!contributorsResponse.ok) {
return NextResponse.json({error: 'Failed to fetch contributors'}, {status: contributorsResponse.status})
}

const contributors = await contributorsResponse.json();

const newCredits = [];

for(const contributor of contributors) {
const { id , name , login , avatar_url , html_url } = contributor;
const contributorName = name || login;

const existingCredit = await Credit.findOne({name: login});
if(existingCredit) {
continue;
}

const avatarResponse = await fetch(avatar_url);
if(!avatarResponse.ok) {
throw new Error(`Failed to fetch avatar for ${contributorName}`);
}

const avatarBuffer = Buffer.from(await avatarResponse.arrayBuffer());

const { secure_url , public_id } = await uploadToCloudinary(avatarBuffer);

const newCredit = new Credit({
userId: id,
name: contributorName,
githubUrl: html_url,
imageUrl: secure_url,
publicId: public_id,
});

await newCredit.save();

newCredits.push(newCredit);
}

return NextResponse.json({success: true, newCredits}, {status: 201});
} catch (error) {
console.log("Error creating credits:", error);
return NextResponse.json({error: 'Failed to create credits'}, {status: 500});
}
}
47 changes: 41 additions & 6 deletions app/(default)/api/hustle/route.ts
Original file line number Diff line number Diff line change
@@ -31,10 +31,30 @@ export async function POST() {
process.env.VJUDGE_CONTEST_API ||
"https://vjudge.net/contest/data?draw=2&start=0&length=20&sortDir=desc&sortCol=0&category=mine&running=3&title=&owner=Pbhustle&_=1733642420751";

const { data } = await axios.get(API_URL);
// Add headers to mimic a browser request
const headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'en-US,en;q=0.9',
'Referer': 'https://vjudge.net/',
'Origin': 'https://vjudge.net',
'Cookie': process.env.VJUDGE_COOKIE || 'JSESSlONID=7YP9VCUCK4ZTTTNQO0U0OQFW6ZDHMJXV; _ga=GA1.1.533089565.1728634731; Jax.Q=04yash|PMVRLFQGIPE5GOTC1SGE6B4W785LQJ; __gads=ID=99f1126d6fe46914:T=1728634731:RT=1737718428:S=ALNI_Mb_KPRQ0uquyzQSN44fpJKwUo53GA; __gpi=UID=00000f3e7503e3cd:T=1728634731:RT=1737718428:S=ALNI_Mb0ueGqLg2jRi_frTwTAZsUQqg_wQ; __eoi=ID=9732350f6625bc1b:T=1728634731:RT=1737718428:S=AA-AfjZnX0ZhSiL1BHBPEMWVFpeh; cf_clearance=rHe4M.4i47txtgYJQPXs0a.LnH.QYSRELQUQKvBeHv0-1737728607-1.2.1.1-QS6DaWVn4ZlR0CDzelDl644mlMOCZU1ty_NqdHf68O5PjZEOniNFWXUEEk3GrVQj0CwVlOScM6DpclcDaZKTv39_vxVd.tdzDDdfI3hBk.BKBOopC_pgojfmWDpHxvMqGBJbqfcoq36wQJQchRX4B0.IUalcf8OnqtyKwFa6mOj1a1oNoBst3jz_nVLMzWjzzQsxtJmhLlSGveAigCmovzVeHuJsXGSGifWipcLBKO2U_QCnzDVWlZ5N0Rqs7qlOPhzT9xmvceNQGlIZLoINYpq5_WyfCbumeC6XsX0i.H4; FCNEC=%5B%5B%22AKsRol-aSOdWtkNjxLPUwAZiyc5kmxDI81NA-AWYxiD_dMfHxJZ0hX5MBVRm9H0Pb0bLbRu7vmWOG3ZJAKwynbFz-3CLj98y_Ps-u7uC3PX4myF02jFz23muu0K9r3Xot0YQRKs-gKcJoQetbuaLAVrOLTIdtXKr4w%3D%3D%22%5D%5D; JSESSIONID=E55418E82CB952060BE04F7A459FD1FF; _ga_374JLX1715=GS1.1.1737728605.51.1.1737729138.60.0.0',
};

const { data } = await axios.get(API_URL, { headers });

if (!data || !data.data || !data.data[0]) {
console.error("Invalid response format from VJudge");
return NextResponse.json({
error: "Invalid response from VJudge",
details: "No contest data available"
}, { status: 400 });
}

const ccode = data.data[0][0];
const { data: rankData } = await axios.get(
`https://vjudge.net/contest/rank/single/${ccode}`
`https://vjudge.net/contest/rank/single/${ccode}`,
{ headers }
);

const leaderboardDoc = await LeaderboardModel.findOne({
@@ -113,8 +133,19 @@ export async function POST() {
message: "Leaderboard updated successfully",
rankings: leaderboardRankings,
});
} catch (error) {
return NextResponse.json({ error: "Failed to update leaderboard" });
} catch (error: any) {
if (axios.isAxiosError(error)) {
console.error("API Error:", error.response?.data || error.message);
} else {
console.error("API Error:", error);
}
return NextResponse.json({
error: "Failed to update leaderboard",
details: error.response?.data || error.message,
status: error.response?.status || 500
}, {
status: error.response?.status || 500
});
}
}

@@ -133,7 +164,11 @@ export async function GET() {
leaderboard: leaderboardDoc,
},
});
} catch (error) {
return NextResponse.json({ error: "Failed to fetch hustle data" });
} catch (error: any) {
console.error("Database error:", error);
return NextResponse.json({
error: "Failed to fetch hustle data",
details: error.message
}, { status: 500 });
}
}
2 changes: 0 additions & 2 deletions app/(default)/page.tsx
Original file line number Diff line number Diff line change
@@ -19,7 +19,6 @@ import SparklesText from "@/components/magicui/sparkles-text";
import Achievements from '@/components/achievements';
import Founder from "@/components/founder";
import Share from "@/components/share";
import CreditComp from "@/components/CreditComp";

export default function Home() {
return (
@@ -52,7 +51,6 @@ export default function Home() {
<Founder />
<Achievements />
<Share />
<CreditComp />
</>
);
}
Loading

0 comments on commit bd853dd

Please sign in to comment.