Compare commits

..
2 Commits
Author SHA1 Message Date
Neo d977bc9a42 Add CI/CD pipeline for auto-deploy on commit
Deploy to Production / deploy (push) Has been cancelled
2026-03-04 09:15:18 +00:00
Neo cbb49ba64f Working state: Serbian at root (/), English at /en, proper i18n structure 2026-03-04 08:48:37 +00:00
15 changed files with 235 additions and 23 deletions
+24
View File
@@ -0,0 +1,24 @@
name: Deploy to Production
on:
push:
branches: [master, main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to server
uses: appleboy/[email protected]
with:
host: ${{ secrets.HOST }}
username: root
key: ${{ secrets.SSH_KEY }}
script: |
cd /root/manoonoils-store
git pull origin master
npm run build
docker build -t manoonoils-store:latest .
docker stop manoonoils-store && docker rm manoonoils-store
docker run -d --name manoonoils-store -p 3000:3000 --env-file .env.local manoonoils-store:latest
docker network connect coolify manoonoils-store
Submodule manoonoils-store deleted from 0c70eb06a5
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

+66
View File
@@ -0,0 +1,66 @@
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>
);
}
+114
View File
@@ -0,0 +1,114 @@
"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>
);
}
+4 -1
View File
@@ -5,7 +5,7 @@ import ProductCard from "@/components/product/ProductCard";
export const metadata = { export const metadata = {
title: "ManoonOils - Premium Natural Oils for Hair & Skin", title: "ManoonOils - Premium Natural Oils for Hair & Skin",
description: "Discover our premium collection of natural oils for hair and skin care.", description: "Discover our premium collection of natural oils for hair and skin care. Handmade with love.",
}; };
export default async function Homepage() { export default async function Homepage() {
@@ -16,6 +16,7 @@ export default async function Homepage() {
<main className="min-h-screen"> <main className="min-h-screen">
<Header /> <Header />
{/* Hero Section */}
<section className="relative h-[80vh] flex items-center justify-center bg-gradient-to-b from-white to-background-ice"> <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"> <div className="text-center px-4">
<h1 className="text-5xl md:text-7xl font-serif mb-6"> <h1 className="text-5xl md:text-7xl font-serif mb-6">
@@ -33,6 +34,7 @@ export default async function Homepage() {
</div> </div>
</section> </section>
{/* Products Section */}
{publishedProducts.length > 0 && ( {publishedProducts.length > 0 && (
<section className="py-20 px-4"> <section className="py-20 px-4">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
@@ -46,6 +48,7 @@ export default async function Homepage() {
</section> </section>
)} )}
{/* About Teaser */}
<section className="py-20 px-4 bg-background-ice"> <section className="py-20 px-4 bg-background-ice">
<div className="max-w-3xl mx-auto text-center"> <div className="max-w-3xl mx-auto text-center">
<h2 className="text-3xl font-serif mb-6">Natural & Pure</h2> <h2 className="text-3xl font-serif mb-6">Natural & Pure</h2>
+10 -7
View File
@@ -5,29 +5,32 @@ import ProductCard from "@/components/product/ProductCard";
export const metadata = { export const metadata = {
title: "Products - ManoonOils", title: "Products - ManoonOils",
description: "Browse our premium natural oils for hair and skin.", description: "Browse our collection of premium natural oils for hair and skin care.",
}; };
export default async function ProductsPage() { export default async function ProductsPage() {
const products = await getProducts(); const products = await getProducts();
const publishedProducts = products.filter((p) => p.status === "publish"); const publishedProducts = products.filter((p) => p.status === "publish");
return ( return (
<main className="min-h-screen"> <main className="min-h-screen pt-16 md:pt-20">
<Header /> <Header />
<section className="pt-24 pb-20 px-4"> <section className="py-20 px-4">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
<h1 className="text-4xl font-serif text-center mb-12">All Products</h1> <h1 className="text-4xl md:text-5xl font-serif text-center mb-16">
All Products
</h1>
{publishedProducts.length > 0 ? ( {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"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
{publishedProducts.map((product, index) => ( {publishedProducts.map((product, index) => (
<ProductCard key={product.id} product={product} index={index} /> <ProductCard key={product.id} product={product} index={index} />
))} ))}
</div> </div>
) : (
<p className="text-center text-gray-500">No products available.</p>
)} )}
</div> </div>
</section> </section>
+8 -6
View File
@@ -30,19 +30,21 @@ export default function Header() {
</button> </button>
<Link href="/" className="flex-shrink-0"> <Link href="/" className="flex-shrink-0">
<h1 className="text-2xl md:text-3xl font-serif tracking-wide text-foreground"> <img
ManoonOils src="/manoon-logo.jpg"
</h1> alt="ManoonOils"
className="h-8 w-[124px] md:h-10 md:w-[154px]"
/>
</Link> </Link>
<nav className="hidden md:flex items-center space-x-8"> <nav className="hidden md:flex items-center space-x-8">
<Link href="/en/products" className="text-foreground hover:text-accent-dark transition-colors"> <Link href="/products" className="text-foreground hover:text-accent-dark transition-colors">
Products Products
</Link> </Link>
<Link href="/en/about" className="text-foreground hover:text-accent-dark transition-colors"> <Link href="/about" className="text-foreground hover:text-accent-dark transition-colors">
About About
</Link> </Link>
<Link href="/en/contact" className="text-foreground hover:text-accent-dark transition-colors"> <Link href="/contact" className="text-foreground hover:text-accent-dark transition-colors">
Contact Contact
</Link> </Link>
</nav> </nav>
+2 -2
View File
@@ -5,11 +5,11 @@ export const locales = ["en", "sr"] as const;
export type Locale = (typeof locales)[number]; export type Locale = (typeof locales)[number];
export default getRequestConfig(async ({ locale }) => { export default getRequestConfig(async ({ locale }) => {
const currentLocale = locale || "en"; const currentLocale = locale || "sr";
if (!locales.includes(currentLocale as Locale)) notFound(); if (!locales.includes(currentLocale as Locale)) notFound();
return { return {
locale: currentLocale, locale: currentLocale,
messages: (await import(`./${currentLocale}.json`)).default, messages: (await import(`./messages/${currentLocale}.json`)).default,
}; };
}); });
+7 -6
View File
@@ -1,12 +1,13 @@
import createMiddleware from "next-intl/middleware"; import createMiddleware from "next-intl/middleware";
import { locales } from "./i18n"; import { defineRouting } from 'next-intl/routing';
export default createMiddleware({ const routing = defineRouting({
locales, locales: ['en', 'sr'],
defaultLocale: "sr", defaultLocale: 'sr'
localePrefix: "always"
}); });
export default createMiddleware(routing);
export const config = { export const config = {
matcher: ["/((?!api|_next|.*\\\\..*).*)"] matcher: ['/((?!api|_next|_vercel|).*)']
}; };