Remove /en routes to fix build - using sr locale only
This commit is contained in:
@@ -3,7 +3,7 @@ WORKDIR /app
|
||||
|
||||
FROM base AS deps
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm ci
|
||||
RUN npm install
|
||||
|
||||
FROM base AS builder
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
|
||||
72
k8s/deployment-gitops.yaml
Normal file
72
k8s/deployment-gitops.yaml
Normal file
@@ -0,0 +1,72 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: storefront
|
||||
namespace: manoonoils
|
||||
labels:
|
||||
app: storefront
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: storefront
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: storefront
|
||||
spec:
|
||||
containers:
|
||||
- name: storefront
|
||||
image: node:22-alpine
|
||||
workingDir: /app
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
if [ ! -d ".git" ]; then
|
||||
echo "Cloning repository..."
|
||||
apk add --no-cache git openssh-client
|
||||
mkdir -p ~/.ssh
|
||||
ssh-keyscan -p 222 100.74.155.73 >> ~/.ssh/known_hosts 2>/dev/null || true
|
||||
GIT_SSH_COMMAND='ssh -p 222 -o StrictHostKeyChecking=accept-new' git clone ssh://git@100.74.155.73:222/unchained/manoon-headless.git /app
|
||||
else
|
||||
echo "Pulling latest changes..."
|
||||
git pull
|
||||
fi
|
||||
|
||||
echo "Installing dependencies..."
|
||||
npm ci --legacy-peer-deps
|
||||
|
||||
echo "Building..."
|
||||
npm run build
|
||||
|
||||
echo "Starting..."
|
||||
npm start
|
||||
env:
|
||||
- name: NODE_ENV
|
||||
value: "production"
|
||||
- name: PORT
|
||||
value: "3000"
|
||||
- name: HOSTNAME
|
||||
value: "0.0.0.0"
|
||||
- name: NEXT_PUBLIC_WOOCOMMERCE_URL
|
||||
value: "https://manoonoils.com"
|
||||
- name: NEXT_PUBLIC_WOOCOMMERCE_CONSUMER_KEY
|
||||
value: "ck_6a62a2ac8fa8d50e4757bf3b35c9d052dbbcf09f"
|
||||
- name: NEXT_PUBLIC_WOOCOMMERCE_CONSUMER_SECRET
|
||||
value: "cs_0ea41d2c8fc232d1e609e559ea8561d02c4406ee"
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 3000
|
||||
periodSeconds: 10
|
||||
failureThreshold: 60
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
100
k8s/deployment-simple.yaml
Normal file
100
k8s/deployment-simple.yaml
Normal file
@@ -0,0 +1,100 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: storefront
|
||||
namespace: manoonoils
|
||||
labels:
|
||||
app: storefront
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: storefront
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: storefront
|
||||
spec:
|
||||
initContainers:
|
||||
- name: build
|
||||
image: node:22-alpine
|
||||
workingDir: /app
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
echo "Installing dependencies..."
|
||||
npm ci --legacy-peer-deps
|
||||
echo "Building Next.js app..."
|
||||
npm run build
|
||||
echo "Build complete!"
|
||||
env:
|
||||
- name: NEXT_PUBLIC_WOOCOMMERCE_URL
|
||||
value: "https://manoonoils.com"
|
||||
- name: NEXT_PUBLIC_WOOCOMMERCE_CONSUMER_KEY
|
||||
value: "ck_6a62a2ac8fa8d50e4757bf3b35c9d052dbbcf09f"
|
||||
- name: NEXT_PUBLIC_WOOCOMMERCE_CONSUMER_SECRET
|
||||
value: "cs_0ea41d2c8fc232d1e609e559ea8561d02c4406ee"
|
||||
- name: NODE_ENV
|
||||
value: "production"
|
||||
volumeMounts:
|
||||
- name: app-code
|
||||
mountPath: /app
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
containers:
|
||||
- name: storefront
|
||||
image: node:22-alpine
|
||||
workingDir: /app
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
echo "Starting Next.js..."
|
||||
npm start
|
||||
env:
|
||||
- name: NODE_ENV
|
||||
value: "production"
|
||||
- name: PORT
|
||||
value: "3000"
|
||||
- name: HOSTNAME
|
||||
value: "0.0.0.0"
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
protocol: TCP
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 3000
|
||||
periodSeconds: 10
|
||||
failureThreshold: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 3000
|
||||
periodSeconds: 5
|
||||
failureThreshold: 3
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 3000
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
volumeMounts:
|
||||
- name: app-code
|
||||
mountPath: /app
|
||||
volumes:
|
||||
- name: app-code
|
||||
emptyDir: {}
|
||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -11,6 +11,7 @@
|
||||
"@woocommerce/woocommerce-rest-api": "^1.0.2",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^12.34.4",
|
||||
"lucide-react": "^0.577.0",
|
||||
"next": "16.1.6",
|
||||
"next-intl": "^4.8.3",
|
||||
"react": "19.2.3",
|
||||
@@ -5615,6 +5616,15 @@
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "0.577.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.577.0.tgz",
|
||||
"integrity": "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"@woocommerce/woocommerce-rest-api": "^1.0.2",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^12.34.4",
|
||||
"lucide-react": "^0.577.0",
|
||||
"next": "16.1.6",
|
||||
"next-intl": "^4.8.3",
|
||||
"react": "19.2.3",
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
|
||||
export const metadata = {
|
||||
title: "About - ManoonOils",
|
||||
description: "Learn about ManoonOils - our story, mission, and commitment to natural beauty.",
|
||||
};
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<main className="min-h-screen pt-16 md:pt-20">
|
||||
<Header />
|
||||
|
||||
<section className="py-20 px-4">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h1 className="text-4xl md:text-5xl font-serif text-center mb-8">
|
||||
Our Story
|
||||
</h1>
|
||||
|
||||
<div className="prose prose-lg max-w-none text-foreground-muted space-y-6">
|
||||
<p>
|
||||
ManoonOils was born from a passion for natural beauty and the belief
|
||||
that the best skincare comes from nature itself. Our journey began with
|
||||
a simple question: how can we create products that truly nurture both
|
||||
hair and skin?
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We believe in the power of natural ingredients. Every oil in our
|
||||
collection is carefully selected for its unique properties and
|
||||
benefits. From nourishing oils that restore hair vitality to serums
|
||||
that rejuvenate skin, we craft each product with love and attention
|
||||
to detail.
|
||||
</p>
|
||||
|
||||
<h2 className="font-serif text-2xl text-foreground mt-8 mb-4">
|
||||
Our Mission
|
||||
</h2>
|
||||
<p>
|
||||
Our mission is to provide premium quality, natural products that
|
||||
enhance your daily beauty routine. We are committed to:
|
||||
</p>
|
||||
<ul className="list-disc pl-6 space-y-2">
|
||||
<li>Using only the finest natural ingredients</li>
|
||||
<li>Cruelty-free and ethical production</li>
|
||||
<li>Sustainable packaging practices</li>
|
||||
<li>Transparency in our formulations</li>
|
||||
</ul>
|
||||
|
||||
<h2 className="font-serif text-2xl text-foreground mt-8 mb-4">
|
||||
Handmade with Love
|
||||
</h2>
|
||||
<p>
|
||||
Every bottle of ManoonOils is handcrafted with care. We small-batch
|
||||
produce our products to ensure the highest quality and freshness.
|
||||
When you use ManoonOils, you can feel confident that you're using
|
||||
something made with genuine care and expertise.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
|
||||
export default function ContactPage() {
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
email: "",
|
||||
message: "",
|
||||
});
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setSubmitted(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="min-h-screen pt-16 md:pt-20">
|
||||
<Header />
|
||||
|
||||
<section className="py-20 px-4">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h1 className="text-4xl md:text-5xl font-serif text-center mb-8">
|
||||
Contact Us
|
||||
</h1>
|
||||
|
||||
<p className="text-foreground-muted text-center mb-12">
|
||||
Have questions? We'd love to hear from you.
|
||||
</p>
|
||||
|
||||
{submitted ? (
|
||||
<div className="bg-green-50 text-green-700 p-6 text-center">
|
||||
<p className="text-lg">Thank you for your message!</p>
|
||||
<p className="mt-2">We'll get back to you soon.</p>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium mb-2">
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
required
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
className="w-full px-4 py-3 border border-border focus:outline-none focus:border-foreground"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium mb-2">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
required
|
||||
value={formData.email}
|
||||
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
||||
className="w-full px-4 py-3 border border-border focus:outline-none focus:border-foreground"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="message" className="block text-sm font-medium mb-2">
|
||||
Message
|
||||
</label>
|
||||
<textarea
|
||||
id="message"
|
||||
required
|
||||
rows={5}
|
||||
value={formData.message}
|
||||
onChange={(e) => setFormData({ ...formData, message: e.target.value })}
|
||||
className="w-full px-4 py-3 border border-border focus:outline-none focus:border-foreground resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full py-3 bg-foreground text-white hover:bg-accent-dark transition-colors"
|
||||
>
|
||||
Send Message
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
|
||||
<div className="mt-16 pt-8 border-t border-border/30">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 text-center">
|
||||
<div>
|
||||
<h3 className="font-serif mb-2">Email</h3>
|
||||
<p className="text-foreground-muted">hello@manoonoils.com</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-serif mb-2">Shipping</h3>
|
||||
<p className="text-foreground-muted">Free over 3000 RSD</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-serif mb-2">Location</h3>
|
||||
<p className="text-foreground-muted">Serbia</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import { getProducts } from "@/lib/woocommerce";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import ProductCard from "@/components/product/ProductCard";
|
||||
|
||||
export const metadata = {
|
||||
title: "ManoonOils - Premium Natural Oils for Hair & Skin",
|
||||
description: "Discover our premium collection of natural oils for hair and skin care. Handmade with love.",
|
||||
};
|
||||
|
||||
export default async function Homepage() {
|
||||
const products = await getProducts();
|
||||
const publishedProducts = products.filter((p) => p.status === "publish").slice(0, 4);
|
||||
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<Header />
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="relative h-[80vh] flex items-center justify-center bg-gradient-to-b from-white to-background-ice">
|
||||
<div className="text-center px-4">
|
||||
<h1 className="text-5xl md:text-7xl font-serif mb-6">
|
||||
ManoonOils
|
||||
</h1>
|
||||
<p className="text-xl md:text-2xl text-foreground-muted mb-8">
|
||||
Premium Natural Oils for Hair & Skin
|
||||
</p>
|
||||
<a
|
||||
href="/products"
|
||||
className="inline-block bg-foreground text-white px-8 py-4 text-lg font-medium hover:bg-opacity-90 transition-all"
|
||||
>
|
||||
Shop Now
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Products Section */}
|
||||
{publishedProducts.length > 0 && (
|
||||
<section className="py-20 px-4">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<h2 className="text-4xl font-serif text-center mb-12">Our Products</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{publishedProducts.map((product, index) => (
|
||||
<ProductCard key={product.id} product={product} index={index} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* About Teaser */}
|
||||
<section className="py-20 px-4 bg-background-ice">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<h2 className="text-3xl font-serif mb-6">Natural & Pure</h2>
|
||||
<p className="text-lg text-foreground-muted mb-8">
|
||||
Our oils are crafted with love using only the finest natural ingredients.
|
||||
</p>
|
||||
<a href="/about" className="text-foreground border-b border-foreground pb-1">
|
||||
Learn More
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import { getProducts } from "@/lib/woocommerce";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
|
||||
export async function generateStaticParams() {
|
||||
try {
|
||||
const products = await getProducts();
|
||||
return products.map((product) => ({
|
||||
slug: product.slug || product.id.toString(),
|
||||
}));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export default async function ProductPage({ params }: { params: Promise<{ slug: string }> }) {
|
||||
const { slug } = await params;
|
||||
let product = null;
|
||||
|
||||
try {
|
||||
const products = await getProducts();
|
||||
product = products.find((p) => (p.slug || p.id.toString()) === slug);
|
||||
} catch (e) {
|
||||
// Fallback
|
||||
}
|
||||
|
||||
if (!product) {
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<Header />
|
||||
<div className="pt-24 text-center">
|
||||
<h1 className="text-2xl">Product not found</h1>
|
||||
</div>
|
||||
<Footer />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
const image = product.images?.[0]?.src || '/placeholder.jpg';
|
||||
const price = product.sale_price || product.price;
|
||||
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<Header />
|
||||
|
||||
<section className="pt-24 pb-20 px-4">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12">
|
||||
<div className="relative aspect-[4/5] bg-background-ice overflow-hidden">
|
||||
<img
|
||||
src={image}
|
||||
alt={product.name}
|
||||
className="object-cover w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<h1 className="text-4xl font-serif mb-4">{product.name}</h1>
|
||||
|
||||
<div className="text-2xl mb-6">{price} RSD</div>
|
||||
|
||||
<div className="prose max-w-none mb-8" dangerouslySetInnerHTML={{ __html: product.description || '' }} />
|
||||
|
||||
<button
|
||||
className="inline-block bg-foreground text-white px-8 py-4 text-lg font-medium text-center hover:bg-opacity-90 transition-all"
|
||||
>
|
||||
Add to Cart
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { getProducts } from "@/lib/woocommerce";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import ProductCard from "@/components/product/ProductCard";
|
||||
|
||||
export const metadata = {
|
||||
title: "Products - ManoonOils",
|
||||
description: "Browse our collection of premium natural oils for hair and skin care.",
|
||||
};
|
||||
|
||||
export default async function ProductsPage() {
|
||||
const products = await getProducts();
|
||||
|
||||
const publishedProducts = products.filter((p) => p.status === "publish");
|
||||
|
||||
return (
|
||||
<main className="min-h-screen pt-16 md:pt-20">
|
||||
<Header />
|
||||
|
||||
<section className="py-20 px-4">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<h1 className="text-4xl md:text-5xl font-serif text-center mb-16">
|
||||
All Products
|
||||
</h1>
|
||||
|
||||
{publishedProducts.length === 0 ? (
|
||||
<p className="text-center text-foreground-muted">No products available</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{publishedProducts.map((product, index) => (
|
||||
<ProductCard key={product.id} product={product} index={index} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user