diff --git a/README.md b/README.md
index e215bc4..c07f777 100644
--- a/README.md
+++ b/README.md
@@ -1,36 +1,51 @@
-This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
+# 🚀 Robin Nework New Client Setup
-## Getting Started
+Follow these steps to run the frontend locally.
-First, run the development server:
+---
+
+## 1️⃣ Install Dependencies
```bash
-npm run dev
-# or
-yarn dev
-# or
-pnpm dev
-# or
-bun dev
+npm install
```
-Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+---
-You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
+## 2️⃣ Create `.env` File
-This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
+Create a `.env` file in the root directory and add:
-## Learn More
+```env
+NEXT_PUBLIC_BACKEND_URL=api.robinnetwork.ir (for example)
+NEXT_PUBLIC_BACKEND_URL_LOCAL=http://127.0.0.1:4000
+```
-To learn more about Next.js, take a look at the following resources:
+⚠️ Make sure your backend server is running on port `4000`.
-- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
-- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+---
-You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
+## 3️⃣ Run the Application
-## Deploy on Vercel
+```bash
+npm run build
+npm start
+```
-The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+---
-Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
+## ✅ Application URL
+
+After running the command, the app will be available at:
+
+```
+http://localhost:3000
+```
+
+---
+
+## 📝 Notes
+
+- Restart the server after changing environment variables.
+- Ensure the backend is running before testing API requests.
+- `NEXT_PUBLIC_` variables are exposed to the browser.
diff --git a/ecosystem.config.js b/ecosystem.config.js
new file mode 100644
index 0000000..c8fb2ed
--- /dev/null
+++ b/ecosystem.config.js
@@ -0,0 +1,17 @@
+module.exports = {
+ apps: [
+ {
+ name: "new_client",
+ script: "npm",
+ args: "start",
+ cwd: __dirname,
+ instances: 1,
+ exec_mode: "fork",
+ watch: false,
+ env: {
+ NEXT_PUBLIC_BACKEND_URL: "http://127.0.0.1:4000",
+ NEXT_PUBLIC_BACKEND_URL_LOCAL: "http://127.0.0.1:4000",
+ },
+ },
+ ],
+};
diff --git a/src/app/[locale]/academy/[slug]/page.tsx b/src/app/[locale]/academy/[slug]/page.tsx
index cab8bd8..3a31f14 100644
--- a/src/app/[locale]/academy/[slug]/page.tsx
+++ b/src/app/[locale]/academy/[slug]/page.tsx
@@ -1,13 +1,35 @@
-import Consultation from "@/components/network/Consultation";
-import ContactFooter from "@/components/network/ContactFooter";
import ArticleBody from "@/components/single-academy/ArticleBody";
import ArticleHeader from "@/components/single-academy/ArticleHeader";
import RelatedArticles from "@/components/single-academy/RelatedArticles";
+import { BACKEND_URL_LOCAL } from "@/utilities/constants/urls.constant";
+import { calculateReadingTime } from "@/utilities/lib/calculate-reading-time";
+import { Blog, BlogTranslation } from "@/utilities/types/blog.type";
+import { notFound } from "next/navigation";
+
+export default async function SingleAcademyPage({ params }: { params: Promise<{ slug: string; locale: string }> }) {
+ const { slug, locale } = await params;
+
+ const [blog, relatedBlogs]: [Blog | null, Blog[]] = await Promise.all([
+ fetch(`${BACKEND_URL_LOCAL}/blogs/single/${slug}/${locale}`)
+ .then((res) => res.json())
+ .then((res) => res.data)
+ .catch(() => null),
+ fetch(`${BACKEND_URL_LOCAL}/blogs/related/${slug}/${locale}`)
+ .then((res) => res.json())
+ .then((res) => res.data)
+ .catch(() => null),
+ ]);
+
+ if (!blog) {
+ return notFound();
+ }
+
+ const translation: BlogTranslation = blog.translations.find((t) => t.language === locale) || blog.translations[0];
+ const isRtl = locale === "fa" || locale === "ar";
+ const readingTime = calculateReadingTime(translation);
-export default function SingleAcademyPage() {
return (
-
- {/* Background Grid Pattern */}
+
-
-
-
+
+
+
-
-
-
-
-
);
}
diff --git a/src/app/[locale]/academy/page.tsx b/src/app/[locale]/academy/page.tsx
index c329d94..1839fac 100644
--- a/src/app/[locale]/academy/page.tsx
+++ b/src/app/[locale]/academy/page.tsx
@@ -1,21 +1,19 @@
-import AcademyHeader from "@/components/academy/AcademyHeader";
-import ArticleGrid from "@/components/academy/ArticleGrid";
-import FeaturedArticle from "@/components/academy/FeaturedArticle";
-import Consultation from "@/components/network/Consultation";
-import ContactFooter from "@/components/network/ContactFooter";
+import AcademyClient from "@/components/academy/AcademyClient";
+import { BACKEND_URL_LOCAL } from "@/utilities/constants/urls.constant";
+import { Blog } from "@/utilities/types/blog.type";
+
+export default async function AcademyPage({ params }: { params: Promise<{ locale: string }> }) {
+ const { locale } = await params;
+
+ const data: Blog[] = await fetch(`${BACKEND_URL_LOCAL}/blogs/all/${locale}`)
+ .then((res) => res.json())
+ .then((res) => res.data)
+ .catch(() => []);
-export default function AcademyPage() {
return (
-
-
-
-
-
-
-
-
+
);
diff --git a/src/app/[locale]/network/page.tsx b/src/app/[locale]/network/page.tsx
index 61bbb60..bc6909c 100644
--- a/src/app/[locale]/network/page.tsx
+++ b/src/app/[locale]/network/page.tsx
@@ -4,8 +4,16 @@ import Technologies from "@/components/network/Technologies";
import Projects from "@/components/network/Projects";
import Consultation from "@/components/network/Consultation";
import ContactFooter from "@/components/network/ContactFooter";
+import { Portfolio } from "@/utilities/types/portfolio.type";
+import { BACKEND_URL_LOCAL } from "@/utilities/constants/urls.constant";
+
+export default async function NetworkPage({ params }: { params: Promise<{ locale: string }> }) {
+ const { locale } = await params;
+
+ const latestPortfolios: Portfolio[] = await fetch(`${BACKEND_URL_LOCAL}/portfolios/category/network/${locale}`)
+ .then((res) => res.json())
+ .then((res) => res.data);
-export default function NetworkPage() {
return (
@@ -14,9 +22,7 @@ export default function NetworkPage() {
-
-
-
+
);
}
diff --git a/src/app/[locale]/software/page.tsx b/src/app/[locale]/software/page.tsx
index 81a62eb..f80a1b8 100644
--- a/src/app/[locale]/software/page.tsx
+++ b/src/app/[locale]/software/page.tsx
@@ -1,4 +1,4 @@
-import Hero from "@/components/software/Hero";
+import Hero from "@/components/software/hero/Hero";
import Services from "@/components/software/Services";
import TechStack from "@/components/software/TechStack";
import Process from "@/components/software/Process";
@@ -9,7 +9,7 @@ import { Portfolio } from "@/utilities/types/portfolio.type";
export default async function SoftwarePage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
- const latestPortfolios: Portfolio[] = await fetch(`${BACKEND_URL_LOCAL}/portfolios/category/network/${locale}`)
+ const latestPortfolios: Portfolio[] = await fetch(`${BACKEND_URL_LOCAL}/portfolios/category/software/${locale}`)
.then((res) => res.json())
.then((res) => res.data);
diff --git a/src/components/academy/AcademyClient.tsx b/src/components/academy/AcademyClient.tsx
new file mode 100644
index 0000000..71f06ce
--- /dev/null
+++ b/src/components/academy/AcademyClient.tsx
@@ -0,0 +1,178 @@
+"use client";
+
+import { Blog } from "@/utilities/types/blog.type";
+import { useState, useMemo } from "react";
+import { useLocale, useTranslations } from "next-intl";
+import { calculateReadingTime } from "@/utilities/lib/calculate-reading-time";
+import { formatDateByLocale } from "@/utilities/lib/format-date-by-locale";
+import Image from "next/image";
+import { BACKEND_URL } from "@/utilities/constants/urls.constant";
+import Link from "next/link";
+
+export default function AcademyClient({ data }: { data: Blog[] }) {
+ const t = useTranslations("academy.page");
+ const locale = useLocale();
+ const [search, setSearch] = useState("");
+
+ const filteredData = useMemo(() => {
+ if (!data) return [];
+
+ return data
+ .filter((blog) => {
+ if (!search) return true;
+ const translation = blog.translations?.[0];
+ if (!translation) return false;
+
+ const searchLower = search.toLowerCase();
+ return translation.title.toLowerCase().includes(searchLower) || translation.description.toLowerCase().includes(searchLower);
+ })
+ .slice(0, 6);
+ }, [data, search]);
+
+ const featuredPost = filteredData[0];
+ const gridPosts = filteredData.slice(1);
+
+ return (
+ <>
+
- راهنمای کامل پیکربندی MikroTik برای شبکههای سازمانی
-
-
-
- در این مقاله به صورت جامع نحوه پیکربندی روتر MikroTik برای یک شبکه سازمانی با ۱۰۰+ کاربر را بررسی میکنیم. از VLAN Segmentation تا QoS و
- Firewall Rules.
-