Skip to content

Comments

feat: Add Past Contests submission and verification#142

Merged
aviralsaxena16 merged 1 commit intoOpenLake:mainfrom
hansikareddy29:feature/past-contests-submission
Feb 14, 2026
Merged

feat: Add Past Contests submission and verification#142
aviralsaxena16 merged 1 commit intoOpenLake:mainfrom
hansikareddy29:feature/past-contests-submission

Conversation

@hansikareddy29
Copy link
Contributor

@hansikareddy29 hansikareddy29 commented Feb 14, 2026

📦 Pull Request

Submit changes for review
Title: feat: Add "Past Contests" Practice Mode & Secure Solution Submission


📌 Related Issue

  • Reshapes the contest experience to include past contests for practice.

✨ Changes Introduced

Frontend Features

  • Past Contests: Added a toggle switch in the Contests page to view "Past Contests" alongside "Upcoming Contests".
  • Contest Problem View: Users can now click on a past contest to view its problem list (fetched dynamically or via API for Codeforces).
  • Solution Submission Modal: A full-featured modal for submitting solutions to problems.
  • Verification System: Users can verify their solutions directly against Codeforces submissions if they have already solved them on the platform.

Backend API & Security

  • New API Route: /api/submit-solution - Handles solution submissions securely using Firebase Admin SDK (prevents client-side permission errors).
  • New API Route: /api/verify-codeforces - Verifies user submissions against the Codeforces API to prevent cheating.
  • New API Route: /api/past-contests - Fetches historical contest data from Clist API with caching and fallback mechanisms.

🤔 Why This Change?

  • Problem: Users could only see upcoming contests but had no way to practice on previous contests or earn coins for solving them.
  • Solution: This PR introduces a "Practice Mode" for past contests. It allows users to simulate participation, submit code, and earn coins securely.
  • Impact: Significantly increases user engagement by providing a vast library of practice problems from past contests across multiple platforms (Codeforces, LeetCode, etc.).

🖼️ Screenshots / Loom Video

Screencast from 2026-02-14 11-23-40.webm

🧪 Testing

  • Past Contests List: Verified that the list loads correctly and filters by platform work (Codeforces, LeetCode, etc.).
  • Problem Generation: Checked that clicking a contest generates/fetches the correct problem list.
  • Submission Flow: Tested the "Submit Solution" modal; verified that the API correctly receives data.

📝 Documentation Updates

  • Updated .env.example to document new required Clist API credentials.
  • Ensure CONTRIBUTING.md standards are met.

✅ Checklist

  • Created a new branch for PR (feature/past-contests-submission)
  • Follows JavaScript/TypeScript Styleguide
  • No console warnings/errors (Cleaned up logs)
  • Self-reviewed my code
  • Code follows project conventions

🚀 Deployment Notes

  • Environment Variables: The project requires FIREBASE_PROJECT_ID, FIREBASE_CLIENT_EMAIL, and FIREBASE_PRIVATE_KEY in .env for the new secure submission API to work. #

Summary by CodeRabbit

  • New Features
    • Browse and filter past coding contests by platform (Codeforces, LeetCode, CodeChef, AtCoder)
    • View contest problems with direct links
    • Submit solutions using code editor with multiple language support
    • Verify solutions directly from Codeforces
    • Earn coins on your first submission for each problem

@vercel
Copy link

vercel bot commented Feb 14, 2026

@hansikareddy29 is attempting to deploy a commit to the aviralsaxena16's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions
Copy link

🎉 Thanks for Your Contribution to CanonForces! ☺️

We'll review it as soon as possible. In the meantime, please:

  • ✅ Double-check the file changes.
  • ✅ Ensure that all commits are clean and meaningful.
  • ✅ Link the PR to its related issue (e.g., Closes #123).
  • ✅ Resolve any unaddressed review comments promptly.

💬 Need help or want faster feedback?
Join our Discord 👉 CanonForces Discord

Thanks again for contributing 🙌 – @hansikareddy29!
cc: @aviralsaxena16

@coderabbitai
Copy link

coderabbitai bot commented Feb 14, 2026

Walkthrough

Introduces a complete contest submission system with past contest listing, problem viewing, and code submission capabilities. Adds API routes for fetching contests from CLIST, submitting solutions with coin rewards, and verifying Codeforces submissions. Includes React components for contest browsing and solution submission with Monaco editor support.

Changes

Cohort / File(s) Summary
Environment & Configuration
.env.example, tsconfig.json
Added CLIST_USERNAME and CLIST_API_KEY environment variables; updated JSX option from "preserve" to "react-jsx" for new runtime transform.
Contest API Routes
src/pages/api/past-contests.ts, src/pages/api/submit-solution.ts, src/pages/api/verify-codeforces.ts
New API routes: past-contests fetches and filters contests from CLIST API with platform mapping; submit-solution handles code submissions with Firestore transactions and coin rewards (10 coins for first submission); verify-codeforces validates Codeforces problem solutions.
Contest UI Components & Styles
src/components/contests/PastContestsList.tsx, src/components/contests/PastContestsList.module.css, src/components/contests/ContestProblems.tsx, src/components/contests/ContestProblems.module.css
New components: PastContestsList displays filterable past contests with platform tabs and skeleton loading; ContestProblems modal shows contest details and problem grid with view/submit actions. CSS modules provide responsive styling, animations (fade-in, slide-up), and hover effects.
Solution Submission UI
src/components/contests/SolutionSubmitModal.tsx, src/components/contests/SolutionSubmitModal.module.css
Solution submission modal with Monaco editor, language selector, and dual submission flows: direct code submission or Codeforces verification. Handles user authentication, code validation, coin award logic, and success states. Comprehensive CSS styling with animations and responsive adjustments.
Type Definitions & Utilities
src/types/contest-submission.ts, src/utils/generate-contest-problems.ts
New types: ContestSubmission, PastContest, ContestProblem, and SUPPORTED_LANGUAGES constant. Utility generateProblemsForContest fetches real Codeforces problem names and generates platform-specific problem links and metadata.
Page Integration
src/pages/contests-list.tsx
Integrated PastContestsList component with view toggle (Upcoming/Past), preserving existing upcoming contests logic while adding past contests browsing capability.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Browser
    participant API as Next.js API
    participant CLIST as CLIST API
    participant Firestore
    
    User->>Browser: Navigate to Past Contests
    Browser->>API: GET /api/past-contests
    API->>CLIST: Fetch contests (30-day window)
    CLIST-->>API: Contest data
    API->>API: Filter platforms & language
    API-->>Browser: Return contest list
    Browser->>User: Display contests with filter tabs
    
    User->>Browser: Click "View Problems"
    Browser->>Browser: Fetch generated problem metadata
    Browser->>User: Show contest problems modal
Loading
sequenceDiagram
    actor User
    participant Browser
    participant SolutionModal as Solution Modal
    participant API as Next.js API
    participant Firestore as Firestore DB
    participant CoinSystem as Coin System
    
    User->>SolutionModal: Select language & write code
    User->>SolutionModal: Click "Submit Solution"
    SolutionModal->>API: POST /submit-solution (code, contest, problem)
    API->>Firestore: Check if first submission for this problem
    API->>API: Compute coinsEarned (10 if first, 0 if repeat)
    API->>Firestore: Start transaction
    Firestore->>Firestore: Create submission document
    alt First Submission
        Firestore->>Firestore: Increment user coins by 10
    end
    Firestore-->>API: Commit transaction
    API-->>SolutionModal: Success response + coinsEarned
    SolutionModal->>User: Show success modal with coins awarded
Loading
sequenceDiagram
    actor User
    participant Modal as Solution Modal
    participant API as Next.js API
    participant CFApi as Codeforces API
    participant Firestore
    
    User->>Modal: Click "Verify Codeforces Submission"
    Modal->>API: POST /verify-codeforces (handle, contestId, problemIndex)
    API->>CFApi: Fetch user.status (submissions)
    CFApi-->>API: Recent submissions
    API->>API: Search for matching submission (verdict=OK)
    alt Found Match
        API-->>Modal: {verified: true}
        Modal->>Firestore: Save verified submission
        Modal->>User: Show verification success
    else No Match
        API-->>Modal: {verified: false}
        Modal->>User: Show verification failed
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • Jagath-P

Poem

🐰 Hops with glee around the code,
Contest paths now clearly showed!
Problems bloom, solutions gleam,
Coins reward each coding dream,
Firebase transactions lock it tight,
Everything feels just right!

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main changes: adding past contests functionality with submission and verification capabilities.
Description check ✅ Passed The description covers all key template sections: related issue, changes introduced (frontend and backend), problem/solution/impact rationale, testing verification, deployment notes, and checklist completion.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

🤖 Fix all issues with AI agents
In @.env.example:
- Around line 27-30: Add the missing server-side Firebase Admin SDK environment
variables to .env.example so the backend can initialize the SDK in
src/lib/firebase_admin.ts; specifically add entries for FIREBASE_PROJECT_ID,
FIREBASE_CLIENT_EMAIL, and FIREBASE_PRIVATE_KEY with placeholder values and a
short comment that these are required for server-side initialization (used by
the firebase admin initialization code in src/lib/firebase_admin.ts and
endpoints like /api/submit-solution).

In `@src/components/contests/ContestProblems.tsx`:
- Around line 63-70: The anchor uses problem.problemLink which is typed string |
undefined, so guard against undefined by conditionally rendering the <a> or
using a safe fallback; update the render in ContestProblems.tsx to only output
the anchor with className styles.viewButton and target/rel attributes when
problem.problemLink is truthy (or alternatively render a non-clickable
element/button with a disabled style when undefined), referencing the
problem.problemLink property and preserving the existing view text and
attributes when present.
- Around line 13-24: The effect that calls generateProblemsForContest currently
sets problems directly and provides no loading or error feedback and depends on
the whole contest object; update the ContestProblems component to add loading
and error state (e.g., isLoading, error via useState), set isLoading=true before
calling generateProblemsForContest and false after, catch and save any exception
to error and avoid swallowing it, and render a loading indicator and an error
message in the UI when appropriate instead of showing "0 Problems"; also change
the effect dependency from contest to contest.contestId (or other stable
primitive) to avoid unnecessary re-runs. Use the existing functions/setters
(generateProblemsForContest, setProblems, setIsSubmitModalOpen) and the
useEffect block to implement these changes.

In `@src/components/contests/PastContestsList.tsx`:
- Around line 22-25: In PastContestsList, the fetch block uses res.json()
without checking HTTP status; update the code in the async fetch logic to check
res.ok first (after awaiting fetch("/api/past-contests")) and handle non-2xx
responses by throwing or logging an error (optionally parsing error body via
res.text() or res.json()), only call res.json() and setContests(data.contests ||
[]) when res.ok is true; ensure any thrown error is caught in the existing
try/catch so the component doesn't attempt to use invalid data.
- Around line 46-58: The getTimeAgo function contains a dead branch: after
returning "Yesterday" when days === 1, the subsequent if (days < 7) uses a
redundant days === 1 ternary that can never run; update the (days < 7) branch in
getTimeAgo to unconditionally return `${days} days ago` (or handle
singular/plural by checking days === 1 only if you remove the earlier
"Yesterday" return), removing the unreachable ternary so the function returns
"Today", "Yesterday", "X days ago" for 2–6 days, then weeks/months as before.

In `@src/components/contests/SolutionSubmitModal.tsx`:
- Line 151: The setTimeout in SolutionSubmitModal that calls onSuccess and
onClose after 2s can fire after the component unmounts; store the timer ID in a
ref (e.g. successTimerRef) when you call setTimeout in the submit success path
(where setSuccess is used) and in the component's useEffect cleanup or useEffect
with empty deps clearTimeout(successTimerRef.current) to prevent invoking
onSuccess/onClose or setState on an unmounted component; ensure you also clear
the timer before setting a new one if the success path can run multiple times.
- Around line 106-141: The read using getDocs(q) inside runTransaction is
outside Firestore's transactional snapshot and allows a race where two
concurrent submissions both see no prior document and both get coins; change the
logic to use a deterministic document ID (e.g., combine user.uid and
problem.problemId) and perform transaction.get(doc(db, "contest_submissions",
deterministicId)) to check existence and transaction.set on that same
deterministic doc (use the existing newSubmissionRef/doc identifiers to locate
the right spot), and only transaction.update the user's coins if the
transactional read shows no prior submission; additionally, the setTimeout
mentioned should be moved into a useEffect hook and store its timer id so you
can clearTimeout on cleanup to avoid it firing after unmount.

In `@src/pages/api/past-contests.ts`:
- Around line 95-97: The catch block in src/pages/api/past-contests.ts is
swallowing the error variable `err`—update the handler’s catch to log the actual
error (include `err` or `err.stack`) instead of just a static warning; locate
the catch that currently calls console.warn("⚠️ Clist failed for past contests,
returning fallback") and change it to include the error details (or use
console.error) so the `err` value is output with the message for debugging.
- Around line 65-67: The code assigns const contests: PastContest[] =
data.objects and then calls .filter() without guarding against data or
data.objects being null/undefined; add a null guard or default to an empty array
before using array methods. Locate the assignment to contests (and any
subsequent .filter() usage) and change it to use a safe value (e.g., const
rawObjects = data?.objects ?? []; const contests = rawObjects.filter(...)) or
explicitly check data && Array.isArray(data.objects) and handle the error path
(log and return an empty list or appropriate response) so the endpoint won't
crash if CLIST changes shape.

In `@src/pages/api/submit-solution.ts`:
- Around line 44-51: The handler currently skips the minimum length check when
language === "Verified", trusting an unvalidated request field; fix by
validating language against SUPPORTED_LANGUAGES (or an explicit Verified flag)
before applying length rules: ensure the request body’s language is one of
SUPPORTED_LANGUAGES, treat verified submissions via a separate verified
submission flow/flag (not by setting language to "Verified"), and only bypass
the code.length < 10 check when the submission is authenticated/marked as
verified by your verification logic; update the validation in the
submit-solution handler (references: language, code, SUPPORTED_LANGUAGES) to
enforce this separation and reject unknown language values.
- Around line 29-42: The handler currently trusts userId from the request body;
instead validate the caller by verifying an auth token (e.g., Firebase) and
derive the user ID from the verified token. In submit-solution handler read the
Authorization bearer token (or session cookie), call
admin.auth().verifyIdToken(token) (or your auth library's verify method), obtain
uid and replace use of body.userId with that uid, return 401 on missing/invalid
token, and remove/ignore userId from req.body so coins/credits are awarded only
to the authenticated uid.
- Around line 53-65: The duplicate-check query (existingSubmissions) runs
outside the transaction causing a race; move that query inside the Firestore
transaction so the check and subsequent writes are atomic. Inside the
transaction callback (the code using adminDb.runTransaction), call
transaction.get(...) on the same query built from
submissionsRef.where('userId','==',userId).where('problemId','==',problemId).limit(1),
determine isFirstSubmission from the returned snapshot, compute coinsEarned
accordingly, then perform the create of the submission doc and the user coin
update within the same transaction; remove the external existingSubmissions read
and any logic that relies on it outside the transaction.

In `@src/pages/api/verify-codeforces.ts`:
- Around line 14-62: The handler in verify-codeforces.ts lacks any
authentication or rate-limiting; before calling the Codeforces API you should
validate the caller (e.g., check a session via next-auth or validate a bearer
token/header) and return 401/403 for unauthenticated requests, then enforce
per-user or global rate limits (in-memory, Redis, or a library) to throttle
calls to the fetch logic (the apiUrl/fetch block) to prevent abuse; add these
checks at the top of the exported async function handler (before reading
req.body and before the fetch), and log and respond with appropriate error codes
when auth or rate limits fail.
- Line 29: The URL construction for Codeforces checks is unsafe and too narrow:
encode the user-supplied handle using encodeURIComponent before interpolating
into the apiUrl (the apiUrl variable in the verify-codeforces handler) to
prevent malformed URLs, and increase the count parameter (or make it
configurable via an environment variable or handler option) instead of
hardcoding count=50 so the verification scans more submissions; update the
apiUrl construction to use the encoded handle and replace the literal count with
a larger value or a configurable constant.
- Around line 46-54: The code calls data.result.some(...) without guarding
against data.result being null/undefined; update the isSolved computation
(referencing targetContestId, isSolved, contestId, problemIndex) to treat
data.result as an array (e.g., use Array.isArray(data.result) ? data.result : []
or default to []) before calling .some(), so the .some() call never runs on a
non-array and avoids a TypeError when the API returns no result field.

In `@src/types/contest-submission.ts`:
- Line 12: The field declaration submittedAt: Date | any collapses to any and
loses type safety; change it to a precise union that covers Firestore sentinels
and returned timestamps, e.g. submittedAt: Date | FirebaseFirestore.FieldValue |
FirebaseFirestore.Timestamp (or the equivalent FieldValue/Timestamp types from
your Firebase SDK), and add the necessary import/namespace references so the
compiler recognizes FirebaseFirestore.FieldValue and
FirebaseFirestore.Timestamp; update the submittedAt type in the interface in
src/types/contest-submission.ts and import the appropriate types from your
Firebase package.

In `@src/utils/generate-contest-problems.ts`:
- Around line 25-31: getProblemTitle currently uses parseInt(contestId) which
returns NaN for non-numeric IDs and breaks the seed math; replace parseInt usage
with a deterministic string-to-number hash of contestId (optionally include
problemIndex) to produce a numeric seed. Compute a simple stable hash (e.g.,
iterate chars, multiply/add and take Math.abs) then combine with
LETTERS.indexOf(problemIndex) as the existing code does, use that seed for the
difficulty calculation (seed % 3) and for selecting a title (Math.floor(seed /
3) % titles.length) so getProblemTitle, LETTERS and PROBLEM_TITLES continue to
work for any contestId string.
- Around line 134-158: The switch in buildProblemLink declares consts
(fixedBaseUrl, contestId) inside case clauses which can cause TDZ issues; fix by
wrapping each case body that declares const in its own block (e.g., case
'codeforces': { ... } and case 'atcoder': { ... }) so fixedBaseUrl and contestId
are scoped to their blocks, then return from inside those blocks. Ensure no
cross-case fallthrough remains.

In `@tsconfig.json`:
- Line 3: Update the tsconfig.json compilerOptions "jsx" setting from
"react-jsx" to "preserve" to match Next.js recommendations; locate the "jsx" key
under compilerOptions in tsconfig.json and replace its value so the project uses
Next/SWC for JSX transformation.
🧹 Nitpick comments (11)
.env.example (2)

27-30: Address static analysis hints and formatting consistency.

The dotenv-linter tool has flagged a couple of minor issues:

  1. Missing blank line at the end of the file (line 30).
  2. Keys should be alphabetically ordered: CLIST_API_KEY should come before CLIST_USERNAME.
  3. The comment on line 28 has inconsistent capitalization: "Clist_API_key" should be "CLIST_API_KEY" for consistency.
📝 Proposed formatting fixes
 
-# Clist Username and Clist_API_key for past contests
-CLIST_USERNAME="Add your Clist Api username"
+# CLIST_API_KEY and CLIST_USERNAME for past contests
 CLIST_API_KEY="Add your key"
+CLIST_USERNAME="Add your Clist Api username"
+

4-10: Consider using placeholder values for Firebase credentials in .env.example.

The .env.example file currently contains what appear to be actual production Firebase credentials rather than placeholder values. While Firebase public API keys are designed to be exposed in client-side code and are protected by security rules, it's a best practice to use placeholder values in .env.example files to avoid accidentally exposing project-specific configuration.

Consider replacing these with placeholder values like:

NEXT_PUBLIC_FIREBASE_API_KEY="your-firebase-api-key"
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN="your-project.firebaseapp.com"

Note: This is a pre-existing issue not introduced by this PR, so it can be addressed separately.

src/components/contests/ContestProblems.module.css (1)

200-220: Rename keyframes to kebab-case to satisfy the Stylelint keyframes-name-pattern rule.

The linter flags fadeIn and slideUp as violations of the kebab-case naming convention for keyframes. Since this rule is configured project-wide, rename them for consistency.

Proposed fix
-@keyframes fadeIn {
+@keyframes fade-in {
     from {
         opacity: 0;
     }
 
     to {
         opacity: 1;
     }
 }
 
-@keyframes slideUp {
+@keyframes slide-up {
     from {
         opacity: 0;
         transform: translateY(30px);
     }
 
     to {
         opacity: 1;
         transform: translateY(0);
     }
 }

Also update the references on lines 15 and 28:

-    animation: fadeIn 0.2s ease-out;
+    animation: fade-in 0.2s ease-out;
-    animation: slideUp 0.3s ease-out;
+    animation: slide-up 0.3s ease-out;
src/components/contests/SolutionSubmitModal.module.css (2)

18-53: Rename keyframes to kebab-case per Stylelint keyframes-name-pattern rule.

Same issue as in ContestProblems.module.cssfadeInfade-in, slideUpslide-up. Update the animation references on lines 15 and 38 accordingly.


150-160: Replace deprecated clip property with clip-path.

The clip property is deprecated per the Stylelint property-no-deprecated rule. Replace with the modern equivalent for the visually-hidden pattern.

Proposed fix
 .fileInput {
     /* Hidden but accessible via label */
     position: absolute;
     width: 1px;
     height: 1px;
     padding: 0;
     margin: -1px;
     overflow: hidden;
-    clip: rect(0, 0, 0, 0);
+    clip-path: inset(50%);
     border: 0;
 }
src/pages/contests-list.tsx (1)

180-181: Prefer a stable key over array index.

Using idx as the React key can cause subtle rendering issues if the list gets reordered or filtered. Consider using a combination of unique contest properties.

Proposed fix
-                  filteredContests.map((contest, idx) => (
-                    <div key={idx} className={styles.card}>
+                  filteredContests.map((contest) => (
+                    <div key={`${contest.platform}-${contest.contestName}-${contest.startTime}`} className={styles.card}>
src/components/contests/PastContestsList.tsx (1)

119-120: Prefer a stable unique key over array index.

Using idx as the React key can cause incorrect reconciliation if the list is reordered or filtered. Each contest has a contestId — use that (or a composite key) instead.

Proposed fix
-                        filteredContests.map((contest, idx) => (
-                            <div key={idx} className={styles.card}>
+                        filteredContests.map((contest) => (
+                            <div key={contest.contestId} className={styles.card}>
src/utils/generate-contest-problems.ts (1)

36-68: Client-side calls to Codeforces API on every modal open — no caching.

fetchCodeforcesProblems is called from ContestProblems.tsx (client-side useEffect) every time a user clicks "View Problems." This hammers the Codeforces API unnecessarily, and the data is static. Consider adding a simple in-memory cache (e.g., a Map<string, Map<string, string>> keyed by contestId) or moving this fetch server-side to generateProblemsForContest behind an API route.

src/pages/api/past-contests.ts (1)

33-99: No caching — every page load triggers a full CLIST API call.

The PR description mentions "caching and fallback," but only the fallback (empty array) is implemented. Since contest data changes infrequently (daily at most), consider adding a simple in-memory cache with a TTL (e.g., 5–10 minutes) to avoid hammering the CLIST API and improve response times.

Example in-memory cache
// Add above the handler
let cachedData: { contests: PastContest[]; timestamp: number } | null = null;
const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes

// Inside handler, before the fetch:
if (cachedData && Date.now() - cachedData.timestamp < CACHE_TTL_MS) {
    return res.status(200).json({ contests: cachedData.contests, source: "clist" });
}

// After building the contests array:
cachedData = { contests, timestamp: Date.now() };
src/components/contests/SolutionSubmitModal.tsx (2)

293-301: Prefer CSS modules over inline styles for consistency.

Line 298 uses style={{ backgroundColor: '#3b82f6', marginRight: 'auto', marginLeft: '10px' }} while the rest of the component uses styles.* from CSS modules. Move these styles to SolutionSubmitModal.module.css for consistency and easier theming.


196-215: handleSubmit validates code length but not against trivial/malicious payloads.

A 10-character minimum is easily gamed (e.g., "aaaaaaaaaa"). If coin rewards are at stake, consider server-side validation in the API route or at least a more meaningful client-side check. This is a minor concern since the actual security boundary should be server-side.

Comment on lines +27 to +30

# Clist Username and Clist_API_key for past contests
CLIST_USERNAME="Add your Clist Api username"
CLIST_API_KEY="Add your key" No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's read the complete .env.example file to see all variables
cat -n .env.example

Repository: OpenLake/canonforces

Length of output: 1333


🏁 Script executed:

# Search for Firebase Admin SDK usage in the codebase
rg -n 'FIREBASE_PROJECT_ID|FIREBASE_CLIENT_EMAIL|FIREBASE_PRIVATE_KEY' --type-add 'env:*.env*' --type env -A 2 -B 2

Repository: OpenLake/canonforces

Length of output: 462


🏁 Script executed:

# Search for Firebase Admin SDK initialization and usage in backend code
rg -n 'firebase-admin|admin\.initializeApp|admin\.credential' -t js -t ts -A 3 -B 1

Repository: OpenLake/canonforces

Length of output: 1338


🏁 Script executed:

# Check if there's a PR description or changelog that mentions these variables
fd -e md | head -20 | xargs grep -l 'FIREBASE_PROJECT_ID\|FIREBASE_CLIENT_EMAIL\|FIREBASE_PRIVATE_KEY' 2>/dev/null || echo "No matches found in markdown files"

Repository: OpenLake/canonforces

Length of output: 98


🏁 Script executed:

cat -n src/lib/firebase_admin.ts

Repository: OpenLake/canonforces

Length of output: 855


🏁 Script executed:

# Check for any setup or configuration documentation that might mention these variables
fd -e md -e txt | xargs grep -l 'FIREBASE_PROJECT_ID\|FIREBASE_CLIENT_EMAIL\|FIREBASE_PRIVATE_KEY' 2>/dev/null || echo "No matches in documentation"

Repository: OpenLake/canonforces

Length of output: 91


🏁 Script executed:

# Also check if there's a .env.local.example or deployment docs
find . -maxdepth 2 -type f \( -name "*.md" -o -name "DEPLOY*" -o -name "SETUP*" -o -name ".env*" \) | head -20

Repository: OpenLake/canonforces

Length of output: 190


🏁 Script executed:

# Check README and CONTRIBUTING for setup instructions
grep -n -A 5 -B 5 'Firebase\|environment\|.env' README.md CONTRIBUTING.md 2>/dev/null | head -60

Repository: OpenLake/canonforces

Length of output: 2597


Add required Firebase Admin SDK server-side environment variables to .env.example.

The PR adds Clist credentials but omits the server-side Firebase Admin SDK environment variables that are actively required by the codebase. The file src/lib/firebase_admin.ts explicitly initializes the Firebase Admin SDK with credentials from:

  • FIREBASE_PROJECT_ID
  • FIREBASE_CLIENT_EMAIL
  • FIREBASE_PRIVATE_KEY

These variables are missing from .env.example and are not documented anywhere in the repository. Since the README directs developers to check .env.example for setup, this omission will cause deployment failures and misconfiguration. Backend routes like /api/submit-solution depend on these credentials.

🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 30-30: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)


[warning] 30-30: [UnorderedKey] The CLIST_API_KEY key should go before the CLIST_USERNAME key

(UnorderedKey)

🤖 Prompt for AI Agents
In @.env.example around lines 27 - 30, Add the missing server-side Firebase
Admin SDK environment variables to .env.example so the backend can initialize
the SDK in src/lib/firebase_admin.ts; specifically add entries for
FIREBASE_PROJECT_ID, FIREBASE_CLIENT_EMAIL, and FIREBASE_PRIVATE_KEY with
placeholder values and a short comment that these are required for server-side
initialization (used by the firebase admin initialization code in
src/lib/firebase_admin.ts and endpoints like /api/submit-solution).

Comment on lines +13 to +24
const [problems, setProblems] = useState<ContestProblem[]>([]);
const [selectedProblem, setSelectedProblem] = useState<ContestProblem | null>(null);
const [isSubmitModalOpen, setIsSubmitModalOpen] = useState(false);

useEffect(() => {
// Generate problems for this contest
const loadProblems = async () => {
const generatedProblems = await generateProblemsForContest(contest);
setProblems(generatedProblems);
};
loadProblems();
}, [contest]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing loading and error states for async problem fetching.

While generateProblemsForContest is fetching (which calls the Codeforces API for CF contests), users see "0 Problems" in the header and an empty grid with no indication that data is loading. Also, if the fetch fails, the error is silently swallowed.

Proposed fix — add loading and error states
     const [problems, setProblems] = useState<ContestProblem[]>([]);
     const [selectedProblem, setSelectedProblem] = useState<ContestProblem | null>(null);
     const [isSubmitModalOpen, setIsSubmitModalOpen] = useState(false);
+    const [loading, setLoading] = useState(true);
+    const [error, setError] = useState<string | null>(null);
 
     useEffect(() => {
         const loadProblems = async () => {
-            const generatedProblems = await generateProblemsForContest(contest);
-            setProblems(generatedProblems);
+            try {
+                setLoading(true);
+                setError(null);
+                const generatedProblems = await generateProblemsForContest(contest);
+                setProblems(generatedProblems);
+            } catch (err) {
+                console.error('Failed to load problems:', err);
+                setError('Failed to load problems. Please try again.');
+            } finally {
+                setLoading(false);
+            }
         };
         loadProblems();
-    }, [contest]);
+    }, [contest.contestId]);

Note: Using contest (an object) as the useEffect dependency instead of contest.contestId will re-trigger on every parent re-render if the object reference isn't stable.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [problems, setProblems] = useState<ContestProblem[]>([]);
const [selectedProblem, setSelectedProblem] = useState<ContestProblem | null>(null);
const [isSubmitModalOpen, setIsSubmitModalOpen] = useState(false);
useEffect(() => {
// Generate problems for this contest
const loadProblems = async () => {
const generatedProblems = await generateProblemsForContest(contest);
setProblems(generatedProblems);
};
loadProblems();
}, [contest]);
const [problems, setProblems] = useState<ContestProblem[]>([]);
const [selectedProblem, setSelectedProblem] = useState<ContestProblem | null>(null);
const [isSubmitModalOpen, setIsSubmitModalOpen] = useState(false);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
// Generate problems for this contest
const loadProblems = async () => {
try {
setLoading(true);
setError(null);
const generatedProblems = await generateProblemsForContest(contest);
setProblems(generatedProblems);
} catch (err) {
console.error('Failed to load problems:', err);
setError('Failed to load problems. Please try again.');
} finally {
setLoading(false);
}
};
loadProblems();
}, [contest.contestId]);
🤖 Prompt for AI Agents
In `@src/components/contests/ContestProblems.tsx` around lines 13 - 24, The effect
that calls generateProblemsForContest currently sets problems directly and
provides no loading or error feedback and depends on the whole contest object;
update the ContestProblems component to add loading and error state (e.g.,
isLoading, error via useState), set isLoading=true before calling
generateProblemsForContest and false after, catch and save any exception to
error and avoid swallowing it, and render a loading indicator and an error
message in the UI when appropriate instead of showing "0 Problems"; also change
the effect dependency from contest to contest.contestId (or other stable
primitive) to avoid unnecessary re-runs. Use the existing functions/setters
(generateProblemsForContest, setProblems, setIsSubmitModalOpen) and the
useEffect block to implement these changes.

Comment on lines +63 to +70
<a
href={problem.problemLink}
target="_blank"
rel="noopener noreferrer"
className={styles.viewButton}
>
View Problem ↗
</a>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

problemLink is optional — guard against undefined.

ContestProblem.problemLink is typed as string | undefined (see src/types/contest-submission.ts line 30). Rendering <a href={undefined}> produces a link that navigates to the current page. Either hide the link when unavailable or provide a fallback.

Proposed fix
-                                <a
-                                    href={problem.problemLink}
-                                    target="_blank"
-                                    rel="noopener noreferrer"
-                                    className={styles.viewButton}
-                                >
-                                    View Problem ↗
-                                </a>
+                                {problem.problemLink ? (
+                                    <a
+                                        href={problem.problemLink}
+                                        target="_blank"
+                                        rel="noopener noreferrer"
+                                        className={styles.viewButton}
+                                    >
+                                        View Problem ↗
+                                    </a>
+                                ) : (
+                                    <span className={styles.viewButton} style={{ opacity: 0.5, cursor: 'default' }}>
+                                        No Link Available
+                                    </span>
+                                )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<a
href={problem.problemLink}
target="_blank"
rel="noopener noreferrer"
className={styles.viewButton}
>
View Problem
</a>
{problem.problemLink ? (
<a
href={problem.problemLink}
target="_blank"
rel="noopener noreferrer"
className={styles.viewButton}
>
View Problem
</a>
) : (
<span className={styles.viewButton} style={{ opacity: 0.5, cursor: 'default' }}>
No Link Available
</span>
)}
🤖 Prompt for AI Agents
In `@src/components/contests/ContestProblems.tsx` around lines 63 - 70, The anchor
uses problem.problemLink which is typed string | undefined, so guard against
undefined by conditionally rendering the <a> or using a safe fallback; update
the render in ContestProblems.tsx to only output the anchor with className
styles.viewButton and target/rel attributes when problem.problemLink is truthy
(or alternatively render a non-clickable element/button with a disabled style
when undefined), referencing the problem.problemLink property and preserving the
existing view text and attributes when present.

Comment on lines +22 to +25
try {
const res = await fetch("/api/past-contests");
const data: ApiResponse = await res.json();
setContests(data.contests || []);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing res.ok check before parsing JSON.

If the API returns a non-2xx status (e.g., 500), res.json() may still succeed but return an error payload—or it may throw on non-JSON bodies. Check res.ok before parsing to surface errors properly.

Proposed fix
             const res = await fetch("/api/past-contests");
+            if (!res.ok) {
+                throw new Error(`API error: ${res.status}`);
+            }
             const data: ApiResponse = await res.json();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
const res = await fetch("/api/past-contests");
const data: ApiResponse = await res.json();
setContests(data.contests || []);
try {
const res = await fetch("/api/past-contests");
if (!res.ok) {
throw new Error(`API error: ${res.status}`);
}
const data: ApiResponse = await res.json();
setContests(data.contests || []);
🤖 Prompt for AI Agents
In `@src/components/contests/PastContestsList.tsx` around lines 22 - 25, In
PastContestsList, the fetch block uses res.json() without checking HTTP status;
update the code in the async fetch logic to check res.ok first (after awaiting
fetch("/api/past-contests")) and handle non-2xx responses by throwing or logging
an error (optionally parsing error body via res.text() or res.json()), only call
res.json() and setContests(data.contests || []) when res.ok is true; ensure any
thrown error is caught in the existing try/catch so the component doesn't
attempt to use invalid data.

Comment on lines +46 to +58
const getTimeAgo = (ts: number) => {
const now = Date.now();
const diff = now - ts;
const days = Math.floor(diff / (1000 * 60 * 60 * 24));

if (days === 0) return "Today";
if (days === 1) return "Yesterday";
if (days < 7) return days === 1 ? `1 day ago` : `${days} days ago`;
const weeks = Math.floor(days / 7);
if (days < 30) return weeks === 1 ? `1 week ago` : `${weeks} weeks ago`;
const months = Math.floor(days / 30);
return months === 1 ? `1 month ago` : `${months} months ago`;
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Dead code branch in getTimeAgo.

Line 53: the days === 1 ternary branch is unreachable because days === 1 is already caught and returned on line 52 (return "Yesterday"). When execution reaches line 53, days is guaranteed to be ≥ 2.

Proposed fix
     if (days === 0) return "Today";
     if (days === 1) return "Yesterday";
-    if (days < 7) return days === 1 ? `1 day ago` : `${days} days ago`;
+    if (days < 7) return `${days} days ago`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getTimeAgo = (ts: number) => {
const now = Date.now();
const diff = now - ts;
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
if (days === 0) return "Today";
if (days === 1) return "Yesterday";
if (days < 7) return days === 1 ? `1 day ago` : `${days} days ago`;
const weeks = Math.floor(days / 7);
if (days < 30) return weeks === 1 ? `1 week ago` : `${weeks} weeks ago`;
const months = Math.floor(days / 30);
return months === 1 ? `1 month ago` : `${months} months ago`;
};
const getTimeAgo = (ts: number) => {
const now = Date.now();
const diff = now - ts;
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
if (days === 0) return "Today";
if (days === 1) return "Yesterday";
if (days < 7) return `${days} days ago`;
const weeks = Math.floor(days / 7);
if (days < 30) return weeks === 1 ? `1 week ago` : `${weeks} weeks ago`;
const months = Math.floor(days / 30);
return months === 1 ? `1 month ago` : `${months} months ago`;
};
🤖 Prompt for AI Agents
In `@src/components/contests/PastContestsList.tsx` around lines 46 - 58, The
getTimeAgo function contains a dead branch: after returning "Yesterday" when
days === 1, the subsequent if (days < 7) uses a redundant days === 1 ternary
that can never run; update the (days < 7) branch in getTimeAgo to
unconditionally return `${days} days ago` (or handle singular/plural by checking
days === 1 only if you remove the earlier "Yesterday" return), removing the
unreachable ternary so the function returns "Today", "Yesterday", "X days ago"
for 2–6 days, then weeks/months as before.

Comment on lines +46 to +54
const targetContestId = parseInt(contestId);

const isSolved = data.result.some((submission: any) => {
return (
submission.contestId === targetContestId &&
submission.problem.index === problemIndex &&
submission.verdict === 'OK'
);
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing null-safety on data.result.

If data.result is undefined or null (e.g., Codeforces API returns status: "OK" but with no result field for a user with zero submissions), calling .some() on it will throw a TypeError at runtime.

Proposed fix
         const targetContestId = parseInt(contestId);
 
-        const isSolved = data.result.some((submission: any) => {
+        const submissions = data.result ?? [];
+        const isSolved = submissions.some((submission: any) => {
             return (
                 submission.contestId === targetContestId &&
                 submission.problem.index === problemIndex &&
                 submission.verdict === 'OK'
             );
         });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const targetContestId = parseInt(contestId);
const isSolved = data.result.some((submission: any) => {
return (
submission.contestId === targetContestId &&
submission.problem.index === problemIndex &&
submission.verdict === 'OK'
);
});
const targetContestId = parseInt(contestId);
const submissions = data.result ?? [];
const isSolved = submissions.some((submission: any) => {
return (
submission.contestId === targetContestId &&
submission.problem.index === problemIndex &&
submission.verdict === 'OK'
);
});
🤖 Prompt for AI Agents
In `@src/pages/api/verify-codeforces.ts` around lines 46 - 54, The code calls
data.result.some(...) without guarding against data.result being null/undefined;
update the isSolved computation (referencing targetContestId, isSolved,
contestId, problemIndex) to treat data.result as an array (e.g., use
Array.isArray(data.result) ? data.result : [] or default to []) before calling
.some(), so the .some() call never runs on a non-array and avoids a TypeError
when the API returns no result field.

platform: string;
language: string;
code: string;
submittedAt: Date | any;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Date | any collapses to any, defeating type safety.

This likely accommodates Firestore's serverTimestamp() sentinel (a FieldValue). Use a more precise union instead.

Proposed fix
+import { FieldValue } from "firebase/firestore";
+
 export interface ContestSubmission {
   // ...
-  submittedAt: Date | any;
+  submittedAt: Date | FieldValue;
   // ...
 }
🤖 Prompt for AI Agents
In `@src/types/contest-submission.ts` at line 12, The field declaration
submittedAt: Date | any collapses to any and loses type safety; change it to a
precise union that covers Firestore sentinels and returned timestamps, e.g.
submittedAt: Date | FirebaseFirestore.FieldValue | FirebaseFirestore.Timestamp
(or the equivalent FieldValue/Timestamp types from your Firebase SDK), and add
the necessary import/namespace references so the compiler recognizes
FirebaseFirestore.FieldValue and FirebaseFirestore.Timestamp; update the
submittedAt type in the interface in src/types/contest-submission.ts and import
the appropriate types from your Firebase package.

Comment on lines +25 to +31
function getProblemTitle(contestId: string, problemIndex: string): string {
const seed = parseInt(contestId) + LETTERS.indexOf(problemIndex);
const difficulty = (seed % 3 === 0) ? 'easy' : (seed % 3 === 1) ? 'medium' : 'hard';
const titles = PROBLEM_TITLES[difficulty];
const selectedTitle = titles[Math.floor(seed / 3) % titles.length];
return selectedTitle;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

parseInt(contestId) returns NaN for non-numeric IDs, causing undefined to be returned.

For platforms like LeetCode (e.g., "weekly-contest-423") or AtCoder (e.g., "abc123"), parseInt returns NaN. This propagates through the arithmetic: NaN % 3NaN, and titles[NaN]undefined, so the function returns undefined instead of a string.

Use a hash-based seed that works for any string:

Proposed fix
 function getProblemTitle(contestId: string, problemIndex: string): string {
-    const seed = parseInt(contestId) + LETTERS.indexOf(problemIndex);
+    // Simple string hash to handle non-numeric contest IDs
+    let hash = 0;
+    for (let i = 0; i < contestId.length; i++) {
+        hash = (hash * 31 + contestId.charCodeAt(i)) | 0;
+    }
+    const seed = Math.abs(hash) + LETTERS.indexOf(problemIndex);
     const difficulty = (seed % 3 === 0) ? 'easy' : (seed % 3 === 1) ? 'medium' : 'hard';
     const titles = PROBLEM_TITLES[difficulty];
     const selectedTitle = titles[Math.floor(seed / 3) % titles.length];
     return selectedTitle;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function getProblemTitle(contestId: string, problemIndex: string): string {
const seed = parseInt(contestId) + LETTERS.indexOf(problemIndex);
const difficulty = (seed % 3 === 0) ? 'easy' : (seed % 3 === 1) ? 'medium' : 'hard';
const titles = PROBLEM_TITLES[difficulty];
const selectedTitle = titles[Math.floor(seed / 3) % titles.length];
return selectedTitle;
}
function getProblemTitle(contestId: string, problemIndex: string): string {
// Simple string hash to handle non-numeric contest IDs
let hash = 0;
for (let i = 0; i < contestId.length; i++) {
hash = (hash * 31 + contestId.charCodeAt(i)) | 0;
}
const seed = Math.abs(hash) + LETTERS.indexOf(problemIndex);
const difficulty = (seed % 3 === 0) ? 'easy' : (seed % 3 === 1) ? 'medium' : 'hard';
const titles = PROBLEM_TITLES[difficulty];
const selectedTitle = titles[Math.floor(seed / 3) % titles.length];
return selectedTitle;
}
🤖 Prompt for AI Agents
In `@src/utils/generate-contest-problems.ts` around lines 25 - 31, getProblemTitle
currently uses parseInt(contestId) which returns NaN for non-numeric IDs and
breaks the seed math; replace parseInt usage with a deterministic
string-to-number hash of contestId (optionally include problemIndex) to produce
a numeric seed. Compute a simple stable hash (e.g., iterate chars, multiply/add
and take Math.abs) then combine with LETTERS.indexOf(problemIndex) as the
existing code does, use that seed for the difficulty calculation (seed % 3) and
for selecting a title (Math.floor(seed / 3) % titles.length) so getProblemTitle,
LETTERS and PROBLEM_TITLES continue to work for any contestId string.

Comment on lines +134 to +158
function buildProblemLink(contest: PastContest, problemIndex: string): string {
const baseUrl = contest.contestLink;

switch (contest.platform.toLowerCase()) {
case 'codeforces':
// e.g., https://codeforces.com/contest/1234/problem/A
const fixedBaseUrl = baseUrl.replace('/contests/', '/contest/');
return `${fixedBaseUrl}/problem/${problemIndex}`;

case 'leetcode':
// LeetCode uses slug-based URLs, harder to construct
return baseUrl;

case 'codechef':
// e.g., https://www.codechef.com/problems/PROBLEMCODE
return baseUrl;

case 'atcoder':
// e.g., https://atcoder.jp/contests/abc123/tasks/abc123_a
const contestId = contest.contestId;
return `https://atcoder.jp/contests/${contestId}/tasks/${contestId}_${problemIndex.toLowerCase()}`;

default:
return baseUrl;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Wrap const declarations in switch cases inside blocks.

Biome correctly flags that const fixedBaseUrl (line 140) and const contestId (line 153) are hoisted within the switch scope, making them accessible (as TDZ references) from other case clauses. Wrap each case body in a block.

Proposed fix
     switch (contest.platform.toLowerCase()) {
-        case 'codeforces':
-            // e.g., https://codeforces.com/contest/1234/problem/A
-            const fixedBaseUrl = baseUrl.replace('/contests/', '/contest/');
-            return `${fixedBaseUrl}/problem/${problemIndex}`;
-
-        case 'leetcode':
+        case 'codeforces': {
+            const fixedBaseUrl = baseUrl.replace('/contests/', '/contest/');
+            return `${fixedBaseUrl}/problem/${problemIndex}`;
+        }
+        case 'leetcode':
             // LeetCode uses slug-based URLs, harder to construct
             return baseUrl;
-
-        case 'codechef':
+        case 'codechef':
             // e.g., https://www.codechef.com/problems/PROBLEMCODE
             return baseUrl;
-
-        case 'atcoder':
-            // e.g., https://atcoder.jp/contests/abc123/tasks/abc123_a
-            const contestId = contest.contestId;
-            return `https://atcoder.jp/contests/${contestId}/tasks/${contestId}_${problemIndex.toLowerCase()}`;
-
+        case 'atcoder': {
+            const contestId = contest.contestId;
+            return `https://atcoder.jp/contests/${contestId}/tasks/${contestId}_${problemIndex.toLowerCase()}`;
+        }
         default:
             return baseUrl;
     }
🧰 Tools
🪛 Biome (2.3.14)

[error] 140-140: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 153-153: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🤖 Prompt for AI Agents
In `@src/utils/generate-contest-problems.ts` around lines 134 - 158, The switch in
buildProblemLink declares consts (fixedBaseUrl, contestId) inside case clauses
which can cause TDZ issues; fix by wrapping each case body that declares const
in its own block (e.g., case 'codeforces': { ... } and case 'atcoder': { ... })
so fixedBaseUrl and contestId are scoped to their blocks, then return from
inside those blocks. Ensure no cross-case fallthrough remains.

{
"compilerOptions": {
"jsx": "preserve",
"jsx": "react-jsx",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check if this is a Next.js project
ls -la | grep -E 'next.config|package.json' | head -10
cat package.json | jq '.dependencies, .devDependencies' 2>/dev/null | grep -i next

# Check the git history for the jsx setting change
git log --oneline -10 -- tsconfig.json

# Show the current tsconfig.json content
cat tsconfig.json

Repository: OpenLake/canonforces

Length of output: 919


🏁 Script executed:

# Check if there are React imports in the codebase
fd -t f -e ts -e tsx . | head -30 | xargs rg -l 'import.*React' 2>/dev/null | head -10

Repository: OpenLake/canonforces

Length of output: 360


🏁 Script executed:

# Check if the tsx files in the codebase are using automatic JSX runtime (no React import) or traditional style
rg -A 2 'import' src/common/components/Notifications/NotificationItem.tsx src/common/components/Loading/Loading.tsx src/common/components/CodeEditor/CodeEditor.tsx 2>/dev/null | head -40

# Check Next.js documentation / best practices for jsx setting
# Also verify the React version being used
cat package.json | jq '.dependencies.react, .dependencies."react-dom"' 2>/dev/null

Repository: OpenLake/canonforces

Length of output: 1376


🏁 Script executed:

# Check if there are any .jsx files (not just .tsx) that would be affected
fd -e jsx . src/ 2>/dev/null | head -10

# Verify if any component is explicitly NOT importing React (indicating automatic JSX runtime is expected)
rg 'jsx|React' src/common/components/CodeEditor/CodeEditor.tsx -A 5 -B 5 | head -40

Repository: OpenLake/canonforces

Length of output: 1348


🏁 Script executed:

# Check if Next.js actually overwrites tsconfig jsx setting or if it respects it
# Look at next.config.js to see any jsx-related configuration
cat next.config.js

# Check for any eslint or other config that might relate to jsx
fd -t f 'eslint\|prettier' . --max-depth 2
cat .eslintrc* 2>/dev/null || cat .eslintignore 2>/dev/null || echo "No eslint config found"

# Verify the actual behavior: check if this is causing any issues by looking at compilation or test results
git log --oneline -20 | grep -i jsx || echo "No jsx-related commits found"

Repository: OpenLake/canonforces

Length of output: 775


🌐 Web query:

Next.js 16 tsconfig jsx setting react-jsx vs preserve official documentation

💡 Result:

In Next.js (including v16 docs), the recommended tsconfig.json setting is:

{
  "compilerOptions": {
    "jsx": "preserve"
  }
}

Next.js’ official migration guides explicitly tell you to set "jsx": "preserve" for compatibility. [1] [2]

What “react-jsx” vs “preserve” means (TypeScript official docs)

  • "jsx": "react-jsx": TypeScript transforms JSX into calls that use the automatic JSX runtime (React 17+). [3]
  • "jsx": "preserve": TypeScript does not transform JSX; it leaves JSX in the output for another toolchain step to handle. [3]

TypeScript also notes that JSX typing behavior differs based on jsx mode (e.g., classic vs automatic runtime typing). [4]

Bottom line: If you want the official Next.js config, use jsx: "preserve"; Next’s build pipeline handles the actual JSX transform. [1] [2]

Sources:
[1] Next.js “Migrating from Create React App” (TypeScript config includes jsx: "preserve") (nextjs.org)
[2] Next.js “Migrating from Vite” (Step: set jsx to preserve) (nextjs.org)
[3] TypeScript tsconfig option jsx (react-jsx vs preserve) (typescriptlang.org)
[4] TypeScript Handbook: JSX / how jsx affects typing/runtime expectations (typescriptlang.org)

Citations:


Change "jsx" to "preserve" to align with Next.js best practices.

Next.js officially recommends "jsx": "preserve" in tsconfig.json—the framework uses its own compiler (SWC) to handle JSX transformation. Although noEmit: true limits the impact on compilation, this setting deviates from Next.js conventions and can cause type-checking inconsistencies. Update it to match the Next.js standard configuration.

🤖 Prompt for AI Agents
In `@tsconfig.json` at line 3, Update the tsconfig.json compilerOptions "jsx"
setting from "react-jsx" to "preserve" to match Next.js recommendations; locate
the "jsx" key under compilerOptions in tsconfig.json and replace its value so
the project uses Next/SWC for JSX transformation.

@aviralsaxena16 aviralsaxena16 merged commit d3d91c0 into OpenLake:main Feb 14, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants