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
233 changes: 120 additions & 113 deletions app/api/og/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,151 +3,158 @@ import { NextRequest } from 'next/server';

export const runtime = 'edge';

const OG_IMAGE_SIZE = { width: 1200, height: 630 };

export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const title = searchParams.get('title');
const description = searchParams.get('description');
const author = searchParams.get('author');
const type = searchParams.get('type') || 'article';
const image = searchParams.get('image');

if (image) {
return new ImageResponse(
<div style={{ height: '100%', width: '100%', display: 'flex', backgroundColor: '#ffffff' }}>
<img src={image} alt="" style={{ width: '100%', height: '100%', objectFit: 'contain' }} />
</div>,
OG_IMAGE_SIZE
);
}

return new ImageResponse(
(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#ffffff',
padding: '60px',
fontFamily: 'Inter, system-ui, sans-serif',
}}
>
{/* Background pattern */}
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)',
opacity: 0.1,
}}
/>

{/* Logo and brand */}
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#ffffff',
padding: '60px',
fontFamily: 'Inter, system-ui, sans-serif',
marginBottom: '40px',
position: 'relative',
zIndex: 1,
}}
>
{/* Background pattern */}
<div
<img
src="https://researchhub.com/logo_blue.png"
alt="ResearchHub"
width={480}
height={120}
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)',
opacity: 0.1,
display: 'block',
background: '#fff',
objectFit: 'contain',
}}
/>
</div>

{/* Logo and brand */}
{/* Content type badge */}
{type && (
<div
style={{
display: 'flex',
alignItems: 'center',
marginBottom: '40px',
position: 'relative',
zIndex: 1,
backgroundColor: '#3b82f6',
color: 'white',
padding: '8px 16px',
borderRadius: '20px',
fontSize: '14px',
fontWeight: '500',
marginBottom: '30px',
textTransform: 'uppercase',
letterSpacing: '0.5px',
}}
>
<img
src="https://researchhub.com/logo_blue.png"
alt="ResearchHub"
width={480}
height={120}
style={{
display: 'block',
background: '#fff',
objectFit: 'contain',
}}
/>
{type}
</div>
)}

{/* Content type badge */}
{type && (
<div
style={{
backgroundColor: '#3b82f6',
color: 'white',
padding: '8px 16px',
borderRadius: '20px',
fontSize: '14px',
fontWeight: '500',
marginBottom: '30px',
textTransform: 'uppercase',
letterSpacing: '0.5px',
}}
>
{type}
</div>
)}

{/* Title */}
{title && (
<h1
style={{
fontSize: '48px',
fontWeight: 'bold',
textAlign: 'center',
color: '#1f2937',
margin: '0 0 20px 0',
lineHeight: 1.2,
maxWidth: '900px',
}}
>
{title}
</h1>
)}
{/* Title */}
{title && (
<h1
style={{
fontSize: '48px',
fontWeight: 'bold',
textAlign: 'center',
color: '#1f2937',
margin: '0 0 20px 0',
lineHeight: 1.2,
maxWidth: '900px',
}}
>
{title}
</h1>
)}

{/* Description */}
{description && (
<p
style={{
fontSize: '24px',
textAlign: 'center',
color: '#6b7280',
margin: '0 0 30px 0',
lineHeight: 1.4,
maxWidth: '800px',
}}
>
{description}
</p>
)}
{/* Description */}
{description && (
<p
style={{
fontSize: '24px',
textAlign: 'center',
color: '#6b7280',
margin: '0 0 30px 0',
lineHeight: 1.4,
maxWidth: '800px',
}}
>
{description}
</p>
)}

{/* Author */}
{author && (
{/* Author */}
{author && (
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '12px',
fontSize: '18px',
color: '#9ca3af',
}}
>
<div
style={{
width: '32px',
height: '32px',
backgroundColor: '#e5e7eb',
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
gap: '12px',
fontSize: '18px',
color: '#9ca3af',
justifyContent: 'center',
fontSize: '14px',
fontWeight: '600',
color: '#6b7280',
}}
>
<div
style={{
width: '32px',
height: '32px',
backgroundColor: '#e5e7eb',
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '14px',
fontWeight: '600',
color: '#6b7280',
}}
>
{author.charAt(0).toUpperCase()}
</div>
<span>By {author}</span>
{author.charAt(0).toUpperCase()}
</div>
)}
</div>
),
{
width: 1200,
height: 630,
}
<span>By {author}</span>
</div>
)}
</div>,
OG_IMAGE_SIZE
);
}
5 changes: 4 additions & 1 deletion app/grant/[id]/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { PostService } from '@/services/post.service';
import { getWorkMetadata } from '@/lib/metadata-helpers';
import { SITE_CONFIG } from '@/lib/metadata';
import { GrantContentSwitcher } from '@/components/Funding/GrantContentSwitcher';

interface Props {
Expand All @@ -25,8 +26,10 @@ async function getGrant(id: string) {
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { id, slug } = await params;
const grant = await getGrant(id);
const shareImage =
grant.image && `${SITE_CONFIG.url}/api/og?image=${encodeURIComponent(grant.image)}`;
return getWorkMetadata({
work: grant,
work: { ...grant, image: shareImage },
url: `/grant/${id}/${slug}`,
});
}
Expand Down
2 changes: 1 addition & 1 deletion components/Feed/items/FeedItemGrantWithApplicants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export const FeedItemGrantWithApplicants: FC<FeedItemGrantWithApplicantsProps> =
src={content.previewImage}
alt={content.title}
fill
className="object-cover transition-transform duration-300 group-hover:scale-105"
className="object-contain object-top sm:object-cover sm:object-center transition-transform duration-300 group-hover:scale-105"
sizes="(max-width: 768px) 100vw, 660px"
/>
) : (
Expand Down
8 changes: 7 additions & 1 deletion components/Funding/GrantDetailsInline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ export function GrantDetailsInline({ content, imageUrl }: GrantDetailsInlineProp
<div className="py-6">
{imageUrl && (
<div className="relative w-full h-[200px] sm:h-[280px] rounded-xl overflow-hidden bg-gray-100 mb-6">
<Image src={imageUrl} alt="" fill className="object-cover" sizes="860px" />
<Image
src={imageUrl}
alt=""
fill
className="object-contain sm:object-cover"
sizes="860px"
/>
</div>
)}

Expand Down
Loading