Skip to content

Conversation

@DeveloperAmrit
Copy link

@DeveloperAmrit DeveloperAmrit commented Feb 2, 2026

Description
Revamped the AOSSIE website to a modern, responsive, UX friendly website. Added proper folder structure, added new products page showcasing ready-to-download projects, improved responsiveness, improved UI/UX of all pages, and added animations.

Changes Made

  1. Add proper folder structure
  • Earlier, the website was using a mix of next js and React Router routing, making it pure Next.js routing
  • Now each page has its folder, having page.tsx and layout.tsx.
  • Refactored all components into different folders based on the page they are being used on.
  • Properly handled ideas data
  1. Added products page
  • Might change the name of this as per the suggestion
  • Contains ready to download build sdks of production-ready projects.
  • Each card has a guide button which leads to a specific page explaining about that application, how to set up, a video tutorial, and feedback.
  1. Improved UI/UX
  • Improved home page UI to deal with cluttered text and components.
  • Improved the about us page UI to show real-time data instead of hardcoded dummy data
  1. Added animations to the entire website
  • This improves UX a lot, added framer-motion minimalist animations all over the website.
  1. Improved responsiveness
  • Hero no longer breaks on small screens
  • Better responsiveness across all pages.

Preview
Hosted link - https://aossie-website.vercel.app/

Summary by CodeRabbit

Release Notes

  • New Features

    • Added About page with team timeline and project statistics
    • Added Apply page outlining GSoC application process steps
    • Added Products section showcasing applications with detailed guides
    • Reorganized Ideas section with year-based browsing and navigation
    • Enhanced homepage with featured projects, statistics dashboard, and improved layout
  • Improvements

    • Updated UI framework dependencies for better performance
    • Added markdown rendering support for documentation
    • Enhanced animations and visual effects throughout the application
    • Improved responsive design across all pages

@vercel
Copy link

vercel bot commented Feb 2, 2026

@DeveloperAmrit is attempting to deploy a commit to the AOSSIE Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Feb 2, 2026

📝 Walkthrough

Walkthrough

This pull request implements a comprehensive migration from Next.js Pages Router to App Router, restructuring the entire site architecture. New pages are created in src/app/, organized layouts are updated, components are reorganized into subdirectories, numerous MDX idea templates are refactored, and legacy Pages Router files are removed.

Changes

Cohort / File(s) Summary
Dependency Updates
package.json
Added telemetry opt-out script and bumped UI dependencies: @fortawesome/react-fontawesome (^0.2.0 → ^3.1.1), @next/mdx (^13.5.6 → ^14.1.3), framer-motion (^12.0.6 → ^12.30.0). Added react-markdown ^8.0.7.
App Router Core Pages
src/app/layout.jsx, src/app/page.jsx
Implemented RootLayout with metadata export, Header, and Footer integration. Redesigned home page with animated hero content, featured/random project filtering, Cards component refactor, new Stats component, and motion-based animations using framer-motion.
New Feature Pages
src/app/about/page.jsx, src/app/apply/page.jsx, src/app/products/page.jsx, src/app/ideas/page.jsx
Added About page with animated stats, chart integration, and section animations. Introduced Apply page with timeline and GSoC application steps. Created Products and Ideas listing pages with metadata exports and responsive layouts.
Dynamic Route Pages
src/app/products/[slug]/page.jsx, src/app/ideas/[year]/page.jsx, src/app/projects/page.jsx
Added generateStaticParams and generateMetadata functions. ProductPage renders product details, testimonials, installation guide, and feedback form. YearIdeasPage fetches year-specific ideas with not-found handling. Projects page splits into Ongoing and Production Ready sections with motion animations.
About & Apply Components
src/components/about/..., src/components/apply/...
Created Team and Timeline components for About page with framer-motion animations. Added ApplyHeader with 5-step horizontal timeline and icon indicators.
Product Components
src/components/products/...
Introduced CardProduct (animated product cards), FeedbackForm (star-rated feedback with submission handling), and ProductsHeader (animated typography).
Ideas Components
src/components/ideas/...
Added IdeaCard (grid items with animations), IdeasList (responsive grid layout), IdeasDisplay (toggle-able year sections), IdeasHeader (CTA and descriptive header), and updated IdeaLayout with client directive and navigation imports.
Home & Shared Components
src/components/home/..., src/components/shared/...
Migrated CardEffect and CardHome from root to home subdirectory with framer-motion animations. Moved Banner and Footer to shared with motion animations. Added Header updates with Products navigation, usePathname routing, and animation directives. Added Stats component with animated counters and intersection observer.
MDX Idea Templates
src/app/ideas/2022/..., src/app/ideas/2023/..., src/app/ideas/2024/..., src/app/ideas/2025/...
Updated 50+ idea pages: changed IdeaLayout import from @/components/IdeaLayout to @/components/ideas/IdeaLayout, refactored default export from props-spreading (props) => <IdeaLayout {...props} /> to children-explicit ({ children }) => <IdeaLayout>{children}</IdeaLayout>.
API & Helper Functions
src/app/api/stats/route.js, src/lib/ideas.js, src/helper/products.js
Created GET /api/stats endpoint fetching GitHub stats (repo count, contributors, yearly graph data). Implemented getIdeas(year) to scan and parse idea MDX front-matter. Added static products array with metadata, logos, links, and feedback data.
Project Data Enhancement
src/helper/projects.js
Added status field ('ongoing' or 'production') to nine project entries for filtering in Projects page.
Removed Legacy Patterns
src/pages/..., src/helper/getAllIdeas*.js
Deleted Pages Router files (_app.jsx, about.jsx, apply.jsx, ideas/index.jsx, ideas/2024/index.jsx) and old getAllIdeas helper modules. Removed root-level Banner, CardEffect, CardHome, Footer components and replaced with app router architecture.
MDX Configuration
src/mdx-components.js
Added useMDXComponents export for MDX component context integration.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Browser
    participant Server as App Router
    participant GitHub as GitHub API
    participant FileSystem as File System

    Client->>Browser: Request /about page
    Browser->>Server: GET /about
    Server->>Server: Render About component
    Server->>Browser: HTML with About content
    Browser->>Browser: Mount & fetch /api/stats
    Browser->>Server: GET /api/stats
    Server->>GitHub: Fetch AOSSIE org repos
    Server->>GitHub: Fetch repo contributors
    GitHub-->>Server: Repo data + contributor list
    Server->>Server: Aggregate stats & build graph data
    Server-->>Browser: JSON (years, projects, contributors, graphData)
    Browser->>Browser: Animate stats counters & render chart

    Client->>Browser: Request /ideas/2024
    Browser->>Server: GET /ideas/2024
    Server->>FileSystem: Scan src/app/ideas/2024/ for page.mdx files
    FileSystem-->>Server: List of idea MDX files
    Server->>Server: Extract front-matter meta from each file
    Server->>Server: Build idea objects with slug, meta
    Server-->>Browser: HTML with IdeasList & IdeaCards
    Browser->>Browser: Render and animate idea cards

    Client->>Browser: Request /products/[slug]
    Browser->>Server: GET /products/example-product
    Server->>Server: Match slug in products helper
    Server->>Browser: HTML with product details, setup guide, feedback
    Browser->>Browser: Render markdown, testimonials, feedback form
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hopping through the App Router's gate,
We've refactored pages and made them great,
With framer-motion's dance so smooth and bright,
Ideas flow in, components take flight!
From pages to app—our migration's complete, 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title 'Revamped AOSSIE website' clearly summarizes the main change: a comprehensive redesign of the website. It directly aligns with the primary objective of modernizing the AOSSIE website with improved UX, responsive design, and structural refactoring.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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

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: 20

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/components/ideas/IdeaLayout.jsx (1)

27-31: ⚠️ Potential issue | 🟡 Minor

Back button should fall back when there's no history.

When users land directly on a page, router.back() does nothing. Add a fallback to the ideas index.

 export function IdeaLayout({
   children,
   meta,
   isRssFeed = false,
 }) {
   let router = useRouter()
+  const handleBack = () => {
+    if (window.history.length > 1) {
+      router.back()
+    } else {
+      router.push('/ideas')
+    }
+  }
@@
               <button
                 type="button"
-                onClick={() => router.back()}
+                onClick={handleBack}
                 aria-label="Go back to articles"
package.json (1)

22-44: ⚠️ Potential issue | 🔴 Critical

Fix framer-motion version: 12.30.0 does not exist on npm.
The latest 12.x release is 12.23.24. Update to an existing version (e.g., ^12.23.24 or ^11.x).

Additionally, note that react-markdown@8.0.7 is ESM-only—if migrating from v7, replace deprecated plugins prop with remarkPlugins and verify plugin compatibility (remark-gfm, remark-footnotes).

@next/mdx@14.1.3 is compatible with Next.js 14.1.3 (no breaking changes in this release).

src/app/projects/page.jsx (1)

56-62: ⚠️ Potential issue | 🟡 Minor

Bug: project.title should be project.name.

The projects data model uses name not title (see src/helper/projects.js). This will render "undefined image" in the alt text.

🐛 Proposed fix
              <Image
                src={project.logo}
-               alt={`${project.title} image`}
+               alt={`${project.name} image`}
                width={80}
                height={80}
                style={{ margin: '0 auto 16px', objectFit: 'contain' }}
              />
🤖 Fix all issues with AI agents
In `@src/app/api/stats/route.js`:
- Around line 21-58: The single fetch of repos (reposRes from the fetch call)
only gets up to 100 items; update the logic that builds repos to paginate and
accumulate all pages before computing repoCountsByYear and topRepos: repeatedly
fetch using a page query param (or follow the Link header) while preserving
headers and next.revalidate, append each page's JSON to the repos array, and
stop when the response page is empty or no "next" link; then run the existing
grouping (repoCountsByYear) and topRepos sort on the full accumulated repos
list.

In `@src/app/apply/page.jsx`:
- Around line 21-52: The TimelineElement usage leaves its internal <time> block
empty, creating unwanted vertical spacing; update the TimelineElement component
to accept an optional prop (e.g., step or hideTime) and either render a step
label (pass a short step string from each TimelineElement call such as "Step 1",
"Step 2", or "Start") or make the <time> conditional (render only when step is
provided or hideTime is false); then update the <TimelineElement ... />
instances in this file to pass the new prop (e.g., step="1" or hideTime={true})
so the empty time row is removed or replaced with a concise label.

In `@src/app/ideas/`[year]/page.jsx:
- Around line 26-32: The not-found branch in the page component is using
"!articles" which never triggers because getIdeas(year) returns an empty array
instead of null; update the check in the page component (where the variable
articles is used) to detect an empty array as well (for example check articles
== null || articles.length === 0 or use articles?.length === 0) so the "Ideas
not found for {year}" UI renders when getIdeas(year) returns [] or null.

In `@src/app/ideas/2023/agora-blockchain/page.mdx`:
- Around line 3-7: The description string in the exported meta object should
hyphenate "Blockchain-based" for readability; update the description property in
the meta export (export const meta) to use "Blockchain-based Agora web
application" instead of "Blockchain based Agora web application" while
preserving the rest of the text.

In `@src/app/ideas/2024/resonate/page.mdx`:
- Around line 4-6: Update the description meta in the page frontmatter by
hyphenating the compound adjective: change the value of the description field
(the 'description' entry in the frontmatter of page.mdx) from "An open source
social voice platform" to "An open-source social voice platform" so the compound
modifier is grammatically correct.

In `@src/app/ideas/2025/resoante/page.mdx`:
- Around line 1-9: The folder name contains a typo ("resoante") causing mismatch
with the project's title; rename the folder to "resonate" and update any
imports/paths referencing it so routes and links resolve correctly; specifically
check the page component that exports meta and default (the file using
IdeaLayout, the exported meta object, and the default export) to ensure its
URL/path now matches the corrected folder name and that any routing or link
generation that references this folder is updated accordingly.

In `@src/app/ideas/page.jsx`:
- Around line 33-36: Remove the unused prop by deleting validIdeasData from the
JSX call to IdeasHeader and from the IdeasHeader component signature/props list;
update the IdeasHeader component (function or const declaration named
IdeasHeader) to no longer accept or reference validIdeasData and ensure any
propTypes/TypeScript types or defaultProps related to validIdeasData are removed
or adjusted accordingly, leaving IdeasDisplay to continue receiving
validIdeasData as before.

In `@src/app/layout.jsx`:
- Around line 50-61: In src/app/layout.jsx update the two <link> elements that
build hrefs from process.env.NEXT_PUBLIC_SITE_URL so they don't output
"undefined/...": compute a guarded siteUrl (e.g. const siteUrl =
process.env.NEXT_PUBLIC_SITE_URL || '') or choose a sensible default like
'http://localhost:3000', then use that siteUrl when constructing the feed hrefs
for the RSS and JSON links (or conditionally render those <link> tags only when
siteUrl is truthy); refer to the link elements in layout.jsx that currently use
`${process.env.NEXT_PUBLIC_SITE_URL}/rss/...` to locate where to apply the
change.

In `@src/app/page.jsx`:
- Around line 89-128: Several external Link elements in page.jsx (the Link
components wrapping FontAwesomeIcon instances for faEnvelope, faGitlab,
faGithub, faDiscord, faTwitter, and other similar Link usages) open with
target="_blank" but lack rel="noopener noreferrer"; update each Link that uses
target="_blank" to include rel="noopener noreferrer" to prevent
reverse-tabnabbing and follow security best practices. Locate each Link
component with target="_blank" (e.g., the ones rendering FontAwesomeIcon icons)
and add the rel attribute value exactly as "noopener noreferrer" to every such
instance.

In `@src/components/about/Timeline.jsx`:
- Around line 20-39: TimelineElement instances in Timeline.jsx are rendering
anchors with href={undefined} because the props button, link, and classCondition
are not supplied; either pass appropriate values for button (anchor text), link
(URL), and classCondition (CSS class) to each TimelineElement call (e.g., set
link to a valid URL or null, button to visible text or null, and classCondition
to desired class), or modify the TimelineElement component itself to
conditionally render the <a> only when link and button are defined (guarding on
link/button inside the TimelineElement render function) so anchors are not
created with undefined href/text.

In `@src/components/home/CardEffect.jsx`:
- Around line 8-13: Replace the non-interactive motion.a used for the 3D flip
card with motion.div and remove the misleading "cursor-pointer" class so the
element is correctly non-interactive/accessibility-friendly; locate the JSX
containing motion.a (in the CardEffect component) and change it to motion.div,
ensure there is no href or onClick on that element, and update the className to
remove "cursor-pointer" (retain other classes like [perspective:1000px]).

In `@src/components/home/CardHome.jsx`:
- Line 16: The Link currently uses a hardcoded href="#" with target="_blank"
which breaks UX; update the CardHome component to accept an href prop (e.g.,
prop name href) and use that instead of "#" in the Link element, and
conditionally set target and rel only when the href is external (e.g.,
startsWith("http") or different origin) so internal links use client-side
navigation without target="_blank"; ensure a sensible default (e.g., undefined
or a prop default) and update any propTypes/TypeScript types for CardHome
accordingly.

In `@src/components/home/Stats.jsx`:
- Around line 34-47: The effect in Stats.jsx starts a requestAnimationFrame loop
(step) and never cancels it, which can cause setCount to run after unmount; fix
by capturing the rAF id returned by window.requestAnimationFrame, use
cancelAnimationFrame(id) in a cleanup function returned from the useEffect, and
also cancel any pending frame when isVisible becomes false or dependencies
change so the step callback (which calls setCount) is never invoked after
unmount; update the useEffect that defines step/requestAnimationFrame to store
the id and return () => cancelAnimationFrame(id).

In `@src/components/ideas/IdeaCard.jsx`:
- Around line 38-58: The inline sx color overrides are preventing Tailwind
dark-mode classes from applying: remove the color properties from the sx props
on the two Typography components (the h5 title Typography that renders
{article.title} and the body1 Typography that renders the excerpt) so that
className-driven tailwind colors (text-green-600 dark:text-yellow-400 and
text-zinc-600 dark:text-zinc-400) can take effect; keep other sx settings like
fontFamily, mt, overflow and WebkitBoxOrient/WebkitLineClamp intact.

In `@src/components/ideas/IdeasHeader.jsx`:
- Around line 16-19: In IdeasHeader.jsx replace the invalid alt attribute on the
decorative div (the element with className starting "hidden md:block w-[75px]
h-[75px] ...") with an appropriate accessibility attribute: remove alt and add
aria-label="GSOC Logo" (and add role="img" if you want it announced as an image)
so screen readers receive the same description as the <img> used elsewhere;
update the same div element where className and background image are defined.

In `@src/components/products/FeedbackForm.jsx`:
- Around line 41-58: The star buttons in FeedbackForm.jsx lack accessible names
and state; update the mapped button elements (the ones using setHoverRating,
setRating, hoverRating, rating and rendering FontAwesomeIcon) to include an
accessible label and state by adding an aria-label (e.g. aria-label={`Rate
${star} star${star>1 ? 's' : ''}`}) and an aria-pressed or aria-checked
attribute reflecting whether rating === star (aria-pressed={rating === star} or
set role="radio" + aria-checked={rating === star}), and wrap the set of buttons
in a container with an accessible name (e.g. role="radiogroup" and
aria-label="Product rating") so assistive tech can announce the group and
current selection.

In `@src/components/shared/Banner.jsx`:
- Around line 56-59: The Apply button in Banner.jsx currently removes the
browser focus outline via the class focus:outline-none without adding a visible
replacement; update the class list on the motion.a element (the "Apply to GSoC
with AOSSIE" button rendered inside Link/motion.a) to include the same focus
ring utilities used elsewhere (e.g., add appropriate focus:ring
focus:ring-offset and focus:ring-color classes matching
FeedbackForm/TimelineElement) so keyboard focus is visible while keeping the
current rounded/px/py styles.

In `@src/components/shared/Card.jsx`:
- Around line 19-31: The Card component currently calls motion(Component) inside
the render causing a new MotionComponent each render; move the motion(Component)
creation out of render by creating a stable reference—either define a
module-scoped map/factory for motion-wrapped elements or use React.useMemo
inside Card to memoize MotionComponent based on the incoming as/Component prop;
update the Card function (referencing Card, MotionComponent, and
motion(Component)) so MotionComponent is stable across renders to prevent
remounts and preserve animation state.

In `@src/helper/products.js`:
- Around line 24-70: The products.js entries for items like slug
'agora-vote-android' and 'djed' currently contain placeholder fields (videoUrl
set to the Rickroll URL and non-real feedbacks) which should not ship; update
each product object (look for the videoUrl and feedbacks properties inside the
product array in src/helper/products.js) to either replace with real
media/testimonials or set videoUrl to null and feedbacks to an empty array (or
remove placeholder objects) and ensure any UI that consumes videoUrl and
feedbacks (e.g., product detail rendering) gracefully handles null/empty values.

In `@src/lib/ideas.js`:
- Around line 35-62: The extractMeta function currently uses new Function to
evaluate the extracted object literal (objectStr via getMeta); replace that
dynamic evaluation with a safe JSON-parse approach: sanitize objectStr (remove
JS comments, strip trailing commas, convert single-quoted strings to double
quotes, and quote unquoted keys) and then call JSON.parse on the sanitized
string to produce and return the metadata object. Update extractMeta to perform
the sanitization on the substring produced from match[1] (objectStr) and remove
the getMeta/new Function usage, returning the parsed object instead; keep
existing brace-matching logic and fallback return {} unchanged.
🧹 Nitpick comments (9)
src/components/ideas/IdeasHeader.jsx (2)

7-7: Unused validIdeasData prop.

The validIdeasData parameter is declared but never used in the component. Remove it from the destructured props to avoid confusion.

Proposed fix
-export function IdeasHeader({ validIdeasData, children }) {
+export function IdeasHeader({ children }) {

71-76: Consider using a regular anchor tag for mailto links.

Next.js Link component is optimized for client-side navigation between pages. For external protocols like mailto:, a standard <a> tag is more semantically appropriate and avoids unnecessary client-side routing overhead.

Proposed fix
-                 <Link
+                 <a
                    href="mailto:aossie.oss@gmail.com"
                    className="inline-block rounded-md border border-transparent bg-[`#00843D`] px-8 py-3 text-base font-medium text-white hover:bg-[`#006e32`] md:py-4 md:px-10 md:text-lg font-mono dark:bg-yellow-400 dark:text-black dark:hover:bg-yellow-500 transition-colors"
                  >
                    Submit Your Idea
-                 </Link>
+                 </a>
src/app/ideas/2024/resonate-ios/page.mdx (1)

5-5: Optional: Consider hyphenating "open-source" as a compound adjective.

When "open source" modifies a noun (like "platform"), it's conventionally hyphenated as "open-source". This is a minor style preference.

src/lib/ideas.js (1)

14-25: Avoid sync I/O in the ideas loader.

fs.readFileSync blocks the event loop while resolving the list; async reads keep the loader non‑blocking.

♻️ Suggested change
-        const source = fs.readFileSync(filePath, 'utf8')
+        const source = await fs.promises.readFile(filePath, 'utf8')
src/app/projects/page.jsx (1)

35-41: Consider using a stable key instead of array index.

While using index as a key works for static lists, using project.name or project.link.label would be more resilient if the list order changes in the future.

♻️ Suggested change
-        <Grid item xs={12} sm={6} md={4} key={index} component={motion.div} 
+        <Grid item xs={12} sm={6} md={4} key={project.name} component={motion.div} 
src/app/ideas/2023/eduAid/page.mdx (1)

3-7: Tighten the meta description wording.
Consider replacing “on the basis of” with “based on” to reduce wordiness.

✏️ Suggested copy tweak
-    'A tool that can auto-generate short quizzes on the basis of the content provided.',
+    'A tool that can auto-generate short quizzes based on the content provided.',
src/app/ideas/[year]/page.jsx (1)

12-20: Consider deriving supported years dynamically.

The years are hardcoded in generateStaticParams, which requires manual updates when a new year is added. The parent src/app/ideas/page.jsx already reads year directories dynamically from the filesystem. Consider extracting this logic into a shared utility to keep both files in sync.

src/app/ideas/page.jsx (1)

4-4: Remove unused import.

The Link import from next/link is not used in this file.

🧹 Proposed fix
 import { getIdeas } from '@/lib/ideas'
 import { IdeasDisplay } from '@/components/ideas/IdeasDisplay'
 import { IdeasHeader } from '@/components/ideas/IdeasHeader'
-import Link from 'next/link'
 import path from 'path'
 import fs from 'fs'
src/components/ideas/IdeasDisplay.jsx (1)

35-44: Consider adding visible focus styles for accessibility.

The focus:outline-none class removes the default focus indicator, which can make keyboard navigation difficult for users who rely on it. Consider adding a visible focus state such as focus:ring-2 focus:ring-offset-2 focus:ring-zinc-500 or similar.

♿ Proposed fix
             className="group order-2 mx-auto items-center overflow-hidden rounded-lg bg-zinc-800 px-8 py-3 text-white focus:outline-none dark:bg-white dark:text-black font-mono font-semibold transition-transform"
+            className="group order-2 mx-auto items-center overflow-hidden rounded-lg bg-zinc-800 px-8 py-3 text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-zinc-500 dark:bg-white dark:text-black dark:focus:ring-yellow-400 font-mono font-semibold transition-transform"

Comment on lines +21 to +58
// 1. Fetch Repositories
const reposRes = await fetch(`https://api.github.com/orgs/${org}/repos?per_page=100&type=public`, { headers, next: { revalidate: 3600 } });

if (!reposRes.ok) {
throw new Error(`GitHub API Error: ${reposRes.statusText}`);
}

const repos = await reposRes.json();

// 2. Process for Graph (Repos per year)
// The user wants "Number of repos in (green) over the past years".
// We will group by 'created_at'.
const repoCountsByYear = {};
const yearsSet = new Set();

// Initialize years from startYear to currentYear to ensure continuous axis
for (let y = startYear; y <= currentYear; y++) {
repoCountsByYear[y] = 0;
yearsSet.add(y);
}

repos.forEach(repo => {
const year = new Date(repo.created_at).getFullYear();
if (year >= startYear) {
repoCountsByYear[year] = (repoCountsByYear[year] || 0) + 1;
}
});

const labels = Object.keys(repoCountsByYear).sort((a, b) => parseInt(a) - parseInt(b));
const data = labels.map(year => repoCountsByYear[year]);


// 3. Count Contributors
// Fetching contributors for ALL repos consumes too much rate limit (N requests).
// We will proactively fetch contributors for the top 6 most starred/popular repos to get a good estimate.
// Sorting repos by stargazers_count
const topRepos = repos.sort((a, b) => b.stargazers_count - a.stargazers_count).slice(0, 6);

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

🌐 Web query:

GitHub REST API pagination /orgs/{org}/repos endpoint per_page parameter maximum behavior

💡 Result:

For GET /orgs/{org}/repos, per_page is supported with:

  • Default: 30 items per page. [1]
  • Maximum: 100 items per page. [2]

If you pass per_page > 100 (e.g., 1000), GitHub will not return more than 100 results in that response; it effectively caps the page size at 100, so you must use pagination (page= and/or follow the Link response header) to retrieve additional repositories. [1][2]

Sources:
[1] GitHub Docs – Using pagination in the REST API (link headers, per-page paging) (docs.github.com)
[2] GitHub REST docs (per_page “max 100” as used across org list endpoints / generated REST route docs for list-for-org) (docs.github.com)

Citations:


🏁 Script executed:

# Check the full context of the file to see if there's any pagination logic elsewhere
head -100 src/app/api/stats/route.js

Repository: AOSSIE-Org/website

Length of output: 3350


Paginate GitHub repo fetch to handle organizations with more than 100 repositories.

The single fetch call with per_page=100 only retrieves the first page of results. Organizations with more than 100 public repositories will have their data truncated, causing incomplete repo counts in the graph, incorrect topRepos selection, and inaccurate contributor counts. Use pagination via the page parameter or follow the Link response header to retrieve all repositories.

🔁 Pagination-safe approach
-    const reposRes = await fetch(`https://api.github.com/orgs/${org}/repos?per_page=100&type=public`, { headers, next: { revalidate: 3600 } });
-    
-    if (!reposRes.ok) {
-      throw new Error(`GitHub API Error: ${reposRes.statusText}`);
-    }
-
-    const repos = await reposRes.json();
+    const repos = [];
+    for (let page = 1; ; page++) {
+      const reposRes = await fetch(
+        `https://api.github.com/orgs/${org}/repos?per_page=100&type=public&page=${page}`,
+        { headers, next: { revalidate: 3600 } }
+      );
+      if (!reposRes.ok) {
+        throw new Error(`GitHub API Error: ${reposRes.statusText}`);
+      }
+      const batch = await reposRes.json();
+      repos.push(...batch);
+      if (batch.length < 100) break;
+    }
📝 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
// 1. Fetch Repositories
const reposRes = await fetch(`https://api.github.com/orgs/${org}/repos?per_page=100&type=public`, { headers, next: { revalidate: 3600 } });
if (!reposRes.ok) {
throw new Error(`GitHub API Error: ${reposRes.statusText}`);
}
const repos = await reposRes.json();
// 2. Process for Graph (Repos per year)
// The user wants "Number of repos in (green) over the past years".
// We will group by 'created_at'.
const repoCountsByYear = {};
const yearsSet = new Set();
// Initialize years from startYear to currentYear to ensure continuous axis
for (let y = startYear; y <= currentYear; y++) {
repoCountsByYear[y] = 0;
yearsSet.add(y);
}
repos.forEach(repo => {
const year = new Date(repo.created_at).getFullYear();
if (year >= startYear) {
repoCountsByYear[year] = (repoCountsByYear[year] || 0) + 1;
}
});
const labels = Object.keys(repoCountsByYear).sort((a, b) => parseInt(a) - parseInt(b));
const data = labels.map(year => repoCountsByYear[year]);
// 3. Count Contributors
// Fetching contributors for ALL repos consumes too much rate limit (N requests).
// We will proactively fetch contributors for the top 6 most starred/popular repos to get a good estimate.
// Sorting repos by stargazers_count
const topRepos = repos.sort((a, b) => b.stargazers_count - a.stargazers_count).slice(0, 6);
// 1. Fetch Repositories
const repos = [];
for (let page = 1; ; page++) {
const reposRes = await fetch(
`https://api.github.com/orgs/${org}/repos?per_page=100&type=public&page=${page}`,
{ headers, next: { revalidate: 3600 } }
);
if (!reposRes.ok) {
throw new Error(`GitHub API Error: ${reposRes.statusText}`);
}
const batch = await reposRes.json();
repos.push(...batch);
if (batch.length < 100) break;
}
// 2. Process for Graph (Repos per year)
// The user wants "Number of repos in (green) over the past years".
// We will group by 'created_at'.
const repoCountsByYear = {};
const yearsSet = new Set();
// Initialize years from startYear to currentYear to ensure continuous axis
for (let y = startYear; y <= currentYear; y++) {
repoCountsByYear[y] = 0;
yearsSet.add(y);
}
repos.forEach(repo => {
const year = new Date(repo.created_at).getFullYear();
if (year >= startYear) {
repoCountsByYear[year] = (repoCountsByYear[year] || 0) + 1;
}
});
const labels = Object.keys(repoCountsByYear).sort((a, b) => parseInt(a) - parseInt(b));
const data = labels.map(year => repoCountsByYear[year]);
// 3. Count Contributors
// Fetching contributors for ALL repos consumes too much rate limit (N requests).
// We will proactively fetch contributors for the top 6 most starred/popular repos to get a good estimate.
// Sorting repos by stargazers_count
const topRepos = repos.sort((a, b) => b.stargazers_count - a.stargazers_count).slice(0, 6);
🤖 Prompt for AI Agents
In `@src/app/api/stats/route.js` around lines 21 - 58, The single fetch of repos
(reposRes from the fetch call) only gets up to 100 items; update the logic that
builds repos to paginate and accumulate all pages before computing
repoCountsByYear and topRepos: repeatedly fetch using a page query param (or
follow the Link header) while preserving headers and next.revalidate, append
each page's JSON to the repos array, and stop when the response page is empty or
no "next" link; then run the existing grouping (repoCountsByYear) and topRepos
sort on the full accumulated repos list.

Comment on lines +21 to +52
<ol className="relative border-l-2 border-gray-200 dark:border-gray-700">
<TimelineElement
title="Join us on Discord"
description="Join the AOSSIE community on Discord and connect with other developers, mentors, and organizers. Our Discord server is a great place to ask questions, share ideas, and get support throughout the Google Summer of Code application process. From proposal writing tips to coding advice, our community is here to help you succeed. Don't go through the process alone, join us on Discord now!"
button="Join Discord"
link="https://discord.gg/hjUhu33uAn"
/>
<TimelineElement
title="Start Contributing"
description="Contribute to the project and make your mark on open-source development with AOSSIE. By making a Pull Request (PR) to one of our existing projects, you'll have the opportunity to showcase your skills and demonstrate your understanding of the project. This will also give you an opportunity to work with the mentors and get familiar with the project before the official GSoC coding period starts. This is a great way to get started and increase your chances of being selected for the program."
button="Contribute"
link="https://gitlab.com/aossie"
/>
<TimelineElement
title="Write a Draft Application"
description="Select an Idea and write a draft application that expands this idea with your own proposals and showcases how you will execute and complete your project. This is your chance to demonstrate your understanding of the project, your skills, and your passion for open-source development. Our mentors will provide feedback and help you refine your proposal, increasing your chances of being selected for the program."
button="Choose an Idea"
link="/ideas"
/>
<TimelineElement
title="Discuss with Mentors"
description="Share your draft application with our mentors and get feedback on your proposal. Our mentors are experienced developers who have been through the GSoC process before and can provide valuable insights to help you improve your application. Discussing your proposal with mentors also demonstrates your commitment to the project and your willingness to learn and improve."
button="Mentors List"
link="https://github.com/orgs/AOSSIE-Org/people"
/>
<TimelineElement
title="Submit Application"
description="Submit your final application to Google Summer of Code before the deadline. Make sure to double-check your application and ensure that you have included all the necessary information. Good luck!"
button="Apply Now"
link="https://summerofcode.withgoogle.com/"
/>
</ol>
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

Provide a step label or hide the empty time row.

TimelineElement renders a <time> block; omitting time leaves an empty row/spacing for each step. Consider passing a step label or making the time block conditional.

✏️ Example fix: add step labels
               <TimelineElement
                 title="Join us on Discord"
+                time="Step 1"
                 description="Join the AOSSIE community on Discord and connect with other developers, mentors, and organizers. Our Discord server is a great place to ask questions, share ideas, and get support throughout the Google Summer of Code application process. From proposal writing tips to coding advice, our community is here to help you succeed. Don't go through the process alone, join us on Discord now!"
                 button="Join Discord"
                 link="https://discord.gg/hjUhu33uAn"
               />
               <TimelineElement
                 title="Start Contributing"
+                time="Step 2"
                 description="Contribute to the project and make your mark on open-source development with AOSSIE. By making a Pull Request (PR) to one of our existing projects, you'll have the opportunity to showcase your skills and demonstrate your understanding of the project. This will also give you an opportunity to work with the mentors and get familiar with the project before the official GSoC coding period starts. This is a great way to get started and increase your chances of being selected for the program."
                 button="Contribute"
                 link="https://gitlab.com/aossie"
               />
               <TimelineElement
                 title="Write a Draft Application"
+                time="Step 3"
                 description="Select an Idea and write a draft application that expands this idea with your own proposals and showcases how you will execute and complete your project. This is your chance to demonstrate your understanding of the project, your skills, and your passion for open-source development. Our mentors will provide feedback and help you refine your proposal, increasing your chances of being selected for the program."
                 button="Choose an Idea"
                 link="/ideas"
               />
               <TimelineElement
                 title="Discuss with Mentors"
+                time="Step 4"
                 description="Share your draft application with our mentors and get feedback on your proposal. Our mentors are experienced developers who have been through the GSoC process before and can provide valuable insights to help you improve your application. Discussing your proposal with mentors also demonstrates your commitment to the project and your willingness to learn and improve."
                 button="Mentors List"
                 link="https://github.com/orgs/AOSSIE-Org/people"
               />
               <TimelineElement
                 title="Submit Application"
+                time="Step 5"
                 description="Submit your final application to Google Summer of Code before the deadline. Make sure to double-check your application and ensure that you have included all the necessary information. Good luck!"
                 button="Apply Now"
                 link="https://summerofcode.withgoogle.com/"
               />
📝 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
<ol className="relative border-l-2 border-gray-200 dark:border-gray-700">
<TimelineElement
title="Join us on Discord"
description="Join the AOSSIE community on Discord and connect with other developers, mentors, and organizers. Our Discord server is a great place to ask questions, share ideas, and get support throughout the Google Summer of Code application process. From proposal writing tips to coding advice, our community is here to help you succeed. Don't go through the process alone, join us on Discord now!"
button="Join Discord"
link="https://discord.gg/hjUhu33uAn"
/>
<TimelineElement
title="Start Contributing"
description="Contribute to the project and make your mark on open-source development with AOSSIE. By making a Pull Request (PR) to one of our existing projects, you'll have the opportunity to showcase your skills and demonstrate your understanding of the project. This will also give you an opportunity to work with the mentors and get familiar with the project before the official GSoC coding period starts. This is a great way to get started and increase your chances of being selected for the program."
button="Contribute"
link="https://gitlab.com/aossie"
/>
<TimelineElement
title="Write a Draft Application"
description="Select an Idea and write a draft application that expands this idea with your own proposals and showcases how you will execute and complete your project. This is your chance to demonstrate your understanding of the project, your skills, and your passion for open-source development. Our mentors will provide feedback and help you refine your proposal, increasing your chances of being selected for the program."
button="Choose an Idea"
link="/ideas"
/>
<TimelineElement
title="Discuss with Mentors"
description="Share your draft application with our mentors and get feedback on your proposal. Our mentors are experienced developers who have been through the GSoC process before and can provide valuable insights to help you improve your application. Discussing your proposal with mentors also demonstrates your commitment to the project and your willingness to learn and improve."
button="Mentors List"
link="https://github.com/orgs/AOSSIE-Org/people"
/>
<TimelineElement
title="Submit Application"
description="Submit your final application to Google Summer of Code before the deadline. Make sure to double-check your application and ensure that you have included all the necessary information. Good luck!"
button="Apply Now"
link="https://summerofcode.withgoogle.com/"
/>
</ol>
<ol className="relative border-l-2 border-gray-200 dark:border-gray-700">
<TimelineElement
title="Join us on Discord"
time="Step 1"
description="Join the AOSSIE community on Discord and connect with other developers, mentors, and organizers. Our Discord server is a great place to ask questions, share ideas, and get support throughout the Google Summer of Code application process. From proposal writing tips to coding advice, our community is here to help you succeed. Don't go through the process alone, join us on Discord now!"
button="Join Discord"
link="https://discord.gg/hjUhu33uAn"
/>
<TimelineElement
title="Start Contributing"
time="Step 2"
description="Contribute to the project and make your mark on open-source development with AOSSIE. By making a Pull Request (PR) to one of our existing projects, you'll have the opportunity to showcase your skills and demonstrate your understanding of the project. This will also give you an opportunity to work with the mentors and get familiar with the project before the official GSoC coding period starts. This is a great way to get started and increase your chances of being selected for the program."
button="Contribute"
link="https://gitlab.com/aossie"
/>
<TimelineElement
title="Write a Draft Application"
time="Step 3"
description="Select an Idea and write a draft application that expands this idea with your own proposals and showcases how you will execute and complete your project. This is your chance to demonstrate your understanding of the project, your skills, and your passion for open-source development. Our mentors will provide feedback and help you refine your proposal, increasing your chances of being selected for the program."
button="Choose an Idea"
link="/ideas"
/>
<TimelineElement
title="Discuss with Mentors"
time="Step 4"
description="Share your draft application with our mentors and get feedback on your proposal. Our mentors are experienced developers who have been through the GSoC process before and can provide valuable insights to help you improve your application. Discussing your proposal with mentors also demonstrates your commitment to the project and your willingness to learn and improve."
button="Mentors List"
link="https://github.com/orgs/AOSSIE-Org/people"
/>
<TimelineElement
title="Submit Application"
time="Step 5"
description="Submit your final application to Google Summer of Code before the deadline. Make sure to double-check your application and ensure that you have included all the necessary information. Good luck!"
button="Apply Now"
link="https://summerofcode.withgoogle.com/"
/>
</ol>
🤖 Prompt for AI Agents
In `@src/app/apply/page.jsx` around lines 21 - 52, The TimelineElement usage
leaves its internal <time> block empty, creating unwanted vertical spacing;
update the TimelineElement component to accept an optional prop (e.g., step or
hideTime) and either render a step label (pass a short step string from each
TimelineElement call such as "Step 1", "Step 2", or "Start") or make the <time>
conditional (render only when step is provided or hideTime is false); then
update the <TimelineElement ... /> instances in this file to pass the new prop
(e.g., step="1" or hideTime={true}) so the empty time row is removed or replaced
with a concise label.

Comment on lines +26 to +32
if (!articles) {
return (
<Container className="mt-32">
<h1 className="text-center text-4xl font-bold">Ideas not found for {year}</h1>
</Container>
)
}
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

The not-found check will never trigger.

The getIdeas(year) function returns an empty array [] when the directory doesn't exist (see src/lib/ideas.js lines 7-8). Since an empty array is truthy, !articles will always be false, and this not-found UI will never render.

🐛 Proposed fix
-  if (!articles) {
+  if (!articles || articles.length === 0) {
       return (
           <Container className="mt-32">
               <h1 className="text-center text-4xl font-bold">Ideas not found for {year}</h1>
           </Container>
       )
   }
📝 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
if (!articles) {
return (
<Container className="mt-32">
<h1 className="text-center text-4xl font-bold">Ideas not found for {year}</h1>
</Container>
)
}
if (!articles || articles.length === 0) {
return (
<Container className="mt-32">
<h1 className="text-center text-4xl font-bold">Ideas not found for {year}</h1>
</Container>
)
}
🤖 Prompt for AI Agents
In `@src/app/ideas/`[year]/page.jsx around lines 26 - 32, The not-found branch in
the page component is using "!articles" which never triggers because
getIdeas(year) returns an empty array instead of null; update the check in the
page component (where the variable articles is used) to detect an empty array as
well (for example check articles == null || articles.length === 0 or use
articles?.length === 0) so the "Ideas not found for {year}" UI renders when
getIdeas(year) returns [] or null.

Comment on lines 3 to 7
export const meta = {
title: 'Agora Blockchain',
description:
'First version of Blockchain based Agora web application, with basic features like user registration, election creation, voting and result calculation.',
}
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

Hyphenate “Blockchain‑based” in the description.

Minor copy fix for readability.

✏️ Proposed copy fix
-    'First version of Blockchain based Agora web application, with basic features like user registration, election creation, voting and result calculation.',
+    'First version of Blockchain-based Agora web application, with basic features like user registration, election creation, voting and result calculation.',
📝 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
export const meta = {
title: 'Agora Blockchain',
description:
'First version of Blockchain based Agora web application, with basic features like user registration, election creation, voting and result calculation.',
}
export const meta = {
title: 'Agora Blockchain',
description:
'First version of Blockchain-based Agora web application, with basic features like user registration, election creation, voting and result calculation.',
}
🧰 Tools
🪛 LanguageTool

[grammar] ~6-~6: Use a hyphen to join words.
Context: ...iption: 'First version of Blockchain based Agora web application, with basic ...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
In `@src/app/ideas/2023/agora-blockchain/page.mdx` around lines 3 - 7, The
description string in the exported meta object should hyphenate
"Blockchain-based" for readability; update the description property in the meta
export (export const meta) to use "Blockchain-based Agora web application"
instead of "Blockchain based Agora web application" while preserving the rest of
the text.

Comment on lines 4 to 6
title: 'Resonate App',
description: 'An open source social voice platform',
}
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

Hyphenate “open‑source” in the meta description.

Small compound‑adjective fix.

✏️ Suggested edit
-  description: 'An open source social voice platform',
+  description: 'An open-source social voice platform',
📝 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
title: 'Resonate App',
description: 'An open source social voice platform',
}
title: 'Resonate App',
description: 'An open-source social voice platform',
}
🧰 Tools
🪛 LanguageTool

[uncategorized] ~5-~5: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...tle: 'Resonate App', description: 'An open source social voice platform', } export defau...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

🤖 Prompt for AI Agents
In `@src/app/ideas/2024/resonate/page.mdx` around lines 4 - 6, Update the
description meta in the page frontmatter by hyphenating the compound adjective:
change the value of the description field (the 'description' entry in the
frontmatter of page.mdx) from "An open source social voice platform" to "An
open-source social voice platform" so the compound modifier is grammatically
correct.

Comment on lines +41 to +58
{[1, 2, 3, 4, 5].map((star) => (
<button
type="button"
key={star}
className="focus:outline-none transition-transform hover:scale-110"
onMouseEnter={() => setHoverRating(star)}
onMouseLeave={() => setHoverRating(0)}
onClick={() => setRating(star)}
>
<FontAwesomeIcon
icon={star <= (hoverRating || rating) ? faStar : faStarRegular}
className={`w-6 h-6 ${
star <= (hoverRating || rating)
? 'text-yellow-400'
: 'text-zinc-300 dark:text-zinc-600'
}`}
/>
</button>
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

Add accessible labels/state to the star rating buttons.
The buttons currently have no accessible name or state, so assistive tech can’t interpret the selected rating.

Suggested fix
-            <div className="flex gap-1">
+            <div className="flex gap-1" aria-label="Rating">
               {[1, 2, 3, 4, 5].map((star) => (
                 <button
                   type="button"
                   key={star}
                   className="focus:outline-none transition-transform hover:scale-110"
+                  aria-label={`${star} star${star > 1 ? 's' : ''}`}
+                  aria-pressed={star === rating}
                   onMouseEnter={() => setHoverRating(star)}
                   onMouseLeave={() => setHoverRating(0)}
                   onClick={() => setRating(star)}
                 >
🤖 Prompt for AI Agents
In `@src/components/products/FeedbackForm.jsx` around lines 41 - 58, The star
buttons in FeedbackForm.jsx lack accessible names and state; update the mapped
button elements (the ones using setHoverRating, setRating, hoverRating, rating
and rendering FontAwesomeIcon) to include an accessible label and state by
adding an aria-label (e.g. aria-label={`Rate ${star} star${star>1 ? 's' : ''}`})
and an aria-pressed or aria-checked attribute reflecting whether rating === star
(aria-pressed={rating === star} or set role="radio" + aria-checked={rating ===
star}), and wrap the set of buttons in a container with an accessible name (e.g.
role="radiogroup" and aria-label="Product rating") so assistive tech can
announce the group and current selection.

Comment on lines +56 to +59
<Link href="/apply" legacyBehavior>
<motion.a whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} className="group relative rounded-lg inline-flex items-center overflow-hidden bg-white dark:bg-black px-8 py-3 text-black dark:text-white focus:outline-none font-mono font-semibold">
Apply to GSoC with AOSSIE
</motion.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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/components/shared/Banner.jsx | sed -n '50,65p'

Repository: AOSSIE-Org/website

Length of output: 1270


🏁 Script executed:

# Search for focus-related styling in CSS/styled files
rg "focus-visible|focus:" --type css --type scss --type ts --type tsx -A 2 | head -100

Repository: AOSSIE-Org/website

Length of output: 90


🏁 Script executed:

# Search for focus-related styling across the codebase
rg "focus-visible|focus\:" --type css --type ts --type tsx -B 1 -A 1 | head -150

Repository: AOSSIE-Org/website

Length of output: 168


🏁 Script executed:

# Search for focus-visible styling
rg "focus-visible" --type css --type ts --type tsx -B 1 -A 1

Repository: AOSSIE-Org/website

Length of output: 89


🏁 Script executed:

# Search for focus-visible in codebase
rg "focus-visible" -B 1 -A 1 | head -100

Repository: AOSSIE-Org/website

Length of output: 1725


🏁 Script executed:

# Search for other uses of focus in Button or Link components
rg "className.*focus" src/components/ -B 1 -A 1 | head -200

Repository: AOSSIE-Org/website

Length of output: 3276


Restore a visible focus indicator on the Apply button.
Line 57 removes the default outline with focus:outline-none without providing a replacement focus style. This breaks keyboard navigation visibility. Other buttons in the codebase (FeedbackForm, TimelineElement) pair focus:outline-none with focus:ring classes—this button should follow the same pattern.

Suggested fix
-                <motion.a whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} className="group relative rounded-lg inline-flex items-center overflow-hidden bg-white dark:bg-black px-8 py-3 text-black dark:text-white focus:outline-none font-mono font-semibold">
+                <motion.a
+                  whileHover={{ scale: 1.05 }}
+                  whileTap={{ scale: 0.95 }}
+                  className="group relative rounded-lg inline-flex items-center overflow-hidden bg-white dark:bg-black px-8 py-3 text-black dark:text-white focus:outline-none focus:ring-2 focus:ring-white/80 focus:ring-offset-2 focus:ring-offset-[`#00843D`] dark:focus:ring-offset-yellow-400 font-mono font-semibold"
+                >
🤖 Prompt for AI Agents
In `@src/components/shared/Banner.jsx` around lines 56 - 59, The Apply button in
Banner.jsx currently removes the browser focus outline via the class
focus:outline-none without adding a visible replacement; update the class list
on the motion.a element (the "Apply to GSoC with AOSSIE" button rendered inside
Link/motion.a) to include the same focus ring utilities used elsewhere (e.g.,
add appropriate focus:ring focus:ring-offset and focus:ring-color classes
matching FeedbackForm/TimelineElement) so keyboard focus is visible while
keeping the current rounded/px/py styles.

Comment on lines 19 to +31
export function Card({ as: Component = 'div', className, children }) {
const MotionComponent = motion(Component)
return (
<Component
<MotionComponent
className={clsx(className, 'group relative flex flex-col items-start')}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
whileHover={{ scale: 1.02 }}
>
{children}
</Component>
</MotionComponent>
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

🌐 Web query:

Framer Motion motion() component wrapper best practices dynamic component render

💡 Result:

Best practices for motion() / motion.create() wrappers (custom components)

  1. Always forward the ref to the actual DOM/SVG element you want to animate
    Framer Motion/Motion needs that ref to know which element to drive. Use forwardRef (React 18) or accept props.ref (React 19). [1], [3]

  2. Create the motion-wrapped component once (module scope), not during render
    Don’t do const MotionX = motion(Component) / motion.create(Component) inside a component body or inside .map()—it creates a new component type every render and can break animation state. [1]

  3. If you’re building a “wrapper component” (e.g. MotionDiv) keep it thin and type it from the underlying motion element
    Centralize defaults only if you truly want consistent project-wide motion; otherwise pass props through. (This is mostly a maintainability best practice rather than a runtime requirement.) [4]

  4. If your wrapped component needs to receive motion props (animate, variants, etc.), enable forwarding
    By default, motion props are filtered out before being passed to your custom component; use the “forward motion props” option when you need them. [1]


Dynamic component rendering: patterns that won’t break animations

A) Dynamic tag (DOM element) without re-wrapping every render

Create a stable map once:

// module scope
const motionByTag = {
  div: motion.div,
  section: motion.section,
  button: motion.button,
} as const

function Box({ as = "div", ...props }) {
  const Comp = motionByTag[as]
  return <Comp {...props} />
}

B) Dynamic custom component (React component chosen at runtime)

If you truly have multiple possible components, wrap them once each:

// module scope
const MotionA = motion.create(A)
const MotionB = motion.create(B)

function Thing({ kind, ...props }) {
  const Comp = kind === "a" ? MotionA : MotionB
  return <Comp {...props} />
}

If you can’t pre-wrap (e.g. plugin system), memoize by identity, but still ensure stability so you’re not creating a new motion component every render. The core rule is the same: don’t create a new motion component type per render. [1]


When “dynamic render” involves mount/unmount (conditional UI)

For enter/exit animations, the element with exit must be a direct child of AnimatePresence (React limitation). [1]


Next.js / RSC note (common gotcha)

If you’re in Next.js and rendering in Server Components, use the client entry (motion/react-client) for components that animate on the client. [1]
(If you’re using Framer’s environment, the Motion docs say to import from framer-motion even when examples show motion/react.) [2]

Sources: [1] Motion React “Motion component” docs, [2] Motion “Framer x Motion” docs, [3] StackOverflow forwardRef + motion(Component) discussion, [4] Wrapper best-practices writeup.


🏁 Script executed:

# Let's check the actual file to understand the context better
cd /tmp/repo 2>/dev/null && cat -n src/components/shared/Card.jsx | head -40

Repository: AOSSIE-Org/website

Length of output: 44


Move motion(Component) outside render to prevent remounts on every render.
Framer Motion creates a new component type when motion(Component) is called inside render, causing unnecessary remounts and potential animation state loss. Define the motion component at module scope or use useMemo to maintain a stable reference.

♻️ Suggested fix
 import Link from 'next/link'
 import clsx from 'clsx'
 import { motion } from 'framer-motion'
+import { useMemo } from 'react'
@@
 export function Card({ as: Component = 'div', className, children }) {
-  const MotionComponent = motion(Component)
+  const MotionComponent = useMemo(() => motion(Component), [Component])
   return (
     <MotionComponent
🤖 Prompt for AI Agents
In `@src/components/shared/Card.jsx` around lines 19 - 31, The Card component
currently calls motion(Component) inside the render causing a new
MotionComponent each render; move the motion(Component) creation out of render
by creating a stable reference—either define a module-scoped map/factory for
motion-wrapped elements or use React.useMemo inside Card to memoize
MotionComponent based on the incoming as/Component prop; update the Card
function (referencing Card, MotionComponent, and motion(Component)) so
MotionComponent is stable across renders to prevent remounts and preserve
animation state.

Comment on lines +24 to +70
videoUrl: 'https://www.youtube.com/embed/dQw4w9WgXcQ', // Placeholder
feedbacks: [
{ user: 'John Doe', comment: 'Amazing tool for organizing my gallery!', rating: 5 },
{ user: 'Jane Smith', comment: 'Works well, but could use more features.', rating: 4 }
]
},
{
slug: 'agora-vote-android',
name: 'Agora Vote Android',
description: 'This application uses Agora Web API as backend application. It allows for elections to be held by using multiple algorithms.',
logo: AgoraLogo,
status: 'production',
downloadLink: 'https://gitlab.com/aossie/agora-android/-/releases',
githubLink: 'https://gitlab.com/aossie/agora-android',
discordLink: 'https://discord.com/invite/6mHZkbJ',
setupGuide: `# Setup Guide for Agora Vote Android

1. **Download APK**: Get the latest APK from the releases page.
2. **Enable Unknown Sources**: Go to Settings > Security and enable installation from unknown sources if required.
3. **Install APK**: Open the APK file and tap Install.
4. **Connect**: Launch the app and connect to an Agora server instance.
`,
videoUrl: 'https://www.youtube.com/embed/dQw4w9WgXcQ',
feedbacks: [
{ user: 'Voter123', comment: 'Secure and easy to use.', rating: 5 }
]
},
{
slug: 'djed',
name: 'Djed',
description: 'Djed is a Formally Verified Crypto-Backed Algorithmic Stablecoin Protocol.',
logo: Djed,
status: 'ongoing',
downloadLink: 'https://github.com/AOSSIE-Org/Djed-Solidity-WebDashboard',
githubLink: 'https://github.com/AOSSIE-Org/Djed-Solidity-WebDashboard',
discordLink: 'https://discord.com/invite/6mHZkbJ',
setupGuide: `# Setup Guide for Djed

Djed runs as a web dashboard. You can deploy it locally or access the hosted version.

## Local Deployment
1. Clone the repository.
2. Run \`npm install\`.
3. Run \`npm start\`.
`,
videoUrl: 'https://www.youtube.com/embed/dQw4w9WgXcQ',
feedbacks: []
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

Replace placeholder media/testimonials before release.
Several videoUrl entries and feedbacks look like placeholders; please swap with real content or set them to null and handle conditionally to avoid shipping dummy data.

🤖 Prompt for AI Agents
In `@src/helper/products.js` around lines 24 - 70, The products.js entries for
items like slug 'agora-vote-android' and 'djed' currently contain placeholder
fields (videoUrl set to the Rickroll URL and non-real feedbacks) which should
not ship; update each product object (look for the videoUrl and feedbacks
properties inside the product array in src/helper/products.js) to either replace
with real media/testimonials or set videoUrl to null and feedbacks to an empty
array (or remove placeholder objects) and ensure any UI that consumes videoUrl
and feedbacks (e.g., product detail rendering) gracefully handles null/empty
values.

Comment on lines +35 to +62
function extractMeta(source) {
try {
// Match export const meta = { ... or export const metadata = { ...
const match = source.match(/export const meta(?:data)?\s*=\s*(\{[\s\S]*)/);

if (!match) return {};

// We start from the opening brace
const str = match[1];
let braceCount = 0;
let end = 0;

// Find the closing brace that matches the first opening brace
for (let i = 0; i < str.length; i++) {
if (str[i] === '{') braceCount++;
else if (str[i] === '}') braceCount--;

if (braceCount === 0) {
end = i + 1;
break;
}
}

if (end > 0) {
const objectStr = str.substring(0, end);
// Use new Function to safely evaluate the object literal
const getMeta = new Function(`return ${objectStr}`);
return getMeta();
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

🧩 Analysis chain

🏁 Script executed:

find . -name "ideas.js" -type f

Repository: AOSSIE-Org/website

Length of output: 80


🏁 Script executed:

cat -n src/lib/ideas.js | head -100

Repository: AOSSIE-Org/website

Length of output: 2314


🏁 Script executed:

cd src/lib && grep -r "extractMeta" . -A 5 -B 5

Repository: AOSSIE-Org/website

Length of output: 868


🏁 Script executed:

find . -name "next.config.*" -o -name "vercel.json" -o -name "security.headers.*" | head -20

Repository: AOSSIE-Org/website

Length of output: 79


🏁 Script executed:

rg "Content-Security-Policy\|CSP\|csp" --type json --type js --type ts -A 3 -B 1 | head -50

Repository: AOSSIE-Org/website

Length of output: 44


🏁 Script executed:

find . -path "*/ideas/*" -name "*.mdx" | head -3

Repository: AOSSIE-Org/website

Length of output: 202


🏁 Script executed:

cat -n next.config.mjs

Repository: AOSSIE-Org/website

Length of output: 846


🏁 Script executed:

cat -n src/app/ideas/2022/agora-blockchain/page.mdx | head -50

Repository: AOSSIE-Org/website

Length of output: 2248


Simplify metadata extraction without new Function.

Using new Function to evaluate the metadata object is unnecessary since the exported metadata in these MDX files contains only plain data (strings, numbers). Instead, use a JSON-based approach or direct object literal extraction via regex to safely parse the data without dynamic code evaluation.

🤖 Prompt for AI Agents
In `@src/lib/ideas.js` around lines 35 - 62, The extractMeta function currently
uses new Function to evaluate the extracted object literal (objectStr via
getMeta); replace that dynamic evaluation with a safe JSON-parse approach:
sanitize objectStr (remove JS comments, strip trailing commas, convert
single-quoted strings to double quotes, and quote unquoted keys) and then call
JSON.parse on the sanitized string to produce and return the metadata object.
Update extractMeta to perform the sanitization on the substring produced from
match[1] (objectStr) and remove the getMeta/new Function usage, returning the
parsed object instead; keep existing brace-matching logic and fallback return {}
unchanged.

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.

1 participant