mirror of
https://github.com/katanemo/plano.git
synced 2026-04-25 00:36:34 +02:00
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:
parent
0857cfafbf
commit
39b430d74b
15 changed files with 3156 additions and 701 deletions
|
|
@ -12,6 +12,7 @@
|
|||
"clean": "rm -rf .next"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@katanemo/shared-styles": "*",
|
||||
"@katanemo/ui": "*",
|
||||
"next": "^16.1.6",
|
||||
|
|
|
|||
|
|
@ -66,7 +66,9 @@ export default function RootLayout({
|
|||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={`${ibmPlexSans.variable} antialiased text-white`}>
|
||||
<body
|
||||
className={`${ibmPlexSans.variable} overflow-hidden antialiased text-white`}
|
||||
>
|
||||
{/* Google tag (gtag.js) */}
|
||||
<Script
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-RLD5BDNW5N"
|
||||
|
|
@ -80,7 +82,9 @@ export default function RootLayout({
|
|||
gtag('config', 'G-RLD5BDNW5N');
|
||||
`}
|
||||
</Script>
|
||||
<div className="min-h-screen">{children}</div>
|
||||
<div className="h-screen overflow-hidden">
|
||||
{children}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,23 @@
|
|||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import LogoSlider from "../components/LogoSlider";
|
||||
import { ArrowRightIcon } from "@heroicons/react/16/solid";
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<main className="relative flex min-h-screen items-center justify-center overflow-hidden px-6 pt-12 pb-16 font-sans sm:pt-20 lg:items-start lg:justify-start lg:pt-24">
|
||||
<main className="relative flex h-full items-center justify-center overflow-hidden px-6 pt-12 pb-16 font-sans sm:pt-20 lg:items-start lg:justify-start lg:pt-24">
|
||||
<div className="relative mx-auto w-full max-w-6xl flex flex-col items-center justify-center text-left lg:items-start lg:justify-start">
|
||||
<Link
|
||||
href="https://digitalocean.com/blog/digitalocean-acquires-katanemo-labs-inc"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="mb-7 inline-flex max-w-[20rem] items-center gap-1 self-start rounded-full border border-[#22A875]/30 bg-[#22A875]/30 px-2.5 py-1 text-left text-[12px] leading-tight font-medium text-white transition-opacity hover:opacity-90 lg:hidden"
|
||||
>
|
||||
<span>
|
||||
DigitalOcean acquires Katanemo Labs, Inc.
|
||||
</span>
|
||||
<ArrowRightIcon aria-hidden className="h-3 w-3 shrink-0 text-white/90" />
|
||||
</Link>
|
||||
<div className="pointer-events-none mb-6 w-full self-start lg:hidden">
|
||||
<Image
|
||||
src="/KatanemoLogo.svg"
|
||||
|
|
@ -17,6 +29,20 @@ export default function HomePage() {
|
|||
/>
|
||||
</div>
|
||||
<div className="relative z-10 max-w-xl sm:max-w-2xl lg:max-w-2xl xl:max-w-8xl lg:pr-[26vw] xl:pr-[2vw] sm:right-0 md:right-0 lg:right-0 xl:right-20 2xl:right-50 sm:mt-36 mt-0">
|
||||
<Link
|
||||
href="https://digitalocean.com/blog/digitalocean-acquires-katanemo-labs-inc"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="mb-4 hidden max-w-full items-center gap-2 rounded-full border border-[#22A875]/70 bg-[#22A875]/50 px-4 py-1 text-left text-sm font-medium text-white transition-opacity hover:opacity-90 lg:inline-flex"
|
||||
>
|
||||
<span>
|
||||
DigitalOcean acquires Katanemo Labs, Inc.
|
||||
</span>
|
||||
<ArrowRightIcon
|
||||
aria-hidden
|
||||
className="h-4 w-4 shrink-0 text-white/90"
|
||||
/>
|
||||
</Link>
|
||||
<h1 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-sans font-medium leading-tight tracking-tight text-white">
|
||||
Forward-deployed AI infrastructure engineers.
|
||||
</h1>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"migrate:blogs": "tsx scripts/migrate-blogs.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@katanemo/shared-styles": "*",
|
||||
"@katanemo/ui": "*",
|
||||
"@portabletext/react": "^5.0.0",
|
||||
|
|
@ -32,8 +33,8 @@
|
|||
"next": "^16.1.6",
|
||||
"next-sanity": "^11.6.9",
|
||||
"papaparse": "^5.5.3",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-syntax-highlighter": "^16.1.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue