{
+ const now = new Date();
+ const staticEntries = staticRoutes.map((route) => ({
+ url: `${baseUrl}${route}`,
+ lastModified: now,
+ }));
+
+ try {
+ const [productEntries, postEntries] = await Promise.all([
+ db
+ .select({
+ slug: products.slug,
+ updated_at: products.updated_at,
+ })
+ .from(products)
+ .orderBy(products.order_index),
+ db
+ .select({
+ slug: blogPosts.slug,
+ updated_at: blogPosts.updated_at,
+ })
+ .from(blogPosts)
+ .where(eq(blogPosts.is_published, true))
+ .orderBy(desc(blogPosts.published_at)),
+ ]);
+
+ return [
+ ...staticEntries,
+ ...productEntries.map((product) => ({
+ url: `${baseUrl}/products/${product.slug}`,
+ lastModified: product.updated_at,
+ })),
+ ...postEntries.map((post) => ({
+ url: `${baseUrl}/blog/${post.slug}`,
+ lastModified: post.updated_at,
+ })),
+ ];
+ } catch {
+ return staticEntries;
+ }
+}
diff --git a/src/components/admin/ImageUpload.tsx b/src/components/admin/ImageUpload.tsx
index 373ded6..8261f39 100644
--- a/src/components/admin/ImageUpload.tsx
+++ b/src/components/admin/ImageUpload.tsx
@@ -3,6 +3,7 @@
import Image from "next/image";
import { useRef, useState } from "react";
import Button from "@/components/ui/Button";
+import { getOptimisedUrl } from "@/lib/cloudinary";
type ImageUploadProps = {
value: string | null;
@@ -72,7 +73,7 @@ export default function ImageUpload({ value, onChange }: ImageUploadProps) {
{value ? (
;
+
type LatestReleasesSectionProps = {
- posts: BlogPost[];
+ posts: LatestReleasePost[];
};
const ctaByCategory: Record = {
diff --git a/src/components/sections/ProductsSection.tsx b/src/components/sections/ProductsSection.tsx
index fdb620f..986c5d5 100644
--- a/src/components/sections/ProductsSection.tsx
+++ b/src/components/sections/ProductsSection.tsx
@@ -4,8 +4,20 @@ import Button from "@/components/ui/Button";
import Card from "@/components/ui/Card";
import type { ProductSelect } from "@/types";
+export type ProductSummary = Pick<
+ ProductSelect,
+ | "id"
+ | "name"
+ | "slug"
+ | "tagline"
+ | "cover_url"
+ | "external_url"
+ | "status"
+ | "is_featured"
+>;
+
type ProductsSectionProps = {
- products: ProductSelect[];
+ products: ProductSummary[];
};
function formatStatus(status: ProductSelect["status"]) {
diff --git a/src/lib/cloudinary.ts b/src/lib/cloudinary.ts
index 65cef05..a9e1be1 100644
--- a/src/lib/cloudinary.ts
+++ b/src/lib/cloudinary.ts
@@ -1,17 +1,35 @@
-import { v2 as cloudinary } from "cloudinary";
+export function getOptimisedUrl(url: string | null | undefined): string {
+ if (!url) {
+ return "";
+ }
-cloudinary.config({
- cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
- api_key: process.env.CLOUDINARY_API_KEY,
- api_secret: process.env.CLOUDINARY_API_SECRET,
-});
+ if (!url.includes("res.cloudinary.com") || !url.includes("/upload/")) {
+ return url;
+ }
+
+ if (url.includes("/upload/f_auto,q_auto/")) {
+ return url;
+ }
+
+ return url.replace("/upload/", "/upload/f_auto,q_auto/");
+}
export async function uploadToCloudinary(
fileBuffer: Buffer,
filename: string,
) {
+ const importCloudinary = new Function("return import('cloudinary')") as () => Promise<
+ typeof import("cloudinary")
+ >;
+ const { v2: cloudinary } = await importCloudinary();
const publicId = filename.replace(/\.[^/.]+$/, "");
+ cloudinary.config({
+ cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
+ api_key: process.env.CLOUDINARY_API_KEY,
+ api_secret: process.env.CLOUDINARY_API_SECRET,
+ });
+
return new Promise((resolve, reject) => {
const uploadStream = cloudinary.uploader.upload_stream(
{