chore: align ecosystem to Next.js 16.1.6 and v1.6.0, migrate to ESLint 9 Flat Config
This commit is contained in:
@@ -9,11 +9,11 @@ export const runtime = 'nodejs';
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { locale: string } }
|
||||
{ params }: { params: Promise<{ locale: string }> },
|
||||
) {
|
||||
const { searchParams, origin } = new URL(request.url);
|
||||
const slug = searchParams.get('slug');
|
||||
const locale = params.locale || 'en';
|
||||
const { locale } = await params;
|
||||
|
||||
if (!slug) {
|
||||
return new Response('Missing slug', { status: 400 });
|
||||
@@ -23,24 +23,29 @@ export async function GET(
|
||||
const t = await getTranslations({ locale, namespace: 'Products' });
|
||||
|
||||
// Check if it's a category page
|
||||
const categories = ['low-voltage-cables', 'medium-voltage-cables', 'high-voltage-cables', 'solar-cables'];
|
||||
const categories = [
|
||||
'low-voltage-cables',
|
||||
'medium-voltage-cables',
|
||||
'high-voltage-cables',
|
||||
'solar-cables',
|
||||
];
|
||||
if (categories.includes(slug)) {
|
||||
const categoryKey = slug.replace(/-cables$/, '').replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
||||
const categoryTitle = t.has(`categories.${categoryKey}.title`) ? t(`categories.${categoryKey}.title`) : slug;
|
||||
const categoryDesc = t.has(`categories.${categoryKey}.description`) ? t(`categories.${categoryKey}.description`) : '';
|
||||
const categoryKey = slug
|
||||
.replace(/-cables$/, '')
|
||||
.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
||||
const categoryTitle = t.has(`categories.${categoryKey}.title`)
|
||||
? t(`categories.${categoryKey}.title`)
|
||||
: slug;
|
||||
const categoryDesc = t.has(`categories.${categoryKey}.description`)
|
||||
? t(`categories.${categoryKey}.description`)
|
||||
: '';
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<OGImageTemplate
|
||||
title={categoryTitle}
|
||||
description={categoryDesc}
|
||||
label="Product Category"
|
||||
/>
|
||||
),
|
||||
<OGImageTemplate title={categoryTitle} description={categoryDesc} label="Product Category" />,
|
||||
{
|
||||
...OG_IMAGE_SIZE,
|
||||
fonts,
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,24 +56,21 @@ export async function GET(
|
||||
}
|
||||
|
||||
const featuredImage = product.frontmatter.images?.[0]
|
||||
? (product.frontmatter.images[0].startsWith('http')
|
||||
? product.frontmatter.images[0].startsWith('http')
|
||||
? product.frontmatter.images[0]
|
||||
: `${origin}${product.frontmatter.images[0]}`)
|
||||
: `${origin}${product.frontmatter.images[0]}`
|
||||
: undefined;
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<OGImageTemplate
|
||||
title={product.frontmatter.title}
|
||||
description={product.frontmatter.description}
|
||||
label={product.frontmatter.categories?.[0] || 'Product'}
|
||||
image={featuredImage}
|
||||
/>
|
||||
),
|
||||
<OGImageTemplate
|
||||
title={product.frontmatter.title}
|
||||
description={product.frontmatter.description}
|
||||
label={product.frontmatter.categories?.[0] || 'Product'}
|
||||
image={featuredImage}
|
||||
/>,
|
||||
{
|
||||
...OG_IMAGE_SIZE,
|
||||
fonts,
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,25 +7,16 @@ import { getTranslations } from 'next-intl/server';
|
||||
import { SITE_URL } from '@/lib/schema';
|
||||
import { getOGImageMetadata } from '@/lib/metadata';
|
||||
import { Suspense } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
const LeafletMap = dynamic(() => import('@/components/LeafletMap'), {
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<div className="h-full w-full bg-neutral-medium flex items-center justify-center">
|
||||
<div className="animate-pulse text-primary font-medium">Loading Map...</div>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
import ContactMap from '@/components/ContactMap';
|
||||
|
||||
interface ContactPageProps {
|
||||
params: {
|
||||
params: Promise<{
|
||||
locale: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params: { locale },
|
||||
}: ContactPageProps): Promise<Metadata> {
|
||||
export async function generateMetadata({ params }: ContactPageProps): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: 'Contact' });
|
||||
const title = t('meta.title') || t('title');
|
||||
const description = t('meta.description') || t('subtitle');
|
||||
@@ -66,7 +57,7 @@ export async function generateStaticParams() {
|
||||
}
|
||||
|
||||
export default async function ContactPage({ params }: ContactPageProps) {
|
||||
const { locale } = params;
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: 'Contact' });
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen bg-neutral-light">
|
||||
@@ -249,7 +240,7 @@ export default async function ContactPage({ params }: ContactPageProps) {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<LeafletMap address={t('info.address')} lat={48.8144} lng={9.4144} />
|
||||
<ContactMap address={t('info.address')} lat={48.8144} lng={9.4144} />
|
||||
</Suspense>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import Header from '@/components/Header';
|
||||
import JsonLd from '@/components/JsonLd';
|
||||
import AnalyticsProvider from '@/components/analytics/AnalyticsProvider';
|
||||
import CMSConnectivityNotice from '@/components/CMSConnectivityNotice';
|
||||
import { FeedbackOverlay } from '@/components/feedback/FeedbackOverlay';
|
||||
import { FeedbackOverlay } from '@mintel/next-feedback';
|
||||
import { Metadata, Viewport } from 'next';
|
||||
import { NextIntlClientProvider } from 'next-intl';
|
||||
import { getMessages } from 'next-intl/server';
|
||||
|
||||
@@ -19,14 +19,14 @@ import Link from 'next/link';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
interface ProductPageProps {
|
||||
params: {
|
||||
params: Promise<{
|
||||
locale: string;
|
||||
slug: string[];
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: ProductPageProps): Promise<Metadata> {
|
||||
const { locale, slug } = params;
|
||||
const { locale, slug } = await params;
|
||||
const productSlug = slug[slug.length - 1];
|
||||
const t = await getTranslations('Products');
|
||||
|
||||
@@ -169,7 +169,7 @@ const components = {
|
||||
};
|
||||
|
||||
export default async function ProductPage({ params }: ProductPageProps) {
|
||||
const { locale, slug } = params;
|
||||
const { locale, slug } = await params;
|
||||
const productSlug = slug[slug.length - 1];
|
||||
const t = await getTranslations('Products');
|
||||
|
||||
|
||||
@@ -10,14 +10,13 @@ import { getOGImageMetadata } from '@/lib/metadata';
|
||||
import { SITE_URL } from '@/lib/schema';
|
||||
|
||||
interface ProductsPageProps {
|
||||
params: {
|
||||
params: Promise<{
|
||||
locale: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params: { locale },
|
||||
}: ProductsPageProps): Promise<Metadata> {
|
||||
export async function generateMetadata({ params }: ProductsPageProps): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: 'Products' });
|
||||
const title = t('meta.title') || t('title');
|
||||
const description = t('meta.description') || t('subtitle');
|
||||
@@ -47,13 +46,14 @@ export async function generateMetadata({
|
||||
}
|
||||
|
||||
export default async function ProductsPage({ params }: ProductsPageProps) {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations('Products');
|
||||
|
||||
// Get translated category slugs
|
||||
const lowVoltageSlug = await mapFileSlugToTranslated('low-voltage-cables', params.locale);
|
||||
const mediumVoltageSlug = await mapFileSlugToTranslated('medium-voltage-cables', params.locale);
|
||||
const highVoltageSlug = await mapFileSlugToTranslated('high-voltage-cables', params.locale);
|
||||
const solarSlug = await mapFileSlugToTranslated('solar-cables', params.locale);
|
||||
const lowVoltageSlug = await mapFileSlugToTranslated('low-voltage-cables', locale);
|
||||
const mediumVoltageSlug = await mapFileSlugToTranslated('medium-voltage-cables', locale);
|
||||
const highVoltageSlug = await mapFileSlugToTranslated('high-voltage-cables', locale);
|
||||
const solarSlug = await mapFileSlugToTranslated('solar-cables', locale);
|
||||
|
||||
const categories = [
|
||||
{
|
||||
@@ -61,28 +61,28 @@ export default async function ProductsPage({ params }: ProductsPageProps) {
|
||||
desc: t('categories.lowVoltage.description'),
|
||||
img: '/uploads/2024/11/low-voltage-category.webp',
|
||||
icon: '/uploads/2024/11/Low-Voltage.svg',
|
||||
href: `/${params.locale}/products/${lowVoltageSlug}`,
|
||||
href: `/${locale}/products/${lowVoltageSlug}`,
|
||||
},
|
||||
{
|
||||
title: t('categories.mediumVoltage.title'),
|
||||
desc: t('categories.mediumVoltage.description'),
|
||||
img: '/uploads/2024/11/medium-voltage-category.webp',
|
||||
icon: '/uploads/2024/11/Medium-Voltage.svg',
|
||||
href: `/${params.locale}/products/${mediumVoltageSlug}`,
|
||||
href: `/${locale}/products/${mediumVoltageSlug}`,
|
||||
},
|
||||
{
|
||||
title: t('categories.highVoltage.title'),
|
||||
desc: t('categories.highVoltage.description'),
|
||||
img: '/uploads/2024/11/high-voltage-category.webp',
|
||||
icon: '/uploads/2024/11/High-Voltage.svg',
|
||||
href: `/${params.locale}/products/${highVoltageSlug}`,
|
||||
href: `/${locale}/products/${highVoltageSlug}`,
|
||||
},
|
||||
{
|
||||
title: t('categories.solar.title'),
|
||||
desc: t('categories.solar.description'),
|
||||
img: '/uploads/2024/11/solar-category.webp',
|
||||
icon: '/uploads/2024/11/Solar.svg',
|
||||
href: `/${params.locale}/products/${solarSlug}`,
|
||||
href: `/${locale}/products/${solarSlug}`,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -218,7 +218,7 @@ export default async function ProductsPage({ params }: ProductsPageProps) {
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
href={`/${params.locale}/contact`}
|
||||
href={`/${locale}/contact`}
|
||||
variant="accent"
|
||||
size="lg"
|
||||
className="group whitespace-nowrap w-full md:w-auto md:h-16 md:px-10 md:text-xl"
|
||||
|
||||
@@ -1,79 +1,17 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { createDirectus, rest, authentication, staticToken, createItem, readItems } from '@directus/sdk';
|
||||
import { NextRequest } from 'next/server';
|
||||
import { handleFeedbackRequest } from '@mintel/next-feedback';
|
||||
import { config } from '@/lib/config';
|
||||
|
||||
async function getAuthenticatedClient() {
|
||||
const { url, token: rawToken } = config.infraCMS;
|
||||
const effectiveUrl = url;
|
||||
const token = rawToken?.trim();
|
||||
|
||||
if (!token) {
|
||||
throw new Error('INFRA_DIRECTUS_TOKEN is not configured');
|
||||
}
|
||||
|
||||
const client = createDirectus(effectiveUrl)
|
||||
.with(staticToken(token))
|
||||
.with(rest());
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const client = await getAuthenticatedClient();
|
||||
const items = await client.request(readItems('visual_feedback', {
|
||||
fields: ['*'],
|
||||
sort: ['-date_created'],
|
||||
}));
|
||||
return NextResponse.json(items);
|
||||
} catch (error: any) {
|
||||
const errMsg = error.errors?.[0]?.message || error.message || 'Unknown Directus Error';
|
||||
console.error('Error fetching feedback:', {
|
||||
msg: errMsg,
|
||||
url: config.infraCMS.url,
|
||||
status: error.response?.status,
|
||||
errors: error.errors
|
||||
});
|
||||
return NextResponse.json({ error: errMsg }, { status: 500 });
|
||||
}
|
||||
export async function GET(req: NextRequest) {
|
||||
return handleFeedbackRequest(req, {
|
||||
url: config.infraCMS.url,
|
||||
token: config.infraCMS.token,
|
||||
});
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const client = await getAuthenticatedClient();
|
||||
const body = await req.json();
|
||||
const { action, ...data } = body;
|
||||
|
||||
if (action === 'reply') {
|
||||
const reply = await client.request(createItem('visual_feedback_comments', {
|
||||
feedback_id: data.feedbackId,
|
||||
user_name: data.userName,
|
||||
text: data.text,
|
||||
}));
|
||||
return NextResponse.json(reply);
|
||||
}
|
||||
|
||||
const feedback = await client.request(createItem('visual_feedback', {
|
||||
project: 'klz-cables',
|
||||
url: data.url,
|
||||
selector: data.selector,
|
||||
x: data.x,
|
||||
y: data.y,
|
||||
type: data.type,
|
||||
text: data.text,
|
||||
user_name: data.userName,
|
||||
user_identity: data.userIdentity,
|
||||
}));
|
||||
|
||||
return NextResponse.json(feedback);
|
||||
} catch (error: any) {
|
||||
const errMsg = error.errors?.[0]?.message || error.message || 'Unknown Directus Error';
|
||||
console.error('Error saving feedback:', {
|
||||
msg: errMsg,
|
||||
url: config.infraCMS.url,
|
||||
status: error.response?.status,
|
||||
errors: error.errors
|
||||
});
|
||||
return NextResponse.json({ error: errMsg }, { status: 500 });
|
||||
}
|
||||
return handleFeedbackRequest(req, {
|
||||
url: config.infraCMS.url,
|
||||
token: config.infraCMS.token,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,43 +1,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { envSchema, getRawEnv } from '@/lib/env';
|
||||
import { NextRequest } from 'next/server';
|
||||
import { handleWhoAmIRequest } from '@mintel/next-feedback';
|
||||
import { config } from '@/lib/config';
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const env = envSchema.parse(getRawEnv());
|
||||
const gatekeeperUrl = env.GATEKEEPER_URL;
|
||||
|
||||
const host = req.headers.get('host') || '';
|
||||
const { searchParams } = new URL(req.url);
|
||||
const hasBypassParam = searchParams.get('gatekeeper_bypass') === 'true';
|
||||
|
||||
const isLocal = host.includes('localhost') || host.includes('127.0.0.1') || host.includes('klz.localhost');
|
||||
const isBypassEnabled = hasBypassParam || env.GATEKEEPER_BYPASS_ENABLED || (env.NODE_ENV === 'development' && isLocal);
|
||||
|
||||
// If bypass is enabled or we are in local development, use "Dev-Admin" identity.
|
||||
if (isBypassEnabled) {
|
||||
return NextResponse.json({
|
||||
authenticated: true,
|
||||
identity: 'Dev-Admin',
|
||||
isDevFallback: true
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// We forward the cookie header to gatekeeper so it can identify the session
|
||||
const response = await fetch(`${gatekeeperUrl}/api/whoami`, {
|
||||
headers: {
|
||||
cookie: req.headers.get('cookie') || '',
|
||||
},
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return NextResponse.json({ authenticated: false, identity: 'Guest' });
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return NextResponse.json(data);
|
||||
} catch (error: any) {
|
||||
console.error('Error proxying to gatekeeper:', error);
|
||||
return NextResponse.json({ authenticated: false, identity: 'Guest (Auth Error)' });
|
||||
}
|
||||
return handleWhoAmIRequest(req, config.gatekeeperUrl);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user