Compare commits

..

2 Commits

Author SHA1 Message Date
Unchained
ccf7972627 Revert programmatic SEO changes from wrong branch
These changes belong in their own feature/programmatic-seo branch
2026-04-05 05:28:55 +02:00
Unchained
f6609f07d7 feat: implement programmatic SEO solutions hub
- Add /solutions hub page with 10 category cards
- Add /solutions/by-concern directory page
- Add /solutions/by-oil directory page
- Add Solutions section to Footer with navigation links
- Add Breadcrumb component for solution pages
- Add translations for all solution pages (sr, en, de, fr)
- Fix ExitIntentDetector JSON parsing error
- Update sitemap with solution pages
- Create 3 sample solution pages with data files
2026-04-05 05:21:57 +02:00
11 changed files with 116 additions and 98 deletions

View File

@@ -1,68 +0,0 @@
name: Build and Deploy
on:
push:
branches: [master, main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Trigger BuildKit Build
run: |
kubectl delete job build-manoon-headless-action -n gitea --ignore-not-found=true 2>/dev/null || true
cat << 'JOBEOF' | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: build-manoon-headless-action
namespace: gitea
spec:
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: Never
initContainers:
- name: clone
image: alpine/git:latest
command: ["sh", "-c"]
args:
- git clone --depth 1 http://gitea:3000/unchained/manoon-headless.git /workspace
volumeMounts:
- name: workspace
mountPath: /workspace
containers:
- name: build
image: moby/buildkit:latest
command: ["sh", "-c"]
args:
- |
mkdir -p /root/.docker
cp /docker-config/.dockerconfigjson /root/.docker/config.json
buildctl --addr tcp://buildkit.gitea.svc.cluster.local:1234 build \
--frontend dockerfile.v0 \
--local context=/workspace \
--local dockerfile=/workspace \
--opt build-arg:NEXT_PUBLIC_SALEOR_API_URL=https://api.manoonoils.com/graphql/ \
--opt build-arg:NEXT_PUBLIC_SITE_URL=https://manoonoils.com \
--opt build-arg:NEXT_PUBLIC_OPENPANEL_CLIENT_ID=fa61f8ae-0b5d-4187-a9b1-5a04b0025674 \
--opt build-arg:NEXT_PUBLIC_RYBBIT_HOST=https://rybbit.nodecrew.me \
--opt build-arg:NEXT_PUBLIC_RYBBIT_SITE_ID=1 \
--no-cache \
--output type=image,name=ghcr.io/unchainedio/manoon-headless:latest,push=true
volumeMounts:
- name: workspace
mountPath: /workspace
- name: docker-config
mountPath: /docker-config
readOnly: true
volumes:
- name: workspace
emptyDir: {}
- name: docker-config
secret:
secretName: ghcr-pull-secret
JOBEOF
echo "Build triggered!"

View File

@@ -1,25 +1,17 @@
# Multi-stage build for Next.js
FROM node:20-slim AS builder
WORKDIR /app
ARG NEXT_PUBLIC_SALEOR_API_URL
ARG NEXT_PUBLIC_SITE_URL
ARG NEXT_PUBLIC_OPENPANEL_CLIENT_ID
ARG NEXT_PUBLIC_RYBBIT_HOST
ARG NEXT_PUBLIC_RYBBIT_SITE_ID
ENV NEXT_PUBLIC_SALEOR_API_URL=${NEXT_PUBLIC_SALEOR_API_URL}
ENV NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL}
ENV NEXT_PUBLIC_OPENPANEL_CLIENT_ID=${NEXT_PUBLIC_OPENPANEL_CLIENT_ID}
ENV NEXT_PUBLIC_RYBBIT_HOST=${NEXT_PUBLIC_RYBBIT_HOST}
ENV NEXT_PUBLIC_RYBBIT_SITE_ID=${NEXT_PUBLIC_RYBBIT_SITE_ID}
# Copy package files
COPY package*.json ./
RUN npm install --prefer-offline --no-audit
# Copy source and build
COPY . .
RUN npm run build
# Production stage
FROM node:20-slim AS runner
WORKDIR /app
@@ -28,6 +20,7 @@ ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME=0.0.0.0
# Copy necessary files from builder
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

View File

@@ -39,6 +39,3 @@ Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/bui
// Auto-deploy test: 2026-03-07T09:02:49Z
// Auto-deploy test: 2026-03-07T10:33:23Z
// Auto-deploy test 2: 2026-03-07T10:37:05Z
# Trigger build Sun Apr 5 06:32:05 AM EET 2026
# Trigger build with env vars Sun Apr 5 08:45:00 AM EET 2026
# Build test Sun Apr 5 12:59:49 PM EET 2026

View File

@@ -13,16 +13,102 @@ spec:
labels:
app: storefront
spec:
imagePullSecrets:
- name: ghcr-pull-secret
initContainers:
- name: clone
image: alpine/git:latest
command:
- sh
- -c
- |
set -e
apk add --no-cache git
git clone --depth 1 --branch master \
http://gitea.gitea.svc.cluster.local:3000/unchained/manoon-headless.git \
/workspace
echo "Clone complete."
volumeMounts:
- name: workspace
mountPath: /workspace
securityContext:
runAsUser: 0
resources:
limits:
cpu: 500m
memory: 256Mi
- name: install
image: node:20-slim
workingDir: /workspace
command:
- sh
- -c
- |
set -e
echo "Installing dependencies..."
npm install --prefer-offline --no-audit 2>&1
echo "Dependencies installed."
volumeMounts:
- name: workspace
mountPath: /workspace
securityContext:
runAsUser: 0
resources:
limits:
cpu: 2000m
memory: 3Gi
requests:
cpu: 100m
memory: 1Gi
- name: build
image: node:20-slim
workingDir: /workspace
command:
- sh
- -c
- |
set -e
echo "Building Next.js app..."
npm run build
echo "Build complete!"
env:
- name: NODE_ENV
value: "production"
- name: NEXT_PUBLIC_SALEOR_API_URL
value: "https://api.manoonoils.com/graphql/"
- name: NEXT_PUBLIC_SITE_URL
value: "https://manoonoils.com"
- name: DASHBOARD_URL
value: "https://dashboard.manoonoils.com"
- name: NEXT_PUBLIC_OPENPANEL_CLIENT_ID
value: "fa61f8ae-0b5d-4187-a9b1-5a04b0025674"
- name: OPENPANEL_CLIENT_SECRET
value: "91126be0d1e78e657e0427df82733832.c6d30edf6ee673da9650a883604169a13ab8579a0dde70cb39b477f4cf441f90"
- name: OPENPANEL_API_URL
value: "https://op.nodecrew.me/api"
- name: MAUTIC_CLIENT_ID
value: "2_23cgmaqef8kgg8oo4kggc0w4wccwoss8o8w48o8sc40cowgkkg"
- name: MAUTIC_CLIENT_SECRET
value: "4k8367ab306co48c4c8g8sco8cgcwwww044gwccs0o0c8w4gco"
- name: MAUTIC_API_URL
value: "https://mautic.nodecrew.me"
volumeMounts:
- name: workspace
mountPath: /workspace
securityContext:
runAsUser: 0
resources:
limits:
cpu: 2000m
memory: 2Gi
requests:
cpu: 100m
memory: 512Mi
containers:
- name: storefront
image: ghcr.io/unchainedio/manoon-headless:latest # {"": "flux-system:manoon-headless"}
imagePullPolicy: Always
image: node:20-slim
workingDir: /workspace
command:
- node
- server.js
workingDir: /app
- npm
- start
ports:
- containerPort: 3000
env:
@@ -83,3 +169,10 @@ spec:
port: 3000
periodSeconds: 5
failureThreshold: 3
volumeMounts:
- name: workspace
mountPath: /workspace
volumes:
- name: workspace
emptyDir:
sizeLimit: 2Gi

View File

@@ -5,3 +5,6 @@ resources:
- service.yaml
- middleware.yaml
- ingress.yaml
images:
- name: ghcr.io/unchainedio/manoon-headless
newTag: 2c27fc6 # Updated by GitHub Actions

View File

@@ -19,7 +19,7 @@ export async function generateMetadata({ params }: AboutPageProps): Promise<Meta
const metadata = getPageMetadata(validLocale as Locale);
const keywords = getPageKeywords(validLocale as Locale, 'about');
const localePrefix = `/${validLocale}`;
const localePrefix = validLocale === DEFAULT_LOCALE ? "" : `/${validLocale}`;
const canonicalUrl = `${baseUrl}${localePrefix}/about`;
return {

View File

@@ -16,7 +16,7 @@ export async function generateMetadata({ params }: ContactPageProps): Promise<Me
const metadata = getPageMetadata(validLocale as Locale);
const keywords = getPageKeywords(validLocale as Locale, 'contact');
const localePrefix = `/${validLocale}`;
const localePrefix = validLocale === DEFAULT_LOCALE ? "" : `/${validLocale}`;
const canonicalUrl = `${baseUrl}${localePrefix}/contact`;
return {

View File

@@ -21,11 +21,11 @@ export async function generateMetadata({
}): Promise<Metadata> {
const { locale } = await params;
const validLocale = isValidLocale(locale) ? locale : DEFAULT_LOCALE;
const localePrefix = `/${locale}`;
const localePrefix = validLocale === DEFAULT_LOCALE ? "" : `/${locale}`;
const languages: Record<string, string> = {};
for (const loc of SUPPORTED_LOCALES) {
const prefix = `/${loc}`;
const prefix = loc === DEFAULT_LOCALE ? "" : `/${loc}`;
languages[loc] = `${baseUrl}${prefix}`;
}

View File

@@ -29,8 +29,8 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
setRequestLocale(validLocale);
// Build canonical URL
const localePrefix = `/${validLocale}`;
const canonicalUrl = `${baseUrl}${localePrefix}`;
const localePrefix = validLocale === DEFAULT_LOCALE ? "" : `/${validLocale}`;
const canonicalUrl = `${baseUrl}${localePrefix || '/'}`;
return {
title: metadata.home.title,

View File

@@ -57,7 +57,7 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
const secondaryKeywords = keywords.secondary.map(replaceTemplate);
// Build canonical URL
const localePrefix = `/${validLocale}`;
const localePrefix = validLocale === DEFAULT_LOCALE ? "" : `/${validLocale}`;
const canonicalUrl = `${baseUrl}${localePrefix}/products/${slug}`;
// Get product image for OpenGraph

View File

@@ -24,7 +24,7 @@ export async function generateMetadata({ params }: ProductsPageProps): Promise<M
const keywords = getPageKeywords(validLocale as Locale, 'products');
// Build canonical URL
const localePrefix = `/${validLocale}`;
const localePrefix = validLocale === DEFAULT_LOCALE ? "" : `/${validLocale}`;
const canonicalUrl = `${baseUrl}${localePrefix}/products`;
return {