chore: integrate local imgproxy sidecar and unify list components
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 36s
Build & Deploy / 🧪 QA (push) Successful in 4m2s
Build & Deploy / 🏗️ Build (push) Successful in 10m53s
Build & Deploy / 🚀 Deploy (push) Successful in 27s
Build & Deploy / 🩺 Health Check (push) Failing after 11s
Build & Deploy / 🔔 Notify (push) Successful in 2s
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 36s
Build & Deploy / 🧪 QA (push) Successful in 4m2s
Build & Deploy / 🏗️ Build (push) Successful in 10m53s
Build & Deploy / 🚀 Deploy (push) Successful in 27s
Build & Deploy / 🩺 Health Check (push) Failing after 11s
Build & Deploy / 🔔 Notify (push) Successful in 2s
- Added imgproxy service to docker-compose.dev.yml with URL mapping - Implemented robust Base64 encoding for imgproxy source URLs - Synchronized NEXT_PUBLIC_IMGPROXY_URL and NEXT_PUBLIC_BASE_URL - Refactored About page to use existing marc-mintel.png asset - Created shared IconList component and unified list styles project-wide - Fixed vertical alignment issues in IconList items - Updated dev script with aggressive port 3000 and lock file cleanup
This commit is contained in:
26
apps/web/src/utils/imgproxy-loader.ts
Normal file
26
apps/web/src/utils/imgproxy-loader.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { getImgproxyUrl } from "./imgproxy";
|
||||
|
||||
/**
|
||||
* Next.js Image Loader for imgproxy
|
||||
*
|
||||
* @param {Object} props - properties from Next.js Image component
|
||||
* @param {string} props.src - The source image URL
|
||||
* @param {number} props.width - The desired image width
|
||||
* @param {number} props.quality - The desired image quality (ignored for now as imgproxy handles it)
|
||||
*/
|
||||
export default function imgproxyLoader({
|
||||
src,
|
||||
width,
|
||||
quality,
|
||||
}: {
|
||||
src: string;
|
||||
width: number;
|
||||
quality?: number;
|
||||
}) {
|
||||
// We use the width provided by Next.js for responsive images
|
||||
// Height is set to 0 to maintain aspect ratio
|
||||
return getImgproxyUrl(src, {
|
||||
width,
|
||||
resizing_type: "fit",
|
||||
});
|
||||
}
|
||||
79
apps/web/src/utils/imgproxy.ts
Normal file
79
apps/web/src/utils/imgproxy.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Generates an imgproxy URL for a given source image and options.
|
||||
*
|
||||
* Documentation: https://docs.imgproxy.net/usage/processing
|
||||
*/
|
||||
|
||||
interface ImgproxyOptions {
|
||||
width?: number;
|
||||
height?: number;
|
||||
resizing_type?: "fit" | "fill" | "fill-down" | "force" | "auto";
|
||||
gravity?: string;
|
||||
enlarge?: boolean;
|
||||
extension?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a string to Base64 (URL-safe)
|
||||
*/
|
||||
function encodeBase64(str: string): string {
|
||||
if (typeof Buffer !== "undefined") {
|
||||
return Buffer.from(str)
|
||||
.toString("base64")
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_")
|
||||
.replace(/=+$/, "");
|
||||
} else {
|
||||
// Fallback for browser environment if Buffer is not available
|
||||
return window
|
||||
.btoa(str)
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_")
|
||||
.replace(/=+$/, "");
|
||||
}
|
||||
}
|
||||
|
||||
export function getImgproxyUrl(
|
||||
src: string,
|
||||
options: ImgproxyOptions = {},
|
||||
): string {
|
||||
const baseUrl =
|
||||
process.env.NEXT_PUBLIC_IMGPROXY_URL || "https://img.infra.mintel.me";
|
||||
|
||||
// If no imgproxy URL is configured, return the source as is
|
||||
if (!baseUrl) return src;
|
||||
|
||||
// Handle local paths or relative URLs
|
||||
let absoluteSrc = src;
|
||||
if (src.startsWith("/")) {
|
||||
const baseUrl =
|
||||
process.env.NEXT_PUBLIC_BASE_URL ||
|
||||
(typeof window !== "undefined" ? window.location.origin : "");
|
||||
if (baseUrl) {
|
||||
absoluteSrc = `${baseUrl}${src}`;
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
width = 0,
|
||||
height = 0,
|
||||
resizing_type = "fit",
|
||||
gravity = "sm",
|
||||
enlarge = false,
|
||||
extension = "",
|
||||
} = options;
|
||||
|
||||
// Processing options
|
||||
// Format: /rs:<type>:<width>:<height>:<enlarge>/g:<gravity>
|
||||
const processingOptions = [
|
||||
`rs:${resizing_type}:${width}:${height}:${enlarge ? 1 : 0}`,
|
||||
`g:${gravity}`,
|
||||
].join("/");
|
||||
|
||||
// Using /unsafe/ for now as we don't handle signatures yet
|
||||
// Format: <base_url>/unsafe/<options>/<base64_url>
|
||||
const suffix = extension ? `@${extension}` : "";
|
||||
const encodedSrc = encodeBase64(absoluteSrc + suffix);
|
||||
|
||||
return `${baseUrl}/unsafe/${processingOptions}/${encodedSrc}`;
|
||||
}
|
||||
62
apps/web/src/utils/imgproxy.verify.ts
Normal file
62
apps/web/src/utils/imgproxy.verify.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { getImgproxyUrl } from "./imgproxy";
|
||||
|
||||
/**
|
||||
* Verification script for imgproxy URL generation
|
||||
*/
|
||||
|
||||
function testImgproxyUrl() {
|
||||
const testCases = [
|
||||
{
|
||||
src: "https://picsum.photos/800/800",
|
||||
options: { width: 400, height: 300, resizing_type: "fill" as const },
|
||||
description: "Remote URL with fill resizing",
|
||||
},
|
||||
{
|
||||
src: "/images/avatar.jpg",
|
||||
options: { width: 100, extension: "webp" },
|
||||
description: "Local path with extension conversion",
|
||||
},
|
||||
{
|
||||
src: "https://example.com/image.png",
|
||||
options: { gravity: "no" },
|
||||
description: "Remote URL with custom gravity",
|
||||
},
|
||||
];
|
||||
|
||||
console.log("🧪 Testing imgproxy URL generation...\n");
|
||||
|
||||
testCases.forEach((tc, i) => {
|
||||
const url = getImgproxyUrl(tc.src, tc.options);
|
||||
console.log(`Test Case ${i + 1}: ${tc.description}`);
|
||||
console.log(`Source: ${tc.src}`);
|
||||
console.log(`Result: ${url}`);
|
||||
|
||||
// Basic validation
|
||||
if (url.startsWith("https://img.infra.mintel.me/unsafe/")) {
|
||||
console.log("✅ Base URL and unsafe path correct");
|
||||
} else {
|
||||
console.log("❌ Base URL or unsafe path mismatch");
|
||||
}
|
||||
|
||||
if (
|
||||
tc.options.width &&
|
||||
url.includes(
|
||||
`rs:${tc.options.resizing_type || "fit"}:${tc.options.width}`,
|
||||
)
|
||||
) {
|
||||
console.log("✅ Resizing options present");
|
||||
}
|
||||
|
||||
console.log("-------------------\n");
|
||||
});
|
||||
}
|
||||
|
||||
// Mock environment for testing if not set
|
||||
if (!process.env.NEXT_PUBLIC_IMGPROXY_URL) {
|
||||
process.env.NEXT_PUBLIC_IMGPROXY_URL = "https://img.infra.mintel.me";
|
||||
}
|
||||
if (!process.env.NEXT_PUBLIC_BASE_URL) {
|
||||
process.env.NEXT_PUBLIC_BASE_URL = "http://mintel.localhost";
|
||||
}
|
||||
|
||||
testImgproxyUrl();
|
||||
Reference in New Issue
Block a user