Skip to content
Merged
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
474 changes: 262 additions & 212 deletions AGENTS.md

Large diffs are not rendered by default.

472 changes: 261 additions & 211 deletions CLAUDE.md

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions src/app/(public)/about/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
import type { Metadata } from "next";
import Link from "next/link";
import Button from "@/components/ui/Button";

export const revalidate = 3600;

const title = "About — CodedDevs Technology LTD";
const description =
"We build AI-first software products for African markets. Learn about our mission and approach.";

export const metadata: Metadata = {
title,
description,
openGraph: {
title,
description,
url: "https://codeddevs.com/about",
siteName: "CodedDevs Technology LTD",
type: "website",
},
twitter: {
card: "summary_large_image",
title,
description,
},
};

const companyFacts = [
{ label: "Founded", value: "March 2026" },
{ label: "Registered", value: "RC 9426867, Nigeria" },
Expand Down
40 changes: 32 additions & 8 deletions src/app/(public)/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import type { Metadata } from "next";
import Image from "next/image";
import { notFound } from "next/navigation";
import { cache } from "react";
import { and, desc, eq } from "drizzle-orm";
import PostContent, {
type TiptapJson,
} from "@/components/blog/PostContent";
import Badge from "@/components/ui/Badge";
import { blogPosts, db } from "@/db";
import { getOptimisedUrl } from "@/lib/cloudinary";

export const revalidate = 3600;

type UpdatePageProps = {
params: {
Expand All @@ -30,7 +34,7 @@ function isTiptapJson(value: unknown): value is TiptapJson {
return typeof value === "object" && value !== null && !Array.isArray(value);
}

async function getPublishedPostBySlug(slug: string) {
const getPublishedPostBySlug = cache(async (slug: string) => {
try {
const [post] = await db
.select()
Expand All @@ -39,11 +43,10 @@ async function getPublishedPostBySlug(slug: string) {
.limit(1);

return post ?? null;
} catch (error) {
console.error("Failed to fetch update", error);
} catch {
return null;
}
}
});

export async function generateStaticParams() {
if (process.env.CI === "true") {
Expand All @@ -70,13 +73,34 @@ export async function generateMetadata({

if (!post) {
return {
title: "Update \u2014 CodedDevs Updates",
title: "Update CodedDevs Updates",
};
}

const title = `${post.title} — CodedDevs Updates`;
const description = post.excerpt;
const url = `https://codeddevs.com/blog/${post.slug}`;
const images = post.cover_url
? [{ url: getOptimisedUrl(post.cover_url), alt: post.title }]
: undefined;

return {
title: `${post.title} \u2014 CodedDevs Updates`,
description: post.excerpt,
title,
description,
openGraph: {
title,
description,
url,
siteName: "CodedDevs Technology LTD",
type: "article",
images,
},
twitter: {
card: "summary_large_image",
title,
description,
images: images?.map((image) => image.url),
},
};
}

Expand Down Expand Up @@ -113,7 +137,7 @@ export default async function UpdatePage({ params }: UpdatePageProps) {
<div className="mx-auto max-w-5xl px-6">
<div className="relative aspect-[16/9] overflow-hidden rounded-lg bg-[#F4F5F8]">
<Image
src={post.cover_url}
src={getOptimisedUrl(post.cover_url)}
alt={post.title}
fill
sizes="(min-width: 1024px) 1024px, 100vw"
Expand Down
28 changes: 25 additions & 3 deletions src/app/(public)/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
import type { Metadata } from "next";
import { desc, eq } from "drizzle-orm";
import UpdatesList, {
type UpdateListPost,
} from "@/components/blog/UpdatesList";
import { blogPosts, db } from "@/db";

export const dynamic = "force-dynamic";
export const revalidate = 3600;

const title = "Updates — CodedDevs Technology LTD";
const description =
"Product updates, announcements, and stories from the CodedDevs team.";

export const metadata: Metadata = {
title,
description,
openGraph: {
title,
description,
url: "https://codeddevs.com/blog",
siteName: "CodedDevs Technology LTD",
type: "website",
},
twitter: {
card: "summary_large_image",
title,
description,
},
};

async function getPublishedPosts(): Promise<UpdateListPost[]> {
try {
Expand All @@ -17,6 +39,7 @@ async function getPublishedPosts(): Promise<UpdateListPost[]> {
author: blogPosts.author,
category: blogPosts.category,
published_at: blogPosts.published_at,
cover_url: blogPosts.cover_url,
})
.from(blogPosts)
.where(eq(blogPosts.is_published, true))
Expand All @@ -26,8 +49,7 @@ async function getPublishedPosts(): Promise<UpdateListPost[]> {
...post,
published_at: post.published_at?.toISOString() ?? null,
}));
} catch (error) {
console.error("Failed to fetch updates", error);
} catch {
return [];
}
}
Expand Down
42 changes: 37 additions & 5 deletions src/app/(public)/careers/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Metadata } from "next";
import Link from "next/link";
import { desc, eq } from "drizzle-orm";
import ApplicationForm from "@/components/careers/ApplicationForm";
Expand All @@ -7,7 +8,33 @@ import Card from "@/components/ui/Card";
import { careers, db } from "@/db";
import type { Career } from "@/types";

export const dynamic = "force-dynamic";
export const revalidate = 3600;

const title = "Careers — CodedDevs Technology LTD";
const description =
"Work with CodedDevs. We are a small team building software for African markets.";

type OpenRole = Pick<
Career,
"id" | "title" | "type" | "location" | "description"
>;

export const metadata: Metadata = {
title,
description,
openGraph: {
title,
description,
url: "https://codeddevs.com/careers",
siteName: "CodedDevs Technology LTD",
type: "website",
},
twitter: {
card: "summary_large_image",
title,
description,
},
};

function formatType(type: Career["type"]) {
return type
Expand All @@ -19,17 +46,22 @@ function formatType(type: Career["type"]) {
async function getOpenRoles() {
try {
return await db
.select()
.select({
id: careers.id,
title: careers.title,
type: careers.type,
location: careers.location,
description: careers.description,
})
.from(careers)
.where(eq(careers.is_open, true))
.orderBy(desc(careers.created_at));
} catch (error) {
console.error("Failed to fetch open roles", error);
} catch {
return [];
}
}

function RoleCard({ role }: { role: Career }) {
function RoleCard({ role }: { role: OpenRole }) {
return (
<Card>
<article className="space-y-6">
Expand Down
24 changes: 24 additions & 0 deletions src/app/(public)/contact/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
import type { Metadata } from "next";
import ContactForm from "@/components/contact/ContactForm";

export const revalidate = 3600;

const title = "Contact — CodedDevs Technology LTD";
const description =
"Get in touch with the CodedDevs team. Partnerships, press, investment, and general enquiries.";
Comment thread
coderabbitai[bot] marked this conversation as resolved.

export const metadata: Metadata = {
title,
description,
openGraph: {
title,
description,
url: "https://codeddevs.com/contact",
siteName: "CodedDevs Technology LTD",
type: "website",
},
twitter: {
card: "summary_large_image",
title,
description,
},
};

const socialLinks = [
{
label: "GitHub",
Expand Down
50 changes: 43 additions & 7 deletions src/app/(public)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,71 @@
import type { Metadata } from "next";
import { desc, eq } from "drizzle-orm";
import HeroSection from "@/components/sections/HeroSection";
import HackathonStrip from "@/components/sections/HackathonStrip";
import LatestReleasesSection from "@/components/sections/LatestReleasesSection";
import ProductsSection from "@/components/sections/ProductsSection";
import { blogPosts, db, products } from "@/db";

export const dynamic = "force-dynamic";
export const revalidate = 3600;

const title = "CodedDevs Technology LTD";
const description =
"Engineering software that works for Africa. AI-first products built for African markets from first principles.";

export const metadata: Metadata = {
title,
description,
openGraph: {
title,
description,
url: "https://codeddevs.com",
siteName: "CodedDevs Technology LTD",
type: "website",
},
twitter: {
card: "summary_large_image",
title,
description,
},
};

async function getFeaturedProducts() {
try {
return await db
.select()
.select({
id: products.id,
name: products.name,
slug: products.slug,
tagline: products.tagline,
cover_url: products.cover_url,
external_url: products.external_url,
status: products.status,
is_featured: products.is_featured,
})
.from(products)
.where(eq(products.is_featured, true))
.orderBy(products.order_index);
} catch (error) {
console.error("Failed to fetch featured products", error);
} catch {
return [];
}
}

async function getLatestPosts() {
try {
return await db
.select()
.select({
id: blogPosts.id,
title: blogPosts.title,
slug: blogPosts.slug,
excerpt: blogPosts.excerpt,
category: blogPosts.category,
published_at: blogPosts.published_at,
})
.from(blogPosts)
.where(eq(blogPosts.is_published, true))
.orderBy(desc(blogPosts.published_at))
.limit(3);
} catch (error) {
console.error("Failed to fetch latest posts", error);
} catch {
return [];
}
}
Expand Down
Loading
Loading