Skip to content
Open
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
4 changes: 4 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ const nextConfig = {
's3.amazonaws.com',
// Facebook OAuth user avatars
'platform-lookaside.fbsbx.com',
// Gravatar default avatars
'www.gravatar.com',
// Sanity CMS hosted assets
'cdn.sanity.io',
],
},
webpack: (webpackConfig) => {
Expand Down
17 changes: 15 additions & 2 deletions src/components/Sanity/Blocks/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import classNames from 'classnames';
import Image from 'next/image';

import styles from './Blocks.module.scss';

Expand Down Expand Up @@ -33,13 +34,25 @@ const PageBlocks: React.FC<Props> = ({ page }) => {
const { _type: childElementType, _key: childElementKey, text } = childElement;
if (childElementType === 'inlineImage') {
const { asset, _key: imageKey } = childElement;
const width = asset?.metadata?.dimensions?.width ?? 800;
const height = asset?.metadata?.dimensions?.height ?? 600;
const imageUrl = getImageUrl(asset);
if (!imageUrl) {
return;
}
elementBlocks.push(
<div
key={`${bodyElementKey}-${childElementKey}-${imageKey}`}
className={styles.imageContainer}
>
{/* eslint-disable-next-line @next/next/no-img-element, jsx-a11y/alt-text */}
<img className={styles.image} src={getImageUrl(asset)} />
<Image
className={styles.image}
src={imageUrl}
alt=""
width={width}
height={height}
sizes="(max-width: 768px) 100vw, 800px"
/>
Comment on lines +48 to +55
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Provide meaningful alt text for accessibility.

The empty alt="" violates accessibility guidelines. Sanity assets may include descriptive metadata; if asset.alt or a similar field exists, use it. Otherwise, derive descriptive text from context (e.g., the surrounding content or page title).

Apply this diff if asset.alt is available:

             <Image
               className={styles.image}
               src={imageUrl}
-              alt=""
+              alt={asset.alt || 'Inline image'}
               width={width}
               height={height}
               sizes="(max-width: 768px) 100vw, 800px"
             />

Based on learnings.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/components/Sanity/Blocks/index.tsx around lines 48 to 55, the Image
component currently sets alt="" which breaks accessibility; update the code to
pass a meaningful alt string: use the Sanity asset's alt field (e.g. asset.alt
or image.asset?.alt) if present, otherwise derive a fallback (caption, block
heading, or page title) and use that as the alt prop; ensure you never leave alt
empty and keep it as an empty string only for purely decorative images —
otherwise supply the descriptive fallback.

</div>,
);
return;
Expand Down
14 changes: 11 additions & 3 deletions src/components/Sanity/Page/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';

import classNames from 'classnames';
import Image from 'next/image';
import useTranslation from 'next-translate/useTranslation';

import PageBlocks from '../Blocks';
Expand All @@ -26,6 +27,7 @@ const Page: React.FC<Props> = ({ page, isIndividualPage = false }) => {
year: 'numeric',
});
const pageTitle = <p className={classNames(styles.title, styles.bold)}>{page.title}</p>;
const imageUrl = page.mainPhoto ? getImageUrl(page.mainPhoto) : '';
return (
<div key={page.id} className={styles.pageContainer}>
<div className={styles.headerSection}>
Expand Down Expand Up @@ -55,10 +57,16 @@ const Page: React.FC<Props> = ({ page, isIndividualPage = false }) => {
{pageTitle}
</Link>
{page.summary}
{page.mainPhoto && (
{page.mainPhoto && imageUrl && (
<div className={styles.imageContainer}>
{/* eslint-disable-next-line @next/next/no-img-element, jsx-a11y/alt-text */}
<img className={styles.image} src={getImageUrl(page.mainPhoto)} />
<Image
className={styles.image}
src={imageUrl}
alt=""
width={page.mainPhoto?.metadata?.dimensions?.width ?? 800}
height={page.mainPhoto?.metadata?.dimensions?.height ?? 600}
sizes="(max-width: 768px) 100vw, 800px"
/>
Comment on lines +62 to +69
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Provide meaningful alt text for accessibility.

The empty alt="" violates accessibility guidelines. Sanity's page schema likely includes descriptive metadata for mainPhoto. Use page.mainPhoto.alt or derive descriptive text from page.title.

Apply this diff if page.mainPhoto.alt is available:

                 <Image
                   className={styles.image}
                   src={imageUrl}
-                  alt=""
+                  alt={page.mainPhoto.alt || page.title || 'Page hero image'}
                   width={page.mainPhoto?.metadata?.dimensions?.width ?? 800}
                   height={page.mainPhoto?.metadata?.dimensions?.height ?? 600}
                   sizes="(max-width: 768px) 100vw, 800px"
                 />

Based on learnings.

📝 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
<Image
className={styles.image}
src={imageUrl}
alt=""
width={page.mainPhoto?.metadata?.dimensions?.width ?? 800}
height={page.mainPhoto?.metadata?.dimensions?.height ?? 600}
sizes="(max-width: 768px) 100vw, 800px"
/>
<Image
className={styles.image}
src={imageUrl}
alt={page.mainPhoto?.alt || page.title || 'Page hero image'}
width={page.mainPhoto?.metadata?.dimensions?.width ?? 800}
height={page.mainPhoto?.metadata?.dimensions?.height ?? 600}
sizes="(max-width: 768px) 100vw, 800px"
/>
🤖 Prompt for AI Agents
In src/components/Sanity/Page/index.tsx around lines 62 to 69, the Image
component currently uses an empty alt (alt="") which breaks accessibility;
replace it with a meaningful value by using page.mainPhoto?.alt when present,
otherwise fall back to a derived string such as `Main photo for ${page.title ??
'page'}` (or a shorter descriptive fallback), ensuring you safely handle
undefined page/title so the prop is always a string.

</div>
)}
</>
Expand Down
21 changes: 10 additions & 11 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { DirectionProvider } from '@radix-ui/react-direction';
import { TooltipProvider } from '@radix-ui/react-tooltip';
import Head from 'next/head';
import { useRouter } from 'next/router';
import Script from 'next/script';

import AppContent from '@/components/AppContent/AppContent';
import FontPreLoader from '@/components/Fonts/FontPreLoader';
Expand Down Expand Up @@ -32,6 +33,12 @@ function MyApp({ Component, pageProps }: { Component: any; pageProps: any }) {
const { locale } = router;
const resolvedLocale = locale ?? 'en';
const languageDirection = getDir(resolvedLocale);
const buildInfo = {
date: process.env.NEXT_PUBLIC_BUILD_DATE || new Date().toISOString(),
hash: process.env.NEXT_PUBLIC_COMMIT_HASH || 'development',
version: process.env.NEXT_PUBLIC_APP_VERSION || '',
env: process.env.NEXT_PUBLIC_APP_ENV,
};

useEffect(() => {
document.documentElement.dir = languageDirection;
Expand All @@ -50,18 +57,10 @@ function MyApp({ Component, pageProps }: { Component: any; pageProps: any }) {
<link rel="apple-touch-icon" sizes="192x192" href="/images/logo/[email protected]" />
<link rel="manifest" href="/manifest.json" />
<link rel="preconnect" href={API_HOST} />
<script
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: `window.__BUILD_INFO__ = {
date: "${process.env.NEXT_PUBLIC_BUILD_DATE || new Date().toISOString()}",
hash: "${process.env.NEXT_PUBLIC_COMMIT_HASH || 'development'}",
version: "${process.env.NEXT_PUBLIC_APP_VERSION || ''}",
env: "${process.env.NEXT_PUBLIC_APP_ENV}"
}`,
}}
/>
</Head>
<Script id="build-info" strategy="beforeInteractive">
{`window.__BUILD_INFO__ = ${JSON.stringify(buildInfo)};`}
</Script>
<FontPreLoader locale={resolvedLocale} />
<DirectionProvider dir={languageDirection}>
<TooltipProvider>
Expand Down
14 changes: 10 additions & 4 deletions src/pages/profile.module.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
@use "src/styles/utility";
@use 'src/styles/utility';

.container {
margin-block-start: var(--spacing-medium);
min-height: 80vh;
min-block-size: 80vh;
}

.profileContainer {
Expand All @@ -13,10 +13,16 @@

.profilePicture {
--profile-picture-size: calc(5 * var(--spacing-medium));
width: var(--profile-picture-size);
height: var(--profile-picture-size);
inline-size: var(--profile-picture-size);
block-size: var(--profile-picture-size);
background-color: var(--shade-4);
border-radius: var(--border-radius-circle);
position: relative;
overflow: hidden;
}

.profilePictureImage {
object-fit: cover;
}

.profileInfoContainer {
Expand Down
11 changes: 9 additions & 2 deletions src/pages/profile.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable max-lines */
import classNames from 'classnames';
import { NextPage, GetStaticProps } from 'next';
import Image from 'next/image';
import useTranslation from 'next-translate/useTranslation';

import layoutStyle from './index.module.scss';
Expand Down Expand Up @@ -74,8 +75,14 @@ const ProfilePage: NextPage<Props> = () => {
<div className={classNames(layoutStyle.flowItem)}>
<div className={styles.profileContainer}>
<div className={styles.profilePicture}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img className={styles.profilePicture} alt="avatar" src={photoUrl} />
<Image
src={photoUrl}
alt="avatar"
fill
sizes="(max-width: 768px) 120px, 160px"
className={styles.profilePictureImage}
priority
/>
</div>
{isLoading ? (
profileSkeletonInfoSkeleton
Expand Down
17 changes: 11 additions & 6 deletions src/pages/reciters/[reciterId]/[chapterId].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useSelector } from '@xstate/react';
import classNames from 'classnames';
import clipboardCopy from 'clipboard-copy';
import { GetStaticPaths, GetStaticProps } from 'next';
import Image from 'next/image';
import { useRouter } from 'next/router';
import useTranslation from 'next-translate/useTranslation';

Expand Down Expand Up @@ -114,12 +115,16 @@ const RecitationPage = ({ selectedReciter, selectedChapter }: ShareRecitationPag
/>
<div className={classNames(layoutStyle.flow)}>
<div className={classNames(layoutStyle.flowItem, styles.container)}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
className={styles.reciterImage}
alt={selectedReciter.translatedName.name}
src={makeCDNUrl(selectedReciter.profilePicture)}
/>
<div className={styles.reciterImage}>
<Image
src={makeCDNUrl(selectedReciter.profilePicture)}
alt={selectedReciter.translatedName.name}
fill
sizes="(max-width: 768px) 80vw, 320px"
className={styles.reciterImageContent}
priority
/>
</div>
<div>
<div className={styles.chapterName}>
{/* eslint-disable-next-line i18next/no-literal-string */}
Expand Down
17 changes: 11 additions & 6 deletions src/pages/reciters/[reciterId]/chapterId.module.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@use "src/styles/breakpoints";
@use 'src/styles/breakpoints';

.container {
display: flex;
Expand All @@ -8,20 +8,25 @@
}

.reciterImage {
width: 80%;
max-width: calc(20 * var(--spacing-medium));
border-radius: var(--border-radius-circle);
inline-size: 80%;
max-inline-size: calc(20 * var(--spacing-medium));
margin-block-end: var(--spacing-medium);
margin-inline: auto;
aspect-ratio: 1/1;
border-radius: var(--border-radius-circle);
position: relative;
overflow: hidden;
}

.reciterImageContent {
object-fit: cover;
}

.actionsContainer {
display: flex;
flex-direction: column;
width: 80%;
max-width: calc(30 * var(--spacing-medium));
inline-size: 80%;
max-inline-size: calc(30 * var(--spacing-medium));
}

.playButton {
Expand Down