refactor: polish marketing UI fonts, carousels, and add gradient button - #145
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis PR standardizes marketing typography and layout utilities, introduces a reusable gradient button, adds custom donation amounts, updates navigation and imagery, revises marketing carousels, and applies course-player presentation changes. ChangesMarketing typography foundation
Shared CTAs and donation flow
Layout, navigation, and assets
Carousel interactions
Course-player presentation
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
⚔️ Resolve merge conflicts 💡
🧪 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.
Actionable comments posted: 15
🧹 Nitpick comments (3)
src/features/marketing/components/stories-from-our-community-section.tsx (1)
140-140: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReplace the arbitrary hex color with a design token.
Both navigation buttons use
disabled:text-[#D9D9D9]. Use a repository token, for example a muted foreground token, so the disabled state follows the theme.As per coding guidelines: "Do not use arbitrary hex colors or spacing; use the repository's design tokens."
Also applies to: 149-149
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/marketing/components/stories-from-our-community-section.tsx` at line 140, Replace the disabled:text-[`#D9D9D9`] utility on both navigation buttons in the stories section with the repository’s muted foreground design-token utility, preserving the existing disabled styling and button behavior.Source: Coding guidelines
src/features/marketing/components/testimonials.tsx (2)
113-113: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valuePrefer design tokens over arbitrary sizing values.
These lines add arbitrary spacing and type sizes, for example
mt-21.5,max-w-[1050px],h-[500px],w-[330px],px-[15px],pb-[30px],text-[100px], andmt-[-14px]. Map the recurring values to the repository's design tokens where a token exists.As per coding guidelines: "Do not use arbitrary hex colors or spacing; use the repository's design tokens."
Also applies to: 129-129, 144-147
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/marketing/components/testimonials.tsx` at line 113, Replace the arbitrary Tailwind sizing values in the testimonials component, including mt-21.5, max-w-[1050px], h-[500px], w-[330px], px-[15px], pb-[30px], text-[100px], and mt-[-14px], with the closest existing repository design tokens. Preserve the current layout and responsive behavior while applying tokens consistently to the affected elements.Source: Coding guidelines
46-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove the unused pagination state and DOM ref.
activeIndexis never read.scrollRefis only assigned and never read. Remove both, includingref={scrollRef}, and keeppageIndexas the single source of truth.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/marketing/components/testimonials.tsx` around lines 46 - 49, Remove the unused activeIndex state and scrollRef declaration from the testimonials component, then remove the corresponding ref={scrollRef} usage. Preserve pageIndex and direction state as the single pagination source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@next.config.ts`:
- Line 28: Update the image quality allowlist in the Next.js image configuration
to include the default quality value 75 alongside the existing 100 entry,
preserving support for the background Image in about-impact-carousel-section.tsx
while retaining explicit quality 100 support.
In `@src/app/`(user)/my-learning/page.tsx:
- Line 90: Update the loading and empty-state wrappers in MyLearningPage to use
the same marketing-container class as the loaded state, while preserving only
their state-specific vertical spacing classes.
In `@src/features/marketing/components/about-impact-carousel-section.tsx`:
- Around line 107-108: Update the mapped impact images in the about impact
carousel to remove the priority prop and explicitly use lazy loading, ensuring
all seven below-hero photos are not preloaded.
In `@src/features/marketing/components/contact-hero-section.tsx`:
- Line 49: Update the grid breakpoint on the marketing-container in the contact
hero section so the 562px column and 88px gap are applied only at xl, not lg;
preserve the narrower responsive layout at lg so the form fields do not become
squeezed or overflow.
In `@src/features/marketing/components/course-information-card.tsx`:
- Line 64: Update the h2 className in the course information card to replace the
malformed leading/[100%]-normal utility with the intended valid Tailwind
utility, using either leading-normal or leading-[100%] while preserving the
other classes.
In `@src/features/marketing/components/cta-section.tsx`:
- Around line 21-24: Update the CTA GradientButton in the marketing section so
activating it navigates to the existing /contact destination. Use the project’s
established link/navigation pattern rather than leaving it as a button without
an href or onClick handler, while preserving the current label and styling.
In `@src/features/marketing/components/donation-form.tsx`:
- Around line 71-76: Validate parsedCustomAmount for finiteness before passing
it to computeAdminFee and computeDonationTotal in the donation form. When the
custom amount is invalid, avoid calculating or displaying Infinity by using the
existing neutral-value or inline-validation behavior, while preserving normal
calculations for valid custom amounts and watchedAmount.
- Around line 271-277: Update the GradientButton in the donation CTA to set
type="submit" and form="donation-form" so it submits the existing donation-form
handler; also restore disabled={submitting || !isConfirmed} to prevent duplicate
or unconfirmed checkout requests.
In `@src/features/marketing/components/footer.tsx`:
- Around line 83-86: Replace the arbitrary p-[15px] padding on the
GradientButton with the matching repository spacing token, using p-4 if it
preserves the current layout. Keep the button’s other classes and content
unchanged.
- Around line 64-65: Update the newsletter heading text in the marketing
footer’s h2 element from “Join our News letter” to “Join our Newsletter,”
preserving the existing styling and structure.
In `@src/features/marketing/components/stories-from-our-community-section.tsx`:
- Around line 59-78: In
src/features/marketing/components/stories-from-our-community-section.tsx lines
59-78, update the carousel effect around transitionTo and trackRef to recompute
the GSAP x offset from the current track.clientWidth whenever the viewport
resizes or orientation changes, and kill any active tween during unmount
cleanup. In src/features/marketing/components/testimonials.tsx lines 51-52,
clamp pageIndex to the recomputed maxIndex whenever visibleCount changes,
including the transition from one to three visible items.
- Around line 104-158: Update the stories map in the track so navigation
controls are rendered only once outside the per-story slides, or ensure every
inactive slide is marked inert and non-focusable. Preserve the existing
showPreviousStory/showNextStory behavior and active-slide controls while
preventing off-screen buttons from entering the accessibility tree or being
announced repeatedly.
In `@src/features/marketing/components/testimonials.tsx`:
- Around line 51-52: Clamp pageIndex to maxIndex whenever visibleCount or
maxIndex changes, and use the clamped value as the starting index for
reviews.slice. Preserve existing pagination behavior while ensuring viewport
changes cannot leave pageIndex beyond the new maximum.
- Around line 143-153: Update the testimonial data and rendering in the
testimonials component so each review entry includes its intended quote and
renders that entry’s quote instead of shared hardcoded text. Replace duplicated
“Michael R.” values with the distinct reviewer names associated with each
testimonial, while preserving the existing card layout and styling.
In `@src/features/user-dashboard/components/course-player-content.tsx`:
- Line 171: Update the Skeleton in the course player content loading state to
include the rounded-none class, preserving square corners consistently with the
unavailable-video placeholders and the corresponding loading skeleton in
CoursePlayerShell.
---
Nitpick comments:
In `@src/features/marketing/components/stories-from-our-community-section.tsx`:
- Line 140: Replace the disabled:text-[`#D9D9D9`] utility on both navigation
buttons in the stories section with the repository’s muted foreground
design-token utility, preserving the existing disabled styling and button
behavior.
In `@src/features/marketing/components/testimonials.tsx`:
- Line 113: Replace the arbitrary Tailwind sizing values in the testimonials
component, including mt-21.5, max-w-[1050px], h-[500px], w-[330px], px-[15px],
pb-[30px], text-[100px], and mt-[-14px], with the closest existing repository
design tokens. Preserve the current layout and responsive behavior while
applying tokens consistently to the affected elements.
- Around line 46-49: Remove the unused activeIndex state and scrollRef
declaration from the testimonials component, then remove the corresponding
ref={scrollRef} usage. Preserve pageIndex and direction state as the single
pagination source of truth.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ef15b6d5-dd0d-45ae-91da-5861ce398073
⛔ Files ignored due to path filters (1)
public/figma-home/community-story-6.pngis excluded by!**/*.png
📒 Files selected for processing (48)
next.config.tssrc/app/(marketing)/contact/page.tsxsrc/app/(marketing)/layout.tsxsrc/app/(user)/account/layout.tsxsrc/app/(user)/my-learning/page.tsxsrc/app/globals.csssrc/app/layout.tsxsrc/features/marketing/components/about-ai-healthcare-section.tsxsrc/features/marketing/components/about-hero-section.tsxsrc/features/marketing/components/about-impact-carousel-section.tsxsrc/features/marketing/components/about-our-values-section.tsxsrc/features/marketing/components/about-support-mission-section.tsxsrc/features/marketing/components/community-section.tsxsrc/features/marketing/components/contact-hero-section.tsxsrc/features/marketing/components/course-detail-content-section.tsxsrc/features/marketing/components/course-detail-hero-section.tsxsrc/features/marketing/components/course-information-card.tsxsrc/features/marketing/components/course-module.tsxsrc/features/marketing/components/courses-hero-section.tsxsrc/features/marketing/components/cta-section.tsxsrc/features/marketing/components/donation-cta-section.tsxsrc/features/marketing/components/donation-form.tsxsrc/features/marketing/components/donation-hero-section.tsxsrc/features/marketing/components/donation-support-section.tsxsrc/features/marketing/components/empowerment-section.tsxsrc/features/marketing/components/faq.tsxsrc/features/marketing/components/featured-courses.tsxsrc/features/marketing/components/footer.tsxsrc/features/marketing/components/hero-content.tsxsrc/features/marketing/components/hero-section.tsxsrc/features/marketing/components/how-can-we-help.tsxsrc/features/marketing/components/how-can-we-support.tsxsrc/features/marketing/components/how-it-works.tsxsrc/features/marketing/components/invest-in-hope-section.tsxsrc/features/marketing/components/mission-sections.tsxsrc/features/marketing/components/navbar.tsxsrc/features/marketing/components/our-vision-section.tsxsrc/features/marketing/components/practical-pathways-section.tsxsrc/features/marketing/components/review-card.tsxsrc/features/marketing/components/social-proof.tsxsrc/features/marketing/components/stories-from-our-community-section.tsxsrc/features/marketing/components/testimonials.tsxsrc/features/marketing/components/tutor-card.tsxsrc/features/marketing/components/why-choose-us.tsxsrc/features/user-dashboard/components/course-player-content.tsxsrc/features/user-dashboard/components/course-player-shell.tsxsrc/features/user-dashboard/components/dashboard-navbar.tsxsrc/shared/ui/gradient-button.tsx
| pathname: "/**", | ||
| }, | ||
| ], | ||
| qualities: [100], |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Expect: all Image usages and explicit quality values are reviewed against the global allowlist.
rg -n -C 3 '(<Image|quality\s*=|qualities\s*:)' next.config.ts src --glob '*.{ts,tsx}'Repository: Smartlify07/Modern-Advocates
Length of output: 28079
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- package versions ---'
rg -n '"next"\s*:|next@' package.json package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null || true
printf '%s\n' '--- next.config.ts ---'
cat -n next.config.ts
printf '%s\n' '--- affected component ---'
cat -n src/features/marketing/components/about-impact-carousel-section.tsx
printf '%s\n' '--- Image quality props in source ---'
rg -n -C 2 'quality\s*=|qualities\s*:' src next.config.ts --glob '*.{ts,tsx}'Repository: Smartlify07/Modern-Advocates
Length of output: 13358
🌐 Web query:
Next.js 16 images.qualities default quality omitted closest allowed value 75 100 official documentation
💡 Result:
In Next.js 16, the handling of image quality has changed significantly to improve security and prevent abuse [1][2]. The images.qualities configuration now defaults to [75] [1][3], and this field is effectively required because unrestricted access to all quality levels is no longer allowed [1]. If you pass a quality prop to the Image component that does not match one of the values explicitly defined in your images.qualities array, the system will automatically coerce your requested value to the closest allowed value from that list [1][2]. Key details include: * Default Behavior: By default, only 75 is allowed [1][2]. If you do not configure images.qualities, any quality prop you provide will be coerced to 75 [1]. * Configuration: To support other quality levels (such as 100), you must explicitly define them in your next.config.js file: js filename="next.config.js" module.exports = { images: { qualities: [75, 100], }, } * Coercion Logic: The Image Optimization API selects the closest value from your defined qualities list based on the absolute mathematical difference between your requested quality and the allowed entries [1][4]. For example, if your configuration is qualities: [75, 100] and you request quality={85}, the system will select 75 as it is closer to 85 than 100 is [1][4]. If you request quality={90}, it will select 100 [1][4].
Citations:
- 1: https://nextjs.org/docs/app/api-reference/components/image
- 2: https://codeharbor.tech/blog/next-js-16-image-defaults-changed
- 3: https://en.nextjs.im/docs/app/api-reference/components/image/
- 4: feat(next/image): add support for
images.qualitiesin next.config vercel/next.js#74500
Keep the default image quality in the allowlist.
The background Image in about-impact-carousel-section.tsx omits quality. Next.js 16 defaults it to 75, then coerces it to 100 because images.qualities contains only 100. Add 75, for example [75, 100], unless this higher payload is intentional.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@next.config.ts` at line 28, Update the image quality allowlist in the Next.js
image configuration to include the default quality value 75 alongside the
existing 100 entry, preserving support for the background Image in
about-impact-carousel-section.tsx while retaining explicit quality 100 support.
|
|
||
| return ( | ||
| <div className="mx-auto px-4 py-12.5 lg:max-w-7xl lg:px-25 lg:py-19.25 2xl:max-w-360 2xl:px-50"> | ||
| <div className="marketing-container"> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use the same container contract for every render state.
MyLearningPage now uses marketing-container only for the loaded state. The loading and empty states still use different wrappers. If the shared class changes max-width or horizontal padding, the page shifts when the query state changes. Update those wrappers to use marketing-container and retain only state-specific vertical spacing.
Proposed loading-state alignment
- <div className="mx-auto px-4 py-8 lg:max-w-7xl lg:px-25 2xl:max-w-360 2xl:px-50">
+ <div className="marketing-container py-8">🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/`(user)/my-learning/page.tsx at line 90, Update the loading and
empty-state wrappers in MyLearningPage to use the same marketing-container class
as the loaded state, while preserving only their state-specific vertical spacing
classes.
| priority | ||
| quality={100} |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Expect: only the actual LCP image uses priority, preload, or high fetch priority.
rg -n -C 3 '\b(priority|preload|fetchPriority)\b' src --glob '*.{ts,tsx}'Repository: Smartlify07/Modern-Advocates
Length of output: 7847
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- component outline ---'
ast-grep outline src/features/marketing/components/about-impact-carousel-section.tsx --view expanded
printf '%s\n' '--- component source ---'
sed -n '1,180p' src/features/marketing/components/about-impact-carousel-section.tsx
printf '%s\n' '--- Next.js version and image configuration ---'
rg -n '"next"|"version"|"images"|"qualities"' package.json package-lock.json pnpm-lock.yaml yarn.lock next.config.* 2>/dev/null || true
printf '%s\n' '--- impact photo definitions and usages ---'
rg -n -C 4 'impactPhotos|AboutImpactCarouselSection' src/features/marketing src/appRepository: Smartlify07/Modern-Advocates
Length of output: 8116
🌐 Web query:
Next.js 16.2.6 Image component priority prop deprecated preload documentation
💡 Result:
In Next.js 16 and later, the priority prop on the Image component has been deprecated in favor of the new preload prop [1][2][3]. This change was introduced in version 16.0.0 to improve clarity regarding the behavior of the component [1][4]. While the priority prop is deprecated, it has not been removed and will continue to function, though it will trigger a warning in development [5]. Key details regarding this update: - Use preload instead of priority: For images that serve as the Largest Contentful Paint (LCP) element or are otherwise critical to above-the-fold content, you should now use the preload prop [5][3]. - Code Transition: // Deprecated in Next.js 16
// Recommended for Next.js 16+
- Alternatives for specific use cases: If you have images that may be the LCP element depending on the viewport (e.g., different hero images for mobile vs. desktop), documentation suggests using loading="eager" or fetchPriority="high" instead of preloading every possible image [5][3]. Official documentation for the Image component provides further details on these props and best practices for image optimization [1].
Citations:
- 1: https://nextjs.org/docs/app/api-reference/components/image
- 2: feat(next/image): introduce
preloadprop and deprecatedpriorityprop vercel/next.js#83351 - 3: https://vercel.com/academy/nextjs-foundations/advanced-image-optimization
- 4: https://en.nextjs.im/docs/app/api-reference/components/image/
- 5: https://aitoolsguidebook.com/en/articles/nextjs-image-optimization/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
component = Path("src/features/marketing/components/about-impact-carousel-section.tsx").read_text()
photos = re.findall(r'src:\s*"([^"]+)"', component)
map_block = component[component.index("{impactPhotos.map"):]
print(f"impact photo count: {len(photos)}")
print(f"priority props in mapped block: {len(re.findall(r'\\bpriority\\b', map_block))}")
print(f"preload props in mapped block: {len(re.findall(r'\\bpreload\\b', map_block))}")
print(f"loading props in mapped block: {len(re.findall(r'\\bloading\\s*=', map_block))}")
for src in photos:
path = Path("public") / src.lstrip("/")
print(f"{src}: {path.stat().st_size if path.exists() else 'missing'} bytes")
page = Path("src/app/(marketing)/about/page.tsx").read_text()
print("impact section follows hero:", page.index("AboutHeroSection") < page.index("AboutImpactCarouselSection"))
PYRepository: Smartlify07/Modern-Advocates
Length of output: 444
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
component = Path("src/features/marketing/components/about-impact-carousel-section.tsx").read_text()
photos = re.findall(r'src:\s*"([^"]+)"', component)
map_block = component[component.index("{impactPhotos.map"):]
priority_count = len(re.findall(r"\bpriority\b", map_block))
preload_count = len(re.findall(r"\bpreload\b", map_block))
loading_count = len(re.findall(r"\bloading\s*=", map_block))
print(f"impact photo count: {len(photos)}")
print(f"priority props in mapped block: {priority_count}")
print(f"preload props in mapped block: {preload_count}")
print(f"loading props in mapped block: {loading_count}")
for src in photos:
path = Path("public") / src.lstrip("/")
print(f"{src}: {path.stat().st_size if path.exists() else 'missing'} bytes")
page = Path("src/app/(marketing)/about/page.tsx").read_text()
print("impact section follows hero:", page.index("AboutHeroSection") < page.index("AboutImpactCarouselSection"))
PYRepository: Smartlify07/Modern-Advocates
Length of output: 644
Do not preload the impact photos.
All seven mapped images use priority, preloading about 2.9 MB of below-hero content. Remove priority from these images and use lazy loading.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/marketing/components/about-impact-carousel-section.tsx` around
lines 107 - 108, Update the mapped impact images in the about impact carousel to
remove the priority prop and explicitly use lazy loading, ensuring all seven
below-hero photos are not preloaded.
| return ( | ||
| <section id="contact" className="bg-white text-ma-text"> | ||
| <div className="marketing-container grid items-start gap-14 lg:grid-cols-[444px_1fr] lg:gap-[88px]"> | ||
| <div className="marketing-container grid items-start gap-14 lg:grid-cols-[562px_1fr] lg:gap-[88px]"> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Keep the 562px column out of the lg breakpoint.
At a 1024px viewport, marketing-container leaves 824px of content width after lg:px-25. The 562px column and 88px gap leave only 174px for the form. The form’s responsive two-column fields will be squeezed or overflow at these widths.
Keep the narrower column at lg and apply the 562px layout at xl, or stack the form until enough width is available.
Suggested breakpoint adjustment
- <div className="marketing-container grid items-start gap-14 lg:grid-cols-[562px_1fr] lg:gap-[88px]">
+ <div className="marketing-container grid items-start gap-14 lg:grid-cols-[444px_minmax(0,1fr)] xl:grid-cols-[562px_minmax(0,1fr)] lg:gap-[88px]">📝 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.
| <div className="marketing-container grid items-start gap-14 lg:grid-cols-[562px_1fr] lg:gap-[88px]"> | |
| <div className="marketing-container grid items-start gap-14 lg:grid-cols-[444px_minmax(0,1fr)] xl:grid-cols-[562px_minmax(0,1fr)] lg:gap-[88px]"> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/marketing/components/contact-hero-section.tsx` at line 49,
Update the grid breakpoint on the marketing-container in the contact hero
section so the 562px column and 88px gap are applied only at xl, not lg;
preserve the narrower responsive layout at lg so the form fields do not become
squeezed or overflow.
| return ( | ||
| <aside className="w-full rounded-2xl border border-ma-border-light bg-white px-4 pt-4 pr-3.5 pb-[25px] lg:top-8"> | ||
| <h2 className="leading/[100%]-normal text-xl font-extrabold text-ma-text sm:text-2xl"> | ||
| <h2 className="marketing-header leading/[100%]-normal text-xl font-extrabold text-ma-text sm:text-2xl"> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
! rg -n 'leading/\[100%\]-normal' src/features/marketing/components/course-information-card.tsx
npm run lint
npx tsc --noEmitRepository: Smartlify07/Modern-Advocates
Length of output: 22229
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- package metadata ---'
rg -n '"(tailwindcss|`@tailwindcss/postcss`|next)"|tailwind' package.json package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null || true
printf '%s\n' '--- target component ---'
sed -n '52,72p' src/features/marketing/components/course-information-card.tsx
printf '%s\n' '--- related class definitions and usage ---'
rg -n 'marketing-header|leading/\[100%\]-normal|leading-\[100%\]|leading-normal' . \
-g '!node_modules' -g '!dist' -g '!build' -g '!*lock*' || true
printf '%s\n' '--- token-shape probe ---'
python3 - <<'PY'
from pathlib import Path
import re
path = Path("src/features/marketing/components/course-information-card.tsx")
text = path.read_text()
classes = re.findall(r'className="([^"]*)"', text)
for value in classes:
for token in value.split():
if "leading" in token:
print(f"{token!r}: {'valid named leading utility' if token == 'leading-normal' else 'valid arbitrary leading utility' if re.fullmatch(r'leading-\\[[^]]+\\]', token) else 'not a Tailwind leading utility token'}")
PYRepository: Smartlify07/Modern-Advocates
Length of output: 25632
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- marketing-header definition ---'
sed -n '184,202p' src/app/globals.css
printf '%s\n' '--- exact class-token classification ---'
python3 - <<'PY'
from pathlib import Path
import re
line = next(
line for line in Path("src/features/marketing/components/course-information-card.tsx").read_text().splitlines()
if "Course information" in line
)
class_value = re.search(r'className="([^"]*)"', line).group(1)
for token in class_value.split():
if token.startswith("leading"):
if token == "leading-normal":
result = "valid named leading utility"
elif re.fullmatch(r"leading-\[[^]]+\]", token):
result = "valid arbitrary leading utility"
else:
result = "not a valid leading utility token"
print(f"{token!r}: {result}")
PY
printf '%s\n' '--- nearby valid patterns ---'
sed -n '48,70p' src/features/marketing/components/course-detail-content-section.tsxRepository: Smartlify07/Modern-Advocates
Length of output: 826
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
line = next(
line for line in Path("src/features/marketing/components/course-information-card.tsx").read_text().splitlines()
if '<h2 className=' in line
)
match = re.search(r'className="([^"]*)"', line)
if not match:
raise SystemExit(f"Could not parse className from: {line}")
for token in match.group(1).split():
if token.startswith("leading"):
if token == "leading-normal":
result = "valid named leading utility"
elif re.fullmatch(r"leading-\[[^]]+\]", token):
result = "valid arbitrary leading utility"
else:
result = "not a valid leading utility token"
print(f"{token!r}: {result}")
PYRepository: Smartlify07/Modern-Advocates
Length of output: 225
Replace the malformed Tailwind utility.
leading/[100%]-normal is not a valid Tailwind CSS v4 utility. Replace it with leading-normal or leading-[100%].
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/marketing/components/course-information-card.tsx` at line 64,
Update the h2 className in the course information card to replace the malformed
leading/[100%]-normal utility with the intended valid Tailwind utility, using
either leading-normal or leading-[100%] while preserving the other classes.
Source: Coding guidelines
| const [activeIndex, setActiveIndex] = useState(0) | ||
| const trackRef = useRef<HTMLDivElement>(null) | ||
| const isTransitioningRef = useRef(false) | ||
|
|
||
| const slideVariants = { | ||
| enter: (dir: number) => ({ x: dir * 48 }), | ||
| center: { x: 0 }, | ||
| exit: (dir: number) => ({ x: dir * -48 }), | ||
| function transitionTo(nextIndex: number) { | ||
| const track = trackRef.current | ||
| if (!track || nextIndex === activeIndex || isTransitioningRef.current) | ||
| return | ||
|
|
||
| isTransitioningRef.current = true | ||
| gsap.to(track, { | ||
| x: -nextIndex * track.clientWidth, | ||
| duration: 0.45, | ||
| ease: "power2.inOut", | ||
| onComplete: () => { | ||
| setActiveIndex(nextIndex) | ||
| isTransitioningRef.current = false | ||
| }, | ||
| }) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Carousel state is not resynchronized when the viewport changes. Both carousels derive layout state from viewport-dependent values and never recompute that state after a resize or an orientation change, so each carousel can render a partial or misaligned slide.
src/features/marketing/components/stories-from-our-community-section.tsx#L59-L78: recompute the GSAPxoffset from the currenttrack.clientWidthon resize, and kill the tween on unmount.src/features/marketing/components/testimonials.tsx#L51-L52: clamppageIndexto the recomputedmaxIndexwhenvisibleCountchanges from 1 to 3.
📍 Affects 2 files
src/features/marketing/components/stories-from-our-community-section.tsx#L59-L78(this comment)src/features/marketing/components/testimonials.tsx#L51-L52
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/marketing/components/stories-from-our-community-section.tsx`
around lines 59 - 78, In
src/features/marketing/components/stories-from-our-community-section.tsx lines
59-78, update the carousel effect around transitionTo and trackRef to recompute
the GSAP x offset from the current track.clientWidth whenever the viewport
resizes or orientation changes, and kill any active tween during unmount
cleanup. In src/features/marketing/components/testimonials.tsx lines 51-52,
clamp pageIndex to the recomputed maxIndex whenever visibleCount changes,
including the transition from one to three visible items.
| <div ref={trackRef} className="flex w-full will-change-transform"> | ||
| {stories.map((story) => ( | ||
| <div | ||
| key={story.name} | ||
| className="grid w-full min-w-full shrink-0 gap-8 lg:grid-cols-[minmax(0,600px)_minmax(0,605px)] lg:items-start lg:gap-[50px]" | ||
| > | ||
| <Image | ||
| src={activeStory.image} | ||
| alt={activeStory.imageAlt} | ||
| fill | ||
| sizes="(min-width: 1024px) 600px, calc(100vw - 32px)" | ||
| className={cn("object-cover", activeStory.imageClassName)} | ||
| /> | ||
| </motion.div> | ||
| </AnimatePresence> | ||
| <div className="relative min-h-[340px] overflow-hidden rounded-3xl bg-ma-bg sm:min-h-[460px] lg:h-[560px]"> | ||
| <Image | ||
| src={story.image} | ||
| alt={story.imageAlt} | ||
| fill | ||
| sizes="(min-width: 1024px) 600px, calc(100vw - 32px)" | ||
| className={cn("object-cover", story.imageClassName)} | ||
| /> | ||
| </div> | ||
|
|
||
| <div className="flex min-h-[420px] flex-col justify-between lg:min-h-[560px]"> | ||
| <AnimatePresence mode="wait" custom={direction} initial={false}> | ||
| <motion.div | ||
| key={activeIndex} | ||
| custom={direction} | ||
| variants={slideVariants} | ||
| initial="enter" | ||
| animate="center" | ||
| exit="exit" | ||
| transition={{ duration: 0.3, ease: "easeInOut" }} | ||
| className="flex flex-col justify-between" | ||
| > | ||
| <p className="text-lg font-medium text-primary sm:text-3xl"> | ||
| {activeStory.quote} | ||
| <div className="flex min-h-[420px] flex-col justify-between lg:min-h-[560px]"> | ||
| <p className="text-xl font-medium text-primary sm:text-3xl"> | ||
| {story.quote} | ||
| </p> | ||
|
|
||
| <div className="mt-4 space-y-3 sm:mt-8 sm:space-y-4"> | ||
| <h3 className="text-base font-semibold text-primary sm:text-2xl"> | ||
| {activeStory.name} | ||
| </h3> | ||
| <p className="text-base text-ma-muted-text sm:text-xl"> | ||
| {activeStory.role} | ||
| </p> | ||
| <div className="flex items-center justify-between"> | ||
| <div className="space-y-3 sm:space-y-4"> | ||
| <h3 className="marketing-header text-base font-semibold text-primary sm:text-2xl"> | ||
| {story.name} | ||
| </h3> | ||
| <p className="text-base text-ma-muted-text sm:text-xl"> | ||
| {story.role} | ||
| </p> | ||
| </div> | ||
| <div className="flex justify-center gap-[18px]"> | ||
| <button | ||
| type="button" | ||
| aria-label="Previous community story" | ||
| onClick={showPreviousStory} | ||
| disabled={activeIndex === 0} | ||
| className="flex size-[50px] items-center justify-center rounded-full border border-border text-primary transition-colors hover:border-ma-text/20 hover:bg-white disabled:cursor-not-allowed disabled:bg-ma-bg disabled:text-[#D9D9D9] disabled:hover:border-border disabled:hover:bg-ma-bg sm:size-[60px]" | ||
| > | ||
| <ArrowLeft className="size-6" aria-hidden="true" /> | ||
| </button> | ||
| <button | ||
| type="button" | ||
| aria-label="Next community story" | ||
| onClick={showNextStory} | ||
| disabled={activeIndex === stories.length - 1} | ||
| className="flex size-[50px] items-center justify-center rounded-full border border-border text-primary transition-colors hover:border-ma-text/20 hover:bg-white disabled:cursor-not-allowed disabled:bg-ma-bg disabled:text-[#D9D9D9] disabled:hover:border-border disabled:hover:bg-ma-bg sm:size-[60px]" | ||
| > | ||
| <ArrowRight className="size-6" aria-hidden="true" /> | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </motion.div> | ||
| </AnimatePresence> | ||
|
|
||
| <div className="mt-8 flex justify-center gap-[18px] sm:justify-end"> | ||
| <button | ||
| type="button" | ||
| aria-label="Previous community story" | ||
| onClick={showPreviousStory} | ||
| className="flex size-[50px] items-center justify-center rounded-full border border-border bg-ma-bg text-primary transition-colors hover:border-ma-text/20 hover:bg-white sm:size-[60px]" | ||
| > | ||
| <ArrowLeft className="size-6" aria-hidden="true" /> | ||
| </button> | ||
| <button | ||
| type="button" | ||
| aria-label="Next community story" | ||
| onClick={showNextStory} | ||
| className="flex size-[50px] items-center justify-center rounded-full border border-border bg-ma-bg text-primary transition-colors hover:border-ma-text/20 hover:bg-white sm:size-[60px]" | ||
| > | ||
| <ArrowRight className="size-6" aria-hidden="true" /> | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </article> | ||
| ))} | ||
| </div> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Remove the duplicate navigation buttons from off-screen slides.
The track renders every story, and each slide contains its own previous and next buttons. The container uses overflow-hidden, not display: none. The off-screen buttons stay in the accessibility tree and stay focusable. Keyboard users tab into controls they cannot see, and screen readers announce Previous community story and Next community story once per story.
Render the navigation controls once outside the track, or mark non-active slides with inert.
🔧 Minimal mitigation if the controls stay inside each slide
<div
key={story.name}
+ inert={story.name !== stories[activeIndex]?.name ? true : undefined}
className="grid w-full min-w-full shrink-0 gap-8 lg:grid-cols-[minmax(0,600px)_minmax(0,605px)] lg:items-start lg:gap-[50px]"
>📝 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.
| <div ref={trackRef} className="flex w-full will-change-transform"> | |
| {stories.map((story) => ( | |
| <div | |
| key={story.name} | |
| className="grid w-full min-w-full shrink-0 gap-8 lg:grid-cols-[minmax(0,600px)_minmax(0,605px)] lg:items-start lg:gap-[50px]" | |
| > | |
| <Image | |
| src={activeStory.image} | |
| alt={activeStory.imageAlt} | |
| fill | |
| sizes="(min-width: 1024px) 600px, calc(100vw - 32px)" | |
| className={cn("object-cover", activeStory.imageClassName)} | |
| /> | |
| </motion.div> | |
| </AnimatePresence> | |
| <div className="relative min-h-[340px] overflow-hidden rounded-3xl bg-ma-bg sm:min-h-[460px] lg:h-[560px]"> | |
| <Image | |
| src={story.image} | |
| alt={story.imageAlt} | |
| fill | |
| sizes="(min-width: 1024px) 600px, calc(100vw - 32px)" | |
| className={cn("object-cover", story.imageClassName)} | |
| /> | |
| </div> | |
| <div className="flex min-h-[420px] flex-col justify-between lg:min-h-[560px]"> | |
| <AnimatePresence mode="wait" custom={direction} initial={false}> | |
| <motion.div | |
| key={activeIndex} | |
| custom={direction} | |
| variants={slideVariants} | |
| initial="enter" | |
| animate="center" | |
| exit="exit" | |
| transition={{ duration: 0.3, ease: "easeInOut" }} | |
| className="flex flex-col justify-between" | |
| > | |
| <p className="text-lg font-medium text-primary sm:text-3xl"> | |
| {activeStory.quote} | |
| <div className="flex min-h-[420px] flex-col justify-between lg:min-h-[560px]"> | |
| <p className="text-xl font-medium text-primary sm:text-3xl"> | |
| {story.quote} | |
| </p> | |
| <div className="mt-4 space-y-3 sm:mt-8 sm:space-y-4"> | |
| <h3 className="text-base font-semibold text-primary sm:text-2xl"> | |
| {activeStory.name} | |
| </h3> | |
| <p className="text-base text-ma-muted-text sm:text-xl"> | |
| {activeStory.role} | |
| </p> | |
| <div className="flex items-center justify-between"> | |
| <div className="space-y-3 sm:space-y-4"> | |
| <h3 className="marketing-header text-base font-semibold text-primary sm:text-2xl"> | |
| {story.name} | |
| </h3> | |
| <p className="text-base text-ma-muted-text sm:text-xl"> | |
| {story.role} | |
| </p> | |
| </div> | |
| <div className="flex justify-center gap-[18px]"> | |
| <button | |
| type="button" | |
| aria-label="Previous community story" | |
| onClick={showPreviousStory} | |
| disabled={activeIndex === 0} | |
| className="flex size-[50px] items-center justify-center rounded-full border border-border text-primary transition-colors hover:border-ma-text/20 hover:bg-white disabled:cursor-not-allowed disabled:bg-ma-bg disabled:text-[#D9D9D9] disabled:hover:border-border disabled:hover:bg-ma-bg sm:size-[60px]" | |
| > | |
| <ArrowLeft className="size-6" aria-hidden="true" /> | |
| </button> | |
| <button | |
| type="button" | |
| aria-label="Next community story" | |
| onClick={showNextStory} | |
| disabled={activeIndex === stories.length - 1} | |
| className="flex size-[50px] items-center justify-center rounded-full border border-border text-primary transition-colors hover:border-ma-text/20 hover:bg-white disabled:cursor-not-allowed disabled:bg-ma-bg disabled:text-[#D9D9D9] disabled:hover:border-border disabled:hover:bg-ma-bg sm:size-[60px]" | |
| > | |
| <ArrowRight className="size-6" aria-hidden="true" /> | |
| </button> | |
| </div> | |
| </div> | |
| </motion.div> | |
| </AnimatePresence> | |
| <div className="mt-8 flex justify-center gap-[18px] sm:justify-end"> | |
| <button | |
| type="button" | |
| aria-label="Previous community story" | |
| onClick={showPreviousStory} | |
| className="flex size-[50px] items-center justify-center rounded-full border border-border bg-ma-bg text-primary transition-colors hover:border-ma-text/20 hover:bg-white sm:size-[60px]" | |
| > | |
| <ArrowLeft className="size-6" aria-hidden="true" /> | |
| </button> | |
| <button | |
| type="button" | |
| aria-label="Next community story" | |
| onClick={showNextStory} | |
| className="flex size-[50px] items-center justify-center rounded-full border border-border bg-ma-bg text-primary transition-colors hover:border-ma-text/20 hover:bg-white sm:size-[60px]" | |
| > | |
| <ArrowRight className="size-6" aria-hidden="true" /> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </article> | |
| ))} | |
| </div> | |
| <div ref={trackRef} className="flex w-full will-change-transform"> | |
| {stories.map((story) => ( | |
| <div | |
| key={story.name} | |
| inert={story.name !== stories[activeIndex]?.name ? true : undefined} | |
| className="grid w-full min-w-full shrink-0 gap-8 lg:grid-cols-[minmax(0,600px)_minmax(0,605px)] lg:items-start lg:gap-[50px]" | |
| > | |
| <div className="relative min-h-[340px] overflow-hidden rounded-3xl bg-ma-bg sm:min-h-[460px] lg:h-[560px]"> | |
| <Image | |
| src={story.image} | |
| alt={story.imageAlt} | |
| fill | |
| sizes="(min-width: 1024px) 600px, calc(100vw - 32px)" | |
| className={cn("object-cover", story.imageClassName)} | |
| /> | |
| </div> | |
| <div className="flex min-h-[420px] flex-col justify-between lg:min-h-[560px]"> | |
| <p className="text-xl font-medium text-primary sm:text-3xl"> | |
| {story.quote} | |
| </p> | |
| <div className="flex items-center justify-between"> | |
| <div className="space-y-3 sm:space-y-4"> | |
| <h3 className="marketing-header text-base font-semibold text-primary sm:text-2xl"> | |
| {story.name} | |
| </h3> | |
| <p className="text-base text-ma-muted-text sm:text-xl"> | |
| {story.role} | |
| </p> | |
| </div> | |
| <div className="flex justify-center gap-[18px]"> | |
| <button | |
| type="button" | |
| aria-label="Previous community story" | |
| onClick={showPreviousStory} | |
| disabled={activeIndex === 0} | |
| className="flex size-[50px] items-center justify-center rounded-full border border-border text-primary transition-colors hover:border-ma-text/20 hover:bg-white disabled:cursor-not-allowed disabled:bg-ma-bg disabled:text-[`#D9D9D9`] disabled:hover:border-border disabled:hover:bg-ma-bg sm:size-[60px]" | |
| > | |
| <ArrowLeft className="size-6" aria-hidden="true" /> | |
| </button> | |
| <button | |
| type="button" | |
| aria-label="Next community story" | |
| onClick={showNextStory} | |
| disabled={activeIndex === stories.length - 1} | |
| className="flex size-[50px] items-center justify-center rounded-full border border-border text-primary transition-colors hover:border-ma-text/20 hover:bg-white disabled:cursor-not-allowed disabled:bg-ma-bg disabled:text-[`#D9D9D9`] disabled:hover:border-border disabled:hover:bg-ma-bg sm:size-[60px]" | |
| > | |
| <ArrowRight className="size-6" aria-hidden="true" /> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ))} | |
| </div> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/marketing/components/stories-from-our-community-section.tsx`
around lines 104 - 158, Update the stories map in the track so navigation
controls are rendered only once outside the per-story slides, or ensure every
inactive slide is marked inert and non-focusable. Preserve the existing
showPreviousStory/showNextStory behavior and active-slide controls while
preventing off-screen buttons from entering the accessibility tree or being
announced repeatedly.
| const visibleCount = isMobile ? 1 : 3 | ||
| const maxIndex = Math.max(0, reviews.length - visibleCount) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Clamp pageIndex when visibleCount changes.
maxIndex depends on visibleCount, and visibleCount depends on isMobile. If the viewport changes from mobile to desktop, visibleCount becomes 3 and maxIndex drops by 2. A stored pageIndex above the new maxIndex is not corrected, so reviews.slice(pageIndex, pageIndex + visibleCount) returns fewer than three cards and the row renders partially empty.
Clamp the value that feeds the slice.
🐛 Proposed fix
const visibleCount = isMobile ? 1 : 3
const maxIndex = Math.max(0, reviews.length - visibleCount)
+ const safePageIndex = Math.min(pageIndex, maxIndex)- const visibleReviews = reviews.slice(pageIndex, pageIndex + visibleCount)
+ const visibleReviews = reviews.slice(
+ safePageIndex,
+ safePageIndex + visibleCount
+ )Also applies to: 74-74
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/marketing/components/testimonials.tsx` around lines 51 - 52,
Clamp pageIndex to maxIndex whenever visibleCount or maxIndex changes, and use
the clamped value as the starting index for reviews.slice. Preserve existing
pagination behavior while ensuring viewport changes cannot leave pageIndex
beyond the new maximum.
| <div className="relative z-10 h-[166px] w-[300px] text-white"> | ||
| <p className="font-heading text-[100px] leading-[60px] font-extrabold"> | ||
| “ | ||
| </p> | ||
| <p className="mt-[-14px] text-[18px] leading-normal font-semibold"> | ||
| ModernAdvocates helped me see a clear path forward. The | ||
| guidance and training resources gave | ||
| </p> | ||
| <p className="mt-5 text-[18px] leading-normal font-semibold"> | ||
| -{review.name} | ||
| </p> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Inspect the reviews data shape used by the testimonials component.
fd -t f 'testimonials.tsx' | xargs rg -n -A 8 'const reviews'Repository: Smartlify07/Modern-Advocates
Length of output: 369
🏁 Script executed:
#!/bin/bash
set -eu
file="$(fd -t f 'testimonials.tsx' | head -n 1)"
printf '%s\n' "FILE: $file"
cat -n "$file" | sed -n '1,230p'
printf '\nREVIEWS REFERENCES:\n'
rg -n -C 3 '\breviews\b|review\.' "$file"Repository: Smartlify07/Modern-Advocates
Length of output: 8557
🏁 Script executed:
#!/bin/bash
set -eu
file="src/features/marketing/components/testimonials.tsx"
python3 - <<'PY'
from pathlib import Path
import re
text = Path("src/features/marketing/components/testimonials.tsx").read_text()
entries = re.findall(r'\{\s*image:\s*"([^"]+)",\s*name:\s*"([^"]+)",\s*\}', text)
quote_literals = re.findall(r'<p className="mt-\[-14px\][^>]*>\s*(.*?)\s*</p>', text, re.S)
print(f"review_entries={len(entries)}")
print(f"review_names={[name for _, name in entries]}")
print(f"review_images={[image for image, _ in entries]}")
print(f"quote_fields_present={bool(re.search(r'\\bquote\\s*:', text))}')
print(f"hardcoded_quote_render_count={len(quote_literals)}")
print("rendered_quote_text=" + " ".join(q.strip() for q in quote_literals))
PY
printf '\nOTHER TESTIMONIAL DATA REFERENCES:\n'
rg -n -i 'ModernAdvocates helped me see a clear path forward|testimonial-[1-4]|quote' src public README.md 2>/dev/null || trueRepository: Smartlify07/Modern-Advocates
Length of output: 394
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re
text = Path("src/features/marketing/components/testimonials.tsx").read_text()
entries = re.findall(r'\{\s*image:\s*"([^"]+)",\s*name:\s*"([^"]+)",\s*\}', text)
quote_literals = re.findall(r'<p className="mt-\[-14px\][^>]*>\s*(.*?)\s*</p>', text, re.S)
quote_field_pattern = re.compile(r'\bquote\s*:')
print(f"review_entries={len(entries)}")
print(f"review_names={[name for _, name in entries]}")
print(f"review_images={[image for image, _ in entries]}")
print(f"quote_fields_present={bool(quote_field_pattern.search(text))}")
print(f"hardcoded_quote_render_count={len(quote_literals)}")
print("rendered_quote_text=" + " ".join(q.strip() for q in quote_literals))
PY
printf '\nOTHER TESTIMONIAL DATA REFERENCES:\n'
rg -n -i 'ModernAdvocates helped me see a clear path forward|testimonial-[1-4]|quote' src public README.md 2>/dev/null || trueRepository: Smartlify07/Modern-Advocates
Length of output: 2729
Bind each testimonial to its review data.
The reviews entries contain no quote field, and all four cards render the same hardcoded quote. Add and render the intended quote for each review. All entries also use "Michael R."; provide distinct names when applicable.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/marketing/components/testimonials.tsx` around lines 143 - 153,
Update the testimonial data and rendering in the testimonials component so each
review entry includes its intended quote and renders that entry’s quote instead
of shared hardcoded text. Replace duplicated “Michael R.” values with the
distinct reviewer names associated with each testimonial, while preserving the
existing card layout and styling.
| </div> | ||
| ) : videoPending ? ( | ||
| <Skeleton className="aspect-video w-full rounded-xl" /> | ||
| <Skeleton className="aspect-video w-full" /> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Preserve square corners on the video loading skeleton.
Skeleton adds rounded-md by default in src/shared/ui/skeleton.tsx. Removing the previous local rounded class therefore leaves rounded corners. Add rounded-none here to match the square unavailable-video placeholders and the loading skeleton in src/features/user-dashboard/components/course-player-shell.tsx:129.
Proposed fix
- <Skeleton className="aspect-video w-full" />
+ <Skeleton className="aspect-video w-full rounded-none" />📝 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.
| <Skeleton className="aspect-video w-full" /> | |
| <Skeleton className="aspect-video w-full rounded-none" /> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/user-dashboard/components/course-player-content.tsx` at line
171, Update the Skeleton in the course player content loading state to include
the rounded-none class, preserving square corners consistently with the
unavailable-video placeholders and the corresponding loading skeleton in
CoursePlayerShell.
What
GradientButton(src/shared/ui/gradient-button.tsx) encapsulating the navbar's blue-to-violet gradient hover-overlay effect; supports both button andhref/link usage.AnimatePresence mode="wait", directional slide variants).Intervia next/font and applied.marketing-header/.marketing-headlineutilities across marketing headings.qualities: [100]to image optimizer config.Verify
npx tsc --noEmit: cleannpm run build: passesSummary by CodeRabbit
New Features
Improvements