Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 57 additions & 42 deletions src/components/SupportUsButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import type { supportUsButtonProps } from "../types/index";
import type { Theme } from "../types/index";
import type { ButtonVariant } from "../types/index";
import type { Tier } from "../types/index";

// Function to get the appropriate classes based on the selected theme, used for styling different sections of the component according to the chosen theme
function classAccordingToTheme(Theme: Theme): string {
Expand Down Expand Up @@ -70,10 +71,24 @@ function SupportUsButton({
},
buttonVariant = "AOSSIE",
}: supportUsButtonProps): React.JSX.Element {
const sortedSponsors = sponsors ? [...sponsors].sort((a, b) => {
const tierPriority: Record<Tier, number> = {
Platinum: 1,
Gold: 2,
Silver: 3,
Bronze: 4,
};
const aTier = a.sponsorshipTier || 'Bronze';
const bTier = b.sponsorshipTier || 'Bronze';
return tierPriority[aTier] - tierPriority[bTier];
}) : [];

const resolvedTheme = Theme === "system" ? (typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light") : Theme;
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Mar 15, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

SSR hydration mismatch and missing reactive updates for system theme.

Accessing window.matchMedia during render causes two issues:

  1. Hydration mismatch: Server renders with "light" (window undefined), but client may resolve to "dark", causing React hydration errors.
  2. No reactive updates: If the user changes their system preference, the component won't re-render.
🐛 Proposed fix using useState and useEffect
+import React, { useState, useEffect } from "react";
-import React from "react";

Then replace the inline resolution with a hook pattern:

+  const [resolvedTheme, setResolvedTheme] = useState<Exclude<Theme, "system">>(() => {
+    if (Theme !== "system") return Theme;
+    if (typeof window === "undefined") return "light";
+    return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
+  });
+
+  useEffect(() => {
+    if (Theme !== "system") {
+      setResolvedTheme(Theme);
+      return;
+    }
+    const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
+    const handleChange = (e: MediaQueryListEvent) => {
+      setResolvedTheme(e.matches ? "dark" : "light");
+    };
+    setResolvedTheme(mediaQuery.matches ? "dark" : "light");
+    mediaQuery.addEventListener("change", handleChange);
+    return () => mediaQuery.removeEventListener("change", handleChange);
+  }, [Theme]);
-  const resolvedTheme = Theme === "system" ? (typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light") : Theme;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/SupportUsButton.tsx` at line 86, The inline access to
window.matchMedia causes SSR hydration mismatches and doesn't update when system
theme changes; replace the inline resolution with a state + effect inside the
SupportUsButton component: create a useState for resolvedTheme with an initial
value of (Theme === "system" ? "light" : Theme), then add a useEffect that runs
when Theme changes — if Theme !== "system" set resolvedTheme to Theme, otherwise
create a MediaQueryList with window.matchMedia("(prefers-color-scheme: dark)"),
set resolvedTheme based on m.matches, attach a change listener (use
addEventListener('change') if available or addEventListener fallback to
addListener) to update resolvedTheme on preference changes, and remove the
listener in the cleanup; reference the symbols resolvedTheme, Theme and the
SupportUsButton component when making these edits.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thank you for the review suggestions. I have updated the implementation to address the SSR hydration issue and applied the recommended improvements.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

Let me verify the latest state of the file to confirm the fix.


🏁 Script executed:

#!/bin/bash
# Fetch the latest content of SupportUsButton.tsx from the PR branch
gh pr view 14 --repo AOSSIE-Org/SupportUsButton --json headRefName,headRepositoryOwner,headRepository

Length of output: 228


🏁 Script executed:

#!/bin/bash
# Fetch the latest SupportUsButton.tsx from the PR branch
gh api repos/AOSSIE-Org/SupportUsButton/contents/src/components/SupportUsButton.tsx?ref=feat/system-theme-detection --jq '.content' | base64 -d

Length of output: 92


🏁 Script executed:

#!/bin/bash
# Check the latest commits on the PR
gh pr view 14 --repo AOSSIE-Org/SupportUsButton --json commits --jq '.commits[-3:]'

Length of output: 685


🏁 Script executed:

#!/bin/bash
# Fetch file content using the latest commit OID
gh api repos/AOSSIE-Org/SupportUsButton/contents/src/components/SupportUsButton.tsx?ref=d01aa31da4ceda426abdeca7aebd809e2eda72ef --jq '.content' | base64 -d | grep -n "resolvedTheme\|useState\|useEffect\|matchMedia\|import React"

Length of output: 3704


@madhavansingh, thanks for the response! However, after checking the latest commit (d01aa31) on the PR branch, the original inline implementation is still present at Line 86 — useState/useEffect are not yet imported or used:

// Still present in the latest commit — unfixed
const resolvedTheme = Theme === "system" ? (typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light") : Theme;

It looks like the fix may not have been pushed yet. Could you double-check and push the updated commit? The proposed fix from the review should look like this:

import React, { useState, useEffect } from "react";
const [resolvedTheme, setResolvedTheme] = useState<Exclude<Theme, "system">>(() => {
  if (Theme !== "system") return Theme;
  if (typeof window === "undefined") return "light";
  return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
});

useEffect(() => {
  if (Theme !== "system") {
    setResolvedTheme(Theme);
    return;
  }
  const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
  const handleChange = (e: MediaQueryListEvent) => {
    setResolvedTheme(e.matches ? "dark" : "light");
  };
  setResolvedTheme(mediaQuery.matches ? "dark" : "light");
  mediaQuery.addEventListener("change", handleChange);
  return () => mediaQuery.removeEventListener("change", handleChange);
}, [Theme]);

Once the updated commit is pushed, I'll be happy to re-verify! 🐇


🧠 Learnings used
Learnt from: rahul-vyas-dev
Repo: AOSSIE-Org/SupportUsButton PR: 5
File: src/types/index.ts:89-113
Timestamp: 2026-03-07T09:55:47.961Z
Learning: In the `SupportUsButton` project (`src/types/index.ts`), the `sponsors` type (`export type sponsors = sponsor[]`) is intentionally used to represent both current and previous sponsors in a single array. No separate `previousSponsors` type is needed or expected.


return (
// Container for the support us button, with dynamic classes based on the selected theme and custom class names
<div
className={`w-full font-sans justify-center items-center text-center ${Theme == "light" || Theme == "dark" ? classAccordingToTheme(Theme) : "bg-black text-white"} ${classNames.container}`}
className={`w-full font-sans justify-center items-center text-center ${resolvedTheme == "light" || resolvedTheme == "dark" ? classAccordingToTheme(resolvedTheme) : "bg-black text-white"} ${classNames.container}`}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Use strict equality (===) for consistency.

Line 91 uses loose equality (==) while other comparisons in the file use strict equality (===). Prefer strict equality throughout.

♻️ Proposed fix
-      className={`w-full font-sans justify-center items-center text-center ${resolvedTheme == "light" || resolvedTheme == "dark" ? classAccordingToTheme(resolvedTheme) : "bg-black text-white"} ${classNames.container}`}
+      className={`w-full font-sans justify-center items-center text-center ${resolvedTheme === "light" || resolvedTheme === "dark" ? classAccordingToTheme(resolvedTheme) : "bg-black text-white"} ${classNames.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
className={`w-full font-sans justify-center items-center text-center ${resolvedTheme == "light" || resolvedTheme == "dark" ? classAccordingToTheme(resolvedTheme) : "bg-black text-white"} ${classNames.container}`}
className={`w-full font-sans justify-center items-center text-center ${resolvedTheme === "light" || resolvedTheme === "dark" ? classAccordingToTheme(resolvedTheme) : "bg-black text-white"} ${classNames.container}`}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/SupportUsButton.tsx` at line 91, In SupportUsButton.tsx update
the loose equality to strict equality: in the JSX className expression that
currently checks resolvedTheme == "light" || resolvedTheme == "dark", change to
resolvedTheme === "light" || resolvedTheme === "dark" so it matches the rest of
the file; this affects the conditional that chooses between
classAccordingToTheme(resolvedTheme) and the fallback "bg-black text-white"
alongside classNames.container.

>
{/* Hero section with optional background image*/}
<div className="relative w-full h-[50vh] flex justify-center">
Expand Down Expand Up @@ -119,7 +134,7 @@ function SupportUsButton({
{hero.title}
</h1>
<p
className={`wrap-anywhere ${Theme === "light" ? "text-slate-600" : "text-slate-400"} text-lg font-semibold`}
className={`wrap-anywhere ${resolvedTheme === "light" ? "text-slate-600" : "text-slate-400"} text-lg font-semibold`}
>
{hero.description}
</p>
Expand All @@ -134,21 +149,21 @@ function SupportUsButton({
relative w-[90%] p-15 rounded-2xl overflow-visible

// Shadows for different themes
${Theme === "AOSSIE" && "shadow-xl shadow-primary/20"}
${Theme === "light" && "shadow-xl shadow-gray-300/30"}
${Theme === "dark" && "shadow-xl shadow-gray-700/30"}
${Theme === "minimal" && "shadow-xl shadow-gray-800/30"}
${Theme === "corporate" && "shadow-xl shadow-blue-600/30"}
${resolvedTheme === "AOSSIE" && "shadow-xl shadow-primary/20"}
${resolvedTheme === "light" && "shadow-xl shadow-gray-300/30"}
${resolvedTheme === "dark" && "shadow-xl shadow-gray-700/30"}
${resolvedTheme === "minimal" && "shadow-xl shadow-gray-800/30"}
${resolvedTheme === "corporate" && "shadow-xl shadow-blue-600/30"}

// Outline for light and dark themes
${Theme === "light" && "outline-1 outline-gray-300"}
${Theme === "dark" && "outline-1 outline-gray-700"}
${classAccordingToTheme(Theme)}`}
${resolvedTheme === "light" && "outline-1 outline-gray-300"}
${resolvedTheme === "dark" && "outline-1 outline-gray-700"}
${classAccordingToTheme(resolvedTheme)}`}
Comment on lines +152 to +161
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider extracting repeated theme-based class patterns into a helper.

The pattern ${resolvedTheme === "X" && "shadow-class"} is repeated throughout the file (lines 152-156, 214-218, 252-256, 305-309). A helper object or function could reduce duplication and improve maintainability.

♻️ Example helper approach
const themeStyles = {
  shadows: {
    AOSSIE: "shadow-xl shadow-primary/20",
    light: "shadow-xl shadow-gray-300/30",
    dark: "shadow-xl shadow-gray-700/30",
    minimal: "shadow-xl shadow-gray-800/30",
    corporate: "shadow-xl shadow-blue-600/30",
  },
  // ... other categories
} as const;

// Usage:
className={`... ${themeStyles.shadows[resolvedTheme]} ...`}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/SupportUsButton.tsx` around lines 152 - 161, Extract the
repeated theme-based class patterns into a small helper (e.g., a themeStyles
object or getThemeClasses(resolvedTheme) function) and replace the inline
repeated templates in SupportUsButton.tsx that reference resolvedTheme with
references to that helper; specifically consolidate the shadow mappings (AOSSIE,
light, dark, minimal, corporate) and outline mappings and use
themeStyles.shadows[resolvedTheme] and themeStyles.outlines[resolvedTheme] (or
return both from getThemeClasses) in place of the multiple `${resolvedTheme ===
"X" && "..."}` occurrences and in the classAccordingToTheme usage to remove
duplication and centralize theme-to-class logic.

>
{/* Background grid */}
<div className="absolute inset-0 bg-[radial-gradient(rgba(0,0,0,0.15)_1.5px,transparent_0)] bg-size-[20px_20px] pointer-events-none opacity-100"></div>
<div
className={`absolute top-0 left-0 bottom-0 w-1/2 h-full rounded-2xl p-6 overflow-visible ${classAccordingToTheme(Theme)}`}
className={`absolute top-0 left-0 bottom-0 w-1/2 h-full rounded-2xl p-6 overflow-visible ${classAccordingToTheme(resolvedTheme)}`}
></div>

{/* Content container */}
Expand Down Expand Up @@ -196,32 +211,32 @@ function SupportUsButton({
<div
className={`
border
${Theme === "AOSSIE" && "border-[#f1c514]/50"}
${Theme === "light" && "border-gray-300/50"}
${Theme === "dark" && "border-gray-700/50"}
${Theme === "minimal" && "border-gray-800/50"}
${Theme === "corporate" && "border-blue-600/50"}`}
${resolvedTheme === "AOSSIE" && "border-[#f1c514]/50"}
${resolvedTheme === "light" && "border-gray-300/50"}
${resolvedTheme === "dark" && "border-gray-700/50"}
${resolvedTheme === "minimal" && "border-gray-800/50"}
${resolvedTheme === "corporate" && "border-blue-600/50"}`}
></div>

{/* Project information */}
<div className="flex flex-col gap-2">
<h3
className={`font-bold w-fit uppercase text-sm p-2 rounded-lg
${Theme === "AOSSIE" && "bg-[#edc214]"}
${Theme === "light" && "bg-gray-300/50"}
${Theme === "dark" && "bg-gray-700/50"}
${Theme === "minimal" && "bg-gray-800/50"}
${Theme === "corporate" && "bg-blue-600/50"}`}
${resolvedTheme === "AOSSIE" && "bg-[#edc214]"}
${resolvedTheme === "light" && "bg-gray-300/50"}
${resolvedTheme === "dark" && "bg-gray-700/50"}
${resolvedTheme === "minimal" && "bg-gray-800/50"}
${resolvedTheme === "corporate" && "bg-blue-600/50"}`}
>
ABOUT PROJECT: {organizationInformation.projectInformation.name}
</h3>
<p
className={`italic font-semibold
${Theme === "AOSSIE" && "text-[#614f08]"}
${Theme === "light" && "text-gray-600"}
${Theme === "dark" && "text-gray-400"}
${Theme === "minimal" && "text-gray-800"}
${Theme === "corporate" && "text-blue-600/80"}
${resolvedTheme === "AOSSIE" && "text-[#614f08]"}
${resolvedTheme === "light" && "text-gray-600"}
${resolvedTheme === "dark" && "text-gray-400"}
${resolvedTheme === "minimal" && "text-gray-800"}
${resolvedTheme === "corporate" && "text-blue-600/80"}
`}
>
"{organizationInformation.projectInformation.description}"
Expand All @@ -234,16 +249,16 @@ function SupportUsButton({
{/* Sponsors section */}
<div
className={`w-full flex justify-center mt-10 p-10
${Theme === "AOSSIE" && "bg-[#1f1f1f]"}
${Theme === "light" && "bg-gray-300/50"}
${Theme === "dark" && "bg-gray-700/50"}
${Theme === "minimal" && "bg-gray-800/50"}
${Theme === "corporate" && "bg-blue-600/50"}`}
${resolvedTheme === "AOSSIE" && "bg-[#1f1f1f]"}
${resolvedTheme === "light" && "bg-gray-300/50"}
${resolvedTheme === "dark" && "bg-gray-700/50"}
${resolvedTheme === "minimal" && "bg-gray-800/50"}
${resolvedTheme === "corporate" && "bg-blue-600/50"}`}
>
{sponsors && sponsors.length > 0 && (
{sortedSponsors && sortedSponsors.length > 0 && (
// List of sponsors with their logos and links, styled according to the selected theme and custom class names
<div
className={`${classNames.sponsors} ${classAccordingToTheme(Theme)}
className={`${classNames.sponsors} ${classAccordingToTheme(resolvedTheme)}
relative w-[90%] flex flex-col p-8 rounded-2xl gap-25 mt-15 overflow-hidden`}
>
{/* Sponsor pattern AOSSIE */}
Expand Down Expand Up @@ -275,7 +290,7 @@ function SupportUsButton({

{/* Sponsor logos */}
<div className="flex flex-row flex-wrap justify-center items-center gap-10 z-10">
{sponsors.map((sponsor, index) => (
{sortedSponsors.map((sponsor, index) => (
<a
href={sponsor.link}
key={index}
Expand All @@ -284,14 +299,14 @@ function SupportUsButton({
title={`Visit ${sponsor.name}'s website`}
>
<div
className={`${Theme === "dark" ? "bg-gray-800 text-white" : "bg-white text-black"} rounded-lg flex flex-col justify-center items-center gap-2 p-8 w-fit transition-transform hover:scale-[1.02] shadow-lg min-h-75 min-w-62.5 hover:border-2
className={`${resolvedTheme === "dark" ? "bg-gray-800 text-white" : "bg-white text-black"} rounded-lg flex flex-col justify-center items-center gap-2 p-8 w-fit transition-transform hover:scale-[1.02] shadow-lg min-h-75 min-w-62.5 hover:border-2

// Shadows for different themes
${Theme === "AOSSIE" && "shadow-primary/20"}
${Theme === "light" && "shadow-gray-300/30"}
${Theme === "dark" && "shadow-gray-700/30"}
${Theme === "minimal" && "shadow-gray-800/30"}
${Theme === "corporate" && "shadow-blue-600/30"}
${resolvedTheme === "AOSSIE" && "shadow-primary/20"}
${resolvedTheme === "light" && "shadow-gray-300/30"}
${resolvedTheme === "dark" && "shadow-gray-700/30"}
${resolvedTheme === "minimal" && "shadow-gray-800/30"}
${resolvedTheme === "corporate" && "shadow-blue-600/30"}

// Size based on sponsorship tier
${sponsor.sponsorshipTier === "Platinum" && "min-w-80 min-h-90"}
Expand Down Expand Up @@ -405,15 +420,15 @@ function SupportUsButton({

{/* Call-to-action section with title, description, and sponsor links */}
<div
className={`w-full flex justify-center p-10 ${(Theme === "light" || Theme === "dark") && classAccordingToTheme(Theme)} ${classNames.ctaSection}`}
className={`w-full flex justify-center p-10 ${(resolvedTheme === "light" || resolvedTheme === "dark") && classAccordingToTheme(resolvedTheme)} ${classNames.ctaSection}`}
>
<div className="w-4/5 flex flex-col items-center gap-5 py-20 border border-primary rounded-sm mt-20 mb-20">
<h2 className={`font-extrabold text-4xl md:text-5xl lg:text-6xl`}>
{ctaSection.title}
</h2>
<p
className={`font-semibold
${Theme === "light" ? "text-gray-600" : "text-gray-400"}`}
${resolvedTheme === "light" ? "text-gray-600" : "text-gray-400"}`}
>
{ctaSection.description}
</p>
Expand Down
4 changes: 2 additions & 2 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { ReactNode } from "react";
Theme
========================= */

export type Theme = "AOSSIE" | "light" | "dark" | "minimal" | "corporate";
export type Theme = "AOSSIE" | "light" | "dark" | "minimal" | "corporate" | "system";

/* =========================================================
Button Variant
Expand Down Expand Up @@ -123,7 +123,7 @@ export type Pattern = "AOSSIE" | "dots" | "grid" | "none";
========================= */

export interface supportUsButtonProps {
// Theme for the button, can be one of "AOSSIE", "light", "dark", "minimal", or "corporate"
// Theme for the button, can be one of "AOSSIE", "light", "dark", "minimal", "corporate", or "system"
Theme?: Theme;

// Optional background pattern for the button, can be one of "dots", "grid", "stripes", or "none"
Expand Down
Loading