Skip to content

Commit 2c5c81e

Browse files
committed
Refactor image handling to use speakerImageUrl for consistent image processing across components
1 parent d7886a4 commit 2c5c81e

8 files changed

Lines changed: 60 additions & 45 deletions

File tree

src/app/(main)/speaker/[slug]/opengraph-image.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react'
22
import { ImageResponse } from 'next/og'
3-
import { sanityImage } from '@/lib/sanity/client'
3+
import { speakerImageUrl } from '@/lib/sanity/client'
44
import { STYLES, OG_IMAGE_SIZE } from '@/lib/og/styles'
55
import {
66
createSvgDataUrl,
@@ -259,8 +259,12 @@ export default async function Image({
259259
}
260260

261261
const primaryTalk = talks[0]
262-
const speakerImageUrl = speakerData.image
263-
? sanityImage(speakerData.image).width(500).height(500).fit('crop').url()
262+
const speakerImgUrl = speakerData.image
263+
? speakerImageUrl(speakerData.image, {
264+
width: 500,
265+
height: 500,
266+
fit: 'crop',
267+
})
264268
: null
265269

266270
const ingressSponsors = conferenceData.sponsors.filter(
@@ -442,7 +446,7 @@ export default async function Image({
442446
zIndex: 1,
443447
}}
444448
>
445-
<SpeakerImage imageUrl={speakerImageUrl} name={speakerData.name} />
449+
<SpeakerImage imageUrl={speakerImgUrl} name={speakerData.name} />
446450

447451
<h2
448452
style={{

src/app/(main)/speaker/[slug]/page.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { Button } from '@/components/Button'
1111
import { BackLink } from '@/components/BackButton'
1212
import { ShowMore } from '@/components/ShowMore'
1313
import { UserIcon } from '@heroicons/react/24/solid'
14-
import { sanityImage } from '@/lib/sanity/client'
14+
import { speakerImageUrl } from '@/lib/sanity/client'
1515
import { PortableText } from '@portabletext/react'
1616
import { portableTextComponents } from '@/lib/portabletext/components'
1717
import { getConferenceForDomain } from '@/lib/conference/sanity'
@@ -151,11 +151,7 @@ async function CachedSpeakerContent({
151151
<div className="mb-6 flex justify-center lg:justify-start">
152152
{speaker.image ? (
153153
<img
154-
src={sanityImage(speaker.image)
155-
.width(400)
156-
.height(400)
157-
.fit('crop')
158-
.url()}
154+
src={speakerImageUrl(speaker.image)}
159155
alt={speaker.name}
160156
className="h-48 w-48 rounded-full object-cover shadow-lg ring-4 ring-white lg:h-64 lg:w-64 dark:ring-gray-700"
161157
/>

src/components/SpeakerProfilePreview.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
languages,
1515
} from '@/lib/proposal/types'
1616
import { Topic } from '@/lib/topic/types'
17-
import { sanityImage } from '@/lib/sanity/client'
17+
import { speakerImageUrl } from '@/lib/sanity/client'
1818
import { hasBlueskySocial } from '@/lib/bluesky/utils'
1919
import { ShowMore } from '@/components/ShowMore'
2020
import { SpeakerAvatars } from '@/components/SpeakerAvatars'
@@ -69,13 +69,7 @@ export default function SpeakerProfilePreview({
6969
<div className="sticky top-8 space-y-6">
7070
{speaker.image ? (
7171
<img
72-
src={
73-
sanityImage(speaker.image)
74-
.width(400)
75-
.height(400)
76-
.fit('crop')
77-
.url() || undefined
78-
}
72+
src={speakerImageUrl(speaker.image)}
7973
alt={speaker.name}
8074
className="mx-auto h-48 w-48 rounded-full object-cover shadow-lg ring-4 ring-white lg:mx-0 lg:h-64 lg:w-64 dark:ring-gray-700"
8175
/>

src/components/admin/ProposalDetail.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { extractSpeakersFromProposal } from '@/lib/proposal/utils'
2323
import { Flags } from '@/lib/speaker/types'
2424
import { Topic } from '@/lib/topic/types'
2525
import { formatDateSafe, formatDateTimeSafe } from '@/lib/time'
26-
import { sanityImage } from '@/lib/sanity/client'
26+
import { speakerImageUrl } from '@/lib/sanity/client'
2727
import { getStatusBadgeConfig } from '@/lib/proposal/ui'
2828
import { portableTextComponents } from '@/lib/portabletext/components'
2929
import { iconForLink } from '@/components/SocialIcons'
@@ -75,11 +75,11 @@ function SpeakerCard({ speaker, requiresTravelFunding }: SpeakerCardProps) {
7575
<div className="float-left mr-4 mb-2">
7676
{speaker.image ? (
7777
<img
78-
src={sanityImage(speaker.image)
79-
.width(128)
80-
.height(128)
81-
.fit('crop')
82-
.url()}
78+
src={speakerImageUrl(speaker.image, {
79+
width: 128,
80+
height: 128,
81+
fit: 'crop',
82+
})}
8383
alt={speaker.name}
8484
width={64}
8585
height={64}

src/components/admin/ProposalReviewList.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { StarIcon } from '@heroicons/react/24/solid'
44
import { Review } from '@/lib/review/types'
55
import { Speaker } from '@/lib/speaker/types'
6-
import { sanityImage } from '@/lib/sanity/client'
6+
import { speakerImageUrl } from '@/lib/sanity/client'
77
import { formatDateSafe } from '@/lib/time'
88

99
interface ProposalReviewListProps {
@@ -80,11 +80,11 @@ export function ProposalReviewList({
8080
<div className="shrink-0">
8181
{reviewer?.image ? (
8282
<img
83-
src={sanityImage(reviewer.image)
84-
.width(64)
85-
.height(64)
86-
.fit('crop')
87-
.url()}
83+
src={speakerImageUrl(reviewer.image, {
84+
width: 64,
85+
height: 64,
86+
fit: 'crop',
87+
})}
8888
alt={reviewer.name || 'Reviewer'}
8989
width={32}
9090
height={32}

src/components/badge/BadgeDisplay.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Link from 'next/link'
1515
import type { BadgeRecord } from '@/lib/badge/types'
1616
import type { Speaker } from '@/lib/speaker/types'
1717
import type { Conference } from '@/lib/conference/types'
18-
import { sanityImage } from '@/lib/sanity/client'
18+
import { speakerImageUrl } from '@/lib/sanity/client'
1919
import { MissingAvatar } from '@/components/common/MissingAvatar'
2020
import { CloudNativePattern } from '@/components/CloudNativePattern'
2121
import { BlueskyIcon, LinkedInIcon } from '@/components/SocialIcons'
@@ -85,8 +85,8 @@ export function BadgeDisplay({
8585
})
8686
}
8787

88-
const speakerImageUrl = speaker.image
89-
? sanityImage(speaker.image).width(400).height(400).fit('crop').url()
88+
const speakerImgUrl = speaker.image
89+
? speakerImageUrl(speaker.image)
9090
: undefined
9191

9292
// Get first talk for evidence (if available)
@@ -150,9 +150,9 @@ export function BadgeDisplay({
150150

151151
{/* Recipient Info */}
152152
<div className="mb-6 flex flex-col items-center gap-4">
153-
{speakerImageUrl ? (
153+
{speakerImgUrl ? (
154154
<img
155-
src={speakerImageUrl}
155+
src={speakerImgUrl}
156156
alt={speaker.name}
157157
className="h-24 w-24 rounded-full border-4 border-white/30 object-cover shadow-xl"
158158
/>

src/lib/auth.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import LinkedIn from 'next-auth/providers/linkedin'
44
import type { NextAuthConfig, Session, User } from 'next-auth'
55
import { NextRequest } from 'next/server'
66
import { getOrCreateSpeaker } from '@/lib/speaker/sanity'
7-
import { sanityImage } from '@/lib/sanity/client'
7+
import { speakerImageUrl } from '@/lib/sanity/client'
88
import { AppEnvironment } from '@/lib/environment/config'
99

1010
export interface NextAuthRequest extends NextRequest {
@@ -95,15 +95,11 @@ const config = {
9595
}
9696

9797
if (speaker.image) {
98-
if (typeof speaker.image === 'object') {
99-
token.picture = sanityImage(speaker.image)
100-
.width(192)
101-
.height(192)
102-
.fit('crop')
103-
.url()
104-
} else if (typeof speaker.image === 'string') {
105-
token.picture = speaker.image
106-
}
98+
token.picture = speakerImageUrl(speaker.image, {
99+
width: 192,
100+
height: 192,
101+
fit: 'crop',
102+
})
107103
}
108104

109105
token.account = account

src/lib/sanity/client.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,28 @@ const builder = createImageUrlBuilder(clientReadCached)
3535
export function sanityImage(source: SanityImageSource) {
3636
return builder.image(source)
3737
}
38+
39+
const SANITY_CDN_PREFIX = 'https://cdn.sanity.io/'
40+
41+
/**
42+
* Resolves a speaker image URL for display. Handles both Sanity CDN URLs
43+
* (from uploaded images) and external URLs (from OAuth providers like GitHub/LinkedIn).
44+
* Only Sanity URLs are passed through the image builder for transforms.
45+
*/
46+
export function speakerImageUrl(
47+
image: string,
48+
opts: { width: number; height: number; fit?: 'crop' | 'max' } = {
49+
width: 400,
50+
height: 400,
51+
fit: 'crop',
52+
},
53+
): string {
54+
if (image.startsWith(SANITY_CDN_PREFIX)) {
55+
return sanityImage(image)
56+
.width(opts.width)
57+
.height(opts.height)
58+
.fit(opts.fit ?? 'crop')
59+
.url()
60+
}
61+
return image
62+
}

0 commit comments

Comments
 (0)