feat(web): merge DigitalOcean release announcement updates (#860)

* feat(web): announce DigitalOcean acquisition across sites

* fix(web): make blog routes resilient without Sanity config

* fix(web): add mobile arrow cue to announcement banner

* fix(web): point acquisition links to announcement post
This commit is contained in:
Musa 2026-04-02 09:03:52 -04:00 committed by GitHub
parent 0857cfafbf
commit 39b430d74b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 3156 additions and 701 deletions

View file

@ -39,6 +39,10 @@ function loadFont(fileName: string, baseUrl: string) {
}
async function getBlogPost(slug: string) {
if (!client) {
return null;
}
const query = `*[_type == "blog" && slug.current == $slug && published == true][0] {
_id,
title,
@ -53,8 +57,13 @@ async function getBlogPost(slug: string) {
}
}`;
const post = await client.fetch(query, { slug });
return post;
try {
const post = await client.fetch(query, { slug });
return post;
} catch (error) {
console.error("Error fetching blog post for OG image:", error);
return null;
}
}
function formatDate(dateString: string): string {

View file

@ -17,6 +17,10 @@ interface BlogPost {
}
async function getBlogPost(slug: string): Promise<BlogPost | null> {
if (!client) {
return null;
}
const query = `*[_type == "blog" && slug.current == $slug && published == true][0] {
_id,
title,
@ -26,8 +30,13 @@ async function getBlogPost(slug: string): Promise<BlogPost | null> {
author
}`;
const post = await client.fetch(query, { slug });
return post || null;
try {
const post = await client.fetch(query, { slug });
return post || null;
} catch (error) {
console.error("Error fetching blog post metadata:", error);
return null;
}
}
export async function generateMetadata({

View file

@ -25,6 +25,10 @@ interface BlogPost {
}
async function getBlogPost(slug: string): Promise<BlogPost | null> {
if (!client) {
return null;
}
const query = `*[_type == "blog" && slug.current == $slug && published == true][0] {
_id,
title,
@ -51,17 +55,31 @@ async function getBlogPost(slug: string): Promise<BlogPost | null> {
author
}`;
const post = await client.fetch(query, { slug });
return post || null;
try {
const post = await client.fetch(query, { slug });
return post || null;
} catch (error) {
console.error("Error fetching blog post:", error);
return null;
}
}
async function getAllBlogSlugs(): Promise<string[]> {
if (!client) {
return [];
}
const query = `*[_type == "blog" && published == true] {
"slug": slug.current
}`;
const posts = await client.fetch(query);
return posts.map((post: { slug: string }) => post.slug);
try {
const posts = await client.fetch(query);
return posts.map((post: { slug: string }) => post.slug);
} catch (error) {
console.error("Error fetching blog slugs:", error);
return [];
}
}
export async function generateStaticParams() {

View file

@ -44,6 +44,10 @@ function formatDate(dateString: string): string {
}
async function getBlogPosts(): Promise<BlogPost[]> {
if (!client) {
return [];
}
const query = `*[_type == "blog" && published == true] | order(publishedAt desc) {
_id,
title,
@ -58,7 +62,12 @@ async function getBlogPosts(): Promise<BlogPost[]> {
featured
}`;
return await client.fetch(query);
try {
return await client.fetch(query);
} catch (error) {
console.error("Error fetching blog posts:", error);
return [];
}
}
export default async function BlogPage() {

View file

@ -1,4 +1,6 @@
import type { Metadata } from "next";
import { ArrowRightIcon } from "@heroicons/react/16/solid";
import Link from "next/link";
import Script from "next/script";
import "@katanemo/shared-styles/globals.css";
import { Analytics } from "@vercel/analytics/next";
@ -35,6 +37,27 @@ export default function RootLayout({
gtag('config', 'G-ML7B1X9HY2');
`}
</Script>
<Link
href="https://digitalocean.com/blog/digitalocean-acquires-katanemo-labs-inc"
target="_blank"
rel="noopener noreferrer"
className="block w-full bg-[#7780D9] py-3 text-white transition-opacity"
>
<div className="mx-auto flex max-w-[85rem] items-center justify-center gap-4 px-6 text-center md:justify-between md:text-left lg:px-8">
<span className="w-full text-xs font-medium leading-snug md:w-auto md:text-base flex items-center">
DigitalOcean acquires Katanemo Labs, Inc. to accelerate AI
development
<ArrowRightIcon
aria-hidden
className="ml-1 inline-block h-3 w-3 align-[-1px] text-white/90 md:hidden"
/>
</span>
<span className="hidden shrink-0 items-center gap-1 text-base font-medium tracking-[-0.989px] font-mono leading-snug opacity-70 transition-opacity hover:opacity-100 md:inline-flex">
Read the announcement
<ArrowRightIcon aria-hidden className="h-3.5 w-3.5 text-white/70" />
</span>
</div>
</Link>
<ConditionalLayout>{children}</ConditionalLayout>
<Analytics />
</body>

View file

@ -10,6 +10,10 @@ interface BlogPost {
}
async function getBlogPosts(): Promise<BlogPost[]> {
if (!client) {
return [];
}
const query = `*[_type == "blog" && published == true] | order(publishedAt desc) {
slug,
publishedAt,

View file

@ -6,15 +6,22 @@ const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID;
const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET;
const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION;
export const client = createClient({
projectId,
dataset,
apiVersion,
useCdn: true, // Set to false if statically generating pages, using ISR or using the on-demand revalidation API
});
export const hasSanityConfig = Boolean(projectId && dataset && apiVersion);
const builder = imageUrlBuilder(client);
export const client = hasSanityConfig
? createClient({
projectId,
dataset,
apiVersion,
useCdn: true, // Set to false if statically generating pages, using ISR or using the on-demand revalidation API
})
: null;
const builder = client ? imageUrlBuilder(client) : null;
export function urlFor(source: SanityImageSource) {
if (!builder) {
throw new Error("Sanity client is not configured.");
}
return builder.image(source);
}