-
Notifications
You must be signed in to change notification settings - Fork 345
Revamped AOSSIE website #553
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@DeveloperAmrit is attempting to deploy a commit to the AOSSIE Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThis pull request implements a comprehensive migration from Next.js Pages Router to App Router, restructuring the entire site architecture. New pages are created in Changes
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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 | 🟡 MinorBack 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 | 🔴 CriticalFix framer-motion version: 12.30.0 does not exist on npm.
The latest12.xrelease is12.23.24. Update to an existing version (e.g.,^12.23.24or^11.x).Additionally, note that
react-markdown@8.0.7is ESM-only—if migrating from v7, replace deprecatedpluginsprop withremarkPluginsand verify plugin compatibility (remark-gfm,remark-footnotes).
@next/mdx@14.1.3is compatible with Next.js 14.1.3 (no breaking changes in this release).src/app/projects/page.jsx (1)
56-62:⚠️ Potential issue | 🟡 MinorBug:
project.titleshould beproject.name.The projects data model uses
namenottitle(seesrc/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: UnusedvalidIdeasDataprop.The
validIdeasDataparameter 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 formailtolinks.Next.js
Linkcomponent is optimized for client-side navigation between pages. For external protocols likemailto:, 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.readFileSyncblocks 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
indexas a key works for static lists, usingproject.nameorproject.link.labelwould 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 parentsrc/app/ideas/page.jsxalready 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
Linkimport fromnext/linkis 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-noneclass removes the default focus indicator, which can make keyboard navigation difficult for users who rely on it. Consider adding a visible focus state such asfocus:ring-2 focus:ring-offset-2 focus:ring-zinc-500or 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"
| // 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); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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:
30items per page. [1] - Maximum:
100items 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:
- 1: https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api?utm_source=openai
- 2: https://docs.github.com/enterprise-cloud%40latest/rest/orgs/orgs?utm_source=openai
🏁 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.jsRepository: 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.
| // 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.
| <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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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.
| if (!articles) { | ||
| return ( | ||
| <Container className="mt-32"> | ||
| <h1 className="text-center text-4xl font-bold">Ideas not found for {year}</h1> | ||
| </Container> | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| 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.', | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| title: 'Resonate App', | ||
| description: 'An open source social voice platform', | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| {[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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -100Repository: 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 -150Repository: 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 1Repository: AOSSIE-Org/website
Length of output: 89
🏁 Script executed:
# Search for focus-visible in codebase
rg "focus-visible" -B 1 -A 1 | head -100Repository: 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 -200Repository: 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.
| 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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Framer Motion motion() component wrapper best practices dynamic component render
💡 Result:
Best practices for motion() / motion.create() wrappers (custom components)
-
Always forward the
refto the actual DOM/SVG element you want to animate
Framer Motion/Motion needs that ref to know which element to drive. UseforwardRef(React 18) or acceptprops.ref(React 19). [1], [3] -
Create the motion-wrapped component once (module scope), not during render
Don’t doconst 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] -
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] -
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 -40Repository: 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.
| 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: [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -name "ideas.js" -type fRepository: AOSSIE-Org/website
Length of output: 80
🏁 Script executed:
cat -n src/lib/ideas.js | head -100Repository: AOSSIE-Org/website
Length of output: 2314
🏁 Script executed:
cd src/lib && grep -r "extractMeta" . -A 5 -B 5Repository: AOSSIE-Org/website
Length of output: 868
🏁 Script executed:
find . -name "next.config.*" -o -name "vercel.json" -o -name "security.headers.*" | head -20Repository: 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 -50Repository: AOSSIE-Org/website
Length of output: 44
🏁 Script executed:
find . -path "*/ideas/*" -name "*.mdx" | head -3Repository: AOSSIE-Org/website
Length of output: 202
🏁 Script executed:
cat -n next.config.mjsRepository: AOSSIE-Org/website
Length of output: 846
🏁 Script executed:
cat -n src/app/ideas/2022/agora-blockchain/page.mdx | head -50Repository: 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.
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
page.tsxandlayout.tsx.Preview
Hosted link - https://aossie-website.vercel.app/
Summary by CodeRabbit
Release Notes
New Features
Improvements