Compare commits

...

11 Commits

Author SHA1 Message Date
Pouya Defaei
1900d6ac36 fix the issues 2026-05-22 21:08:27 +03:30
7e25aed979 fix: use localized academy blog title 2026-05-09 06:49:35 +03:30
8801dee4b5 fix: render academy writer username 2026-05-09 06:43:50 +03:30
cd0d947528 fix 2026-05-09 06:35:58 +03:30
b54bb3d217 fix: sync package-lock for deploy 2026-05-09 00:02:49 +03:30
41ecc7ee77 add docker 2026-05-08 23:03:58 +03:30
71a114ccb5 add docker 2026-05-08 22:34:08 +03:30
Pouya Defaei
3edf6ff351 add readme for documentation 2026-05-02 08:52:08 +03:30
Pouya Defaei
fc97bc5f59 complete software page 2026-04-29 12:01:53 +03:30
Pouya Defaei
96dabc975e add blog types 2026-04-25 15:10:23 +03:30
Pouya Defaei
8c051c3533 setup intl for multi language website and make hero and services and projects components in home dynamic and optimized 2026-04-25 13:27:13 +03:30
63 changed files with 3593 additions and 1569 deletions

View File

@@ -1,10 +1,119 @@
.git
.gitignore
.next
# Dependencies
node_modules
npm-debug.log*
Dockerfile
yarn-debug.log*
yarn-error.log*
# Next.js
.next/
out/
# Production
build
# Misc
.DS_Store
*.tsbuildinfo
# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Local env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Vercel
.vercel
# TypeScript
*.tsbuildinfo
next-env.d.ts
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Git
.git
.gitignore
README.md
*.local
.env*
# Docker
Dockerfile
.dockerignore
# Testing
coverage/
.nyc_output
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
registry=https://mirror-npm.runflare.com/

View File

@@ -1,36 +1,43 @@
FROM node:22-bookworm-slim AS base
ENV NEXT_TELEMETRY_DISABLED=1
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json ./
COPY package.json package-lock.json* ./
COPY .npmrc ./
RUN npm ci
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV HOSTNAME=0.0.0.0
ENV PORT=3000
ENV NEXT_TELEMETRY_DISABLED=1
RUN groupadd --system --gid 1001 nodejs && useradd --system --uid 1001 --gid 1001 nextjs
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
RUN mkdir .next
RUN chown nextjs:nodejs .next
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]

View File

@@ -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.

17
ecosystem.config.js Normal file
View File

@@ -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",
},
},
],
};

View File

@@ -1,8 +1,27 @@
import type { NextConfig } from "next";
const createNextIntlPlugin = require("next-intl/plugin");
const nextConfig: NextConfig = {
output: "standalone",
const withNextIntl = createNextIntlPlugin("./src/i18n.ts");
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
images: {
unoptimized: true,
remotePatterns: [
{
protocol: "http",
hostname: "127.0.0.1", // Changed from localhost
port: "4000",
pathname: "/uploads/**",
},
{
protocol: "http",
hostname: "localhost",
port: "4000", // Updated to match your environment variables
pathname: "/uploads/**",
},
],
},
};
export default nextConfig;
module.exports = withNextIntl(nextConfig);

1147
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@
"dependencies": {
"lucide-react": "^1.8.0",
"next": "16.2.4",
"next-intl": "^4.9.1",
"react": "19.2.4",
"react-dom": "19.2.4"
},

View File

@@ -0,0 +1,51 @@
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);
return (
<div className={`min-h-screen bg-[#0B1120] text-white selection:bg-orange-500/30 ${isRtl ? "rtl" : "ltr"}`} dir={isRtl ? "rtl" : "ltr"}>
<div
className="fixed inset-0 z-0 pointer-events-none opacity-20"
style={{
backgroundImage:
"linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px), linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px)",
backgroundSize: "40px 40px",
}}
></div>
<div className="relative z-10 pt-24 pb-12">
<div className="max-w-6xl px-4 mx-auto sm:px-6 lg:px-8">
<ArticleHeader blog={blog} translation={translation} readingTime={readingTime} />
<ArticleBody blog={blog} translation={translation} />
<RelatedArticles data={relatedBlogs} />
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,20 @@
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(() => []);
return (
<div className="min-h-screen pt-8 bg-background text-foreground md:pt-16">
<div className="max-w-[1440px] mx-auto px-4 md:px-8">
<AcademyClient data={data || []} />
</div>
</div>
);
}

View File

@@ -29,7 +29,6 @@
}
body {
direction: rtl;
background-color: var(--background);
color: var(--foreground);
}
@@ -38,3 +37,44 @@ body {
background-image: linear-gradient(to right, var(--grid) 1px, transparent 1px), linear-gradient(to bottom, var(--grid) 1px, transparent 1px);
background-size: 40px 40px;
}
@keyframes orbit-1 {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes orbit-2 {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes orbit-3 {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes orbit-4 {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes counter-rotate {
from {
transform: translate(-50%, -50%) rotate(0deg);
}
to {
transform: translate(-50%, -50%) rotate(-360deg);
}
}

View File

@@ -0,0 +1,42 @@
import type { Metadata } from "next";
import "./globals.css";
import localFont from "next/font/local";
import { NextIntlClientProvider } from "next-intl";
import { getMessages } from "next-intl/server";
import Navbar from "@/components/shared/Navbar";
import { notFound } from "next/navigation";
import Footer from "@/components/shared/footer/Footer";
const modam = localFont({
src: "../../../public/fonts/ModamWeb-Bold.woff2",
weight: "100 900",
});
export const metadata: Metadata = {
title: "رایین شبکه | توسعه نرم‌افزار",
description: "ساخت اپلیکیشن‌های وب مدرن و سیستم‌های سازمانی",
};
const locales = ["fa", "en", "ar"];
export default async function LocaleLayout({ children, params }: { children: React.ReactNode; params: Promise<{ locale: string }> }) {
const { locale } = await params;
if (!locales.includes(locale)) {
notFound();
}
const messages = await getMessages({ locale });
return (
<html lang={locale} dir={locale === "en" ? "ltr" : "rtl"}>
<body className={`${modam.className} flex flex-col min-h-screen bg-bg text-foreground`}>
<NextIntlClientProvider messages={messages}>
<Navbar />
{children}
<Footer />
</NextIntlClientProvider>
</body>
</html>
);
}

View File

@@ -2,10 +2,16 @@ import Hero from "@/components/network/Hero";
import Services from "@/components/network/Services";
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 (
<main className="relative flex flex-col items-center overflow-x-hidden">
<div className="fixed inset-0 -z-20 bg-grid-pattern" />
@@ -14,9 +20,7 @@ export default function NetworkPage() {
<Hero />
<Services />
<Technologies />
<Projects />
<Consultation />
<ContactFooter />
<Projects data={latestPortfolios} />
</main>
);
}

37
src/app/[locale]/page.tsx Normal file
View File

@@ -0,0 +1,37 @@
import Hero from "@/components/home/hero/Hero";
import Consultation from "@/components/network/Consultation";
import ContactFooter from "@/components/network/ContactFooter";
import Services from "@/components/home/Services";
import Projects from "@/components/home/Projects";
import { BACKEND_URL_LOCAL } from "@/utilities/constants/urls.constant";
import { Portfolio } from "@/utilities/types/portfolio.type";
import { Blog } from "@/utilities/types/blog.type";
import Academy from "@/components/home/Academy";
export default async function HomePage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
const [latestPortfolios, latestBlogs]: [Portfolio[], Blog[]] = await Promise.all([
fetch(`${BACKEND_URL_LOCAL}/portfolios/latest/${locale}`)
.then((res) => res.json())
.then((res) => res.data),
fetch(`${BACKEND_URL_LOCAL}/blogs/latest/${locale}`)
.then((res) => res.json())
.then((res) => res.data),
]);
return (
<div className="min-h-screen bg-[#0B1120] text-white selection:bg-orange-500/30">
<div className="fixed top-0 inset-x-0 h-[500px] bg-gradient-to-b from-orange-500/5 via-transparent to-transparent pointer-events-none -z-10"></div>
<main className="flex flex-col gap-24 pt-32 pb-12">
<Hero />
<div className="w-full px-4 mx-auto space-y-32 max-w-7xl sm:px-6 lg:px-8">
<Services />
<Projects data={latestPortfolios} />
<Academy data={latestBlogs} />
</div>
</main>
</div>
);
}

View File

@@ -0,0 +1,25 @@
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";
import Projects from "@/components/software/Projects";
import { BACKEND_URL_LOCAL } from "@/utilities/constants/urls.constant";
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/software/${locale}`)
.then((res) => res.json())
.then((res) => res.data);
return (
<main className="bg-[linear-gradient(rgba(255,255,255,0.02)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.02)_1px,transparent_1px)] bg-[size:40px_40px]">
<Hero />
<Services />
<TechStack />
<Process />
<Projects data={latestPortfolios} />
</main>
);
}

View File

@@ -1,34 +0,0 @@
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";
export default function SingleAcademyPage() {
return (
<div className="min-h-screen bg-[#0B1120] text-white font-sans selection:bg-orange-500/30 rtl" dir="rtl">
{/* Background Grid Pattern */}
<div
className="fixed inset-0 z-0 pointer-events-none opacity-20"
style={{
backgroundImage:
"linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px), linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px)",
backgroundSize: "40px 40px",
}}
></div>
<div className="relative z-10 pt-24 pb-12">
<div className="max-w-6xl px-4 mx-auto sm:px-6 lg:px-8">
<ArticleHeader />
<ArticleBody />
<RelatedArticles />
</div>
</div>
<div className="relative z-10 max-w-6xl px-4 pb-12 mx-auto sm:px-6 lg:px-8">
<Consultation />
<ContactFooter />
</div>
</div>
);
}

View File

@@ -1,22 +0,0 @@
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";
export default function AcademyPage() {
return (
<div className="min-h-screen pt-8 font-sans bg-background text-foreground md:pt-16">
<div className="max-w-[1440px] mx-auto px-4 md:px-8">
<AcademyHeader />
<FeaturedArticle />
<ArticleGrid />
</div>
<div className="mt-16 md:mt-24">
<Consultation />
<ContactFooter />
</div>
</div>
);
}

View File

@@ -1,29 +0,0 @@
import type { Metadata } from "next";
import "./globals.css";
import localFont from "next/font/local";
import Navbar from "@/components/shared/Navbar";
const modam = localFont({
src: "../../public/fonts/ModamWeb-Bold.woff2",
weight: "100 900",
});
export const metadata: Metadata = {
title: "رایین شبکه | توسعه نرم‌افزار",
description: "ساخت اپلیکیشن‌های وب مدرن و سیستم‌های سازمانی",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="fa-ir" className={modam.className}>
<body className="flex flex-col min-h-screen bg-bg text-foreground">
<Navbar />
{children}
</body>
</html>
);
}

View File

@@ -1,31 +0,0 @@
import BentoServices from "@/components/home/BentoServices";
import HomeAcademy from "@/components/home/HomeAcademy";
import HomeHero from "@/components/home/HomeHero";
import HomeProjects from "@/components/home/HomeProjects";
import TechMarquee from "@/components/home/TechMarquee";
import Consultation from "@/components/network/Consultation";
import ContactFooter from "@/components/network/ContactFooter";
export default function HomePage() {
return (
<div className="min-h-screen bg-[#0B1120] text-white font-sans selection:bg-orange-500/30 rtl" dir="rtl">
{/* Background Glow */}
<div className="fixed top-0 inset-x-0 h-[500px] bg-gradient-to-b from-orange-500/5 via-transparent to-transparent pointer-events-none -z-10"></div>
<main className="flex flex-col gap-24 pt-32 pb-12">
<HomeHero />
<TechMarquee />
<div className="w-full px-4 mx-auto space-y-32 max-w-7xl sm:px-6 lg:px-8">
<BentoServices />
<HomeProjects />
<HomeAcademy />
<div>
<Consultation />
<ContactFooter />
</div>
</div>
</main>
</div>
);
}

View File

@@ -1,20 +0,0 @@
// app/software/page.tsx
import Hero from "@/components/software/Hero";
import Services from "@/components/software/Services";
import TechStack from "@/components/software/TechStack";
import Process from "@/components/software/Process";
import Projects from "@/components/software/Projects";
import ContactFooter from "@/components/software/ContactFooter";
export default function SoftwarePage() {
return (
<main className="bg-[linear-gradient(rgba(255,255,255,0.02)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.02)_1px,transparent_1px)] bg-[size:40px_40px]">
<Hero />
<Services />
<TechStack />
<Process />
<Projects />
<ContactFooter />
</main>
);
}

View File

@@ -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 (
<>
<div className="flex flex-col items-center mb-12 text-center md:mb-20">
<div className="bg-accent/10 border border-accent/20 text-accent px-4 py-1.5 rounded-full text-xs mb-6 flex items-center gap-2">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
/>
</svg>
{t("hero.badge")}
</div>
<h1 className="mb-4 text-3xl font-bold md:text-5xl lg:text-6xl">
{t("hero.title1")}
<span className="block mt-2 text-accent">{t("hero.title2")}</span>
</h1>
<p className="max-w-2xl mb-10 text-sm text-muted md:text-base">{t("hero.subtitle")}</p>
<div className="relative w-full max-w-2xl mb-12">
<input
type="text"
placeholder={t("searchPlaceholder")}
value={search}
onChange={({ target }) => setSearch(target.value)}
className="w-full px-6 py-4 text-sm transition border bg-panel border-border rounded-xl focus:outline-none focus:border-accent"
/>
<svg
className="absolute w-5 h-5 -translate-y-1/2 text-muted ltr:right-4 rtl:left-4 top-1/2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
</div>
{featuredPost && (
<Link
href={`/academy/${featuredPost.href}`}
className="flex flex-col mb-8 overflow-hidden transition-colors border cursor-pointer bg-panel border-border rounded-2xl md:mb-12 md:flex-row group hover:border-accent/50"
>
<div className="flex flex-col flex-1 order-2 p-6 md:p-10 md:order-1">
<div className="flex items-center gap-4 mb-4 text-xs text-muted">
<span className="flex items-center gap-1">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
{formatDateByLocale(featuredPost.publishedAt, locale)}
</span>
<span className="flex items-center gap-1">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{calculateReadingTime(featuredPost.translations[0])} {t("minutesRead")}
</span>
</div>
<h2 className="mb-4 text-2xl font-bold leading-tight transition-colors md:text-3xl lg:text-4xl group-hover:text-accent">
{featuredPost.translations?.[0]?.title}
</h2>
<p className="mb-8 text-sm leading-relaxed text-muted md:text-base line-clamp-3">{featuredPost.translations?.[0]?.description}</p>
</div>
<div className="w-full md:w-[45%] aspect-video md:h-auto bg-gradient-to-br from-[#2a1b18] to-[#121418] relative order-1 md:order-2 border-b md:border-b-0 md:ltr:border-l md:rtl:border-r border-border">
<Image
src={`${BACKEND_URL}/uploads/${featuredPost.featuredImage}`}
fill
alt={featuredPost.translations[0].title}
className="object-cover"
/>
</div>
</Link>
)}
{gridPosts.length > 0 && (
<div className="flex flex-col items-center">
<div className="grid w-full grid-cols-1 gap-6 mb-12 md:grid-cols-2 lg:grid-cols-3">
{gridPosts.map((b) => {
const translation = b.translations?.[0];
return (
<Link
href={`/academy/${b.href}`}
key={b.id}
className="flex flex-col h-full overflow-hidden transition-all border cursor-pointer bg-panel border-border rounded-xl group hover:border-accent/50 hover:-translate-y-1"
>
<div className="relative w-full border-b aspect-video bg-gradient-to-br from-gray-800 to-gray-900 border-border">
<Image src={`${BACKEND_URL}/uploads/${b.featuredImage}`} fill alt={translation.title} className="object-cover" />
</div>
<div className="flex flex-col flex-1 p-5">
<div className="flex justify-between items-center text-[11px] text-muted mb-3">
<span className="flex items-center gap-1">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
{formatDateByLocale(b.publishedAt, locale)}
</span>
<span className="flex items-center gap-1">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{calculateReadingTime(translation)} {t("minutesRead")}
</span>
</div>
<h3 className="mb-2 text-lg font-bold transition-colors md:text-xl group-hover:text-accent line-clamp-2">{translation?.title}</h3>
<p className="flex-1 mb-6 text-xs leading-relaxed text-muted md:text-sm line-clamp-3">{translation?.description}</p>
</div>
</Link>
);
})}
</div>
{/* Pagination */}
{/* <div className="flex items-center gap-2 font-mono text-sm">
<button className="flex items-center justify-center w-8 h-8 transition border rounded-lg border-border bg-panel text-muted hover:text-foreground">
{t("pagination.previous")}
</button>
<button className="w-8 h-8 rounded-lg border border-accent bg-accent text-background font-bold flex items-center justify-center transition shadow-[0_0_10px_rgba(232,107,53,0.4)]">
1
</button>
<button className="flex items-center justify-center w-8 h-8 transition border rounded-lg border-border bg-panel text-muted hover:text-foreground">
{t("pagination.next")}
</button>
</div> */}
</div>
)}
</>
);
}

View File

@@ -1,58 +0,0 @@
"use client";
import { useState } from "react";
export default function AcademyHeader() {
const [activeFilter, setActiveFilter] = useState("همه مقالات");
const filters = ["همه مقالات", "شبکه", "امنیت", "برنامه‌نویسی", "DevOps"];
return (
<div className="flex flex-col items-center mb-12 text-center md:mb-20">
<div className="bg-accent/10 border border-accent/20 text-accent px-4 py-1.5 rounded-full text-xs mb-6 flex items-center gap-2">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
/>
</svg>
آکادمی رابین شبکه
</div>
<h1 className="mb-4 text-3xl font-bold md:text-5xl lg:text-6xl">
دانش و تجربه
<span className="block mt-2 text-accent">به اشتراک گذاشته شده</span>
</h1>
<p className="max-w-2xl mb-10 text-sm text-muted md:text-base">مقالات تخصصی، آموزشها و تجربیات واقعی از پروژههای شبکه و نرمافزار</p>
<div className="relative w-full max-w-2xl mb-12">
<input
type="text"
placeholder="جستجو در مقالات..."
className="w-full px-6 py-4 text-sm transition border bg-panel border-border rounded-xl focus:outline-none focus:border-accent"
/>
<svg className="absolute w-5 h-5 -translate-y-1/2 text-muted left-4 top-1/2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<div className="flex flex-wrap justify-center gap-2 md:gap-4">
{filters.map((filter) => (
<button
key={filter}
onClick={() => setActiveFilter(filter)}
className={`px-4 py-2 rounded-lg text-sm transition-all border ${
activeFilter === filter
? "bg-accent border-accent text-background font-bold shadow-[0_0_15px_rgba(232,107,53,0.3)]"
: "bg-panel border-border text-muted hover:border-accent/50 hover:text-foreground"
}`}
>
{filter}
</button>
))}
</div>
</div>
);
}

View File

@@ -1,126 +0,0 @@
export default function ArticleGrid() {
const articles = [
{
title: "طراحی شبکه WiFi برای هتل ۱۰۰ اتاقه",
desc: "انتخاب تجهیزات، Site Survey و پیکربندی UniFi برای پوشش کامل و بدون dead zone.",
category: "شبکه",
date: "۱ هفته پیش",
readTime: "۱۸ دقیقه",
tags: ["WiFi", "UniFi"],
gradient: "from-[#1a2332] to-[#121418]",
},
{
title: "معماری Microservices: از تئوری تا عمل",
desc: "چگونه یک سیستم Monolithic را به معماری Microservices تبدیل کنیم؟ تجربه واقعی از یک پروژه.",
category: "برنامه‌نویسی",
date: "۱ هفته پیش",
readTime: "۲۰ دقیقه",
tags: ["Microservices", "Architecture"],
gradient: "from-[#231a32] to-[#121418]",
},
{
title: "۱۰ نکته طلایی برای امن‌سازی Firewall",
desc: "بهترین روش‌ها و تکنیک‌های پیکربندی فایروال برای محافظت حداکثری از شبکه سازمانی.",
category: "امنیت",
date: "۵ روز پیش",
readTime: "۱۲ دقیقه",
tags: ["Firewall", "Security"],
gradient: "from-[#321a1a] to-[#121418]",
},
{
title: "VPN چیست و چگونه کار می‌کند؟",
desc: "آشنایی با انواع VPN، پروتکل‌ها و نحوه پیاده‌سازی Site-to-Site VPN با MikroTik.",
category: "شبکه",
date: "۲ هفته پیش",
readTime: "۱۰ دقیقه",
tags: ["VPN", "IPSec"],
gradient: "from-[#1a2b32] to-[#121418]",
},
{
title: "راه‌اندازی CI/CD Pipeline با GitHub Actions",
desc: "پیاده‌سازی کامل یک pipeline برای deploy خودکار اپلیکیشن Next.js روی VPS.",
category: "DevOps",
date: "۲ هفته پیش",
readTime: "۲۵ دقیقه",
tags: ["CI/CD", "GitHub"],
gradient: "from-[#2e321a] to-[#121418]",
},
];
return (
<div className="flex flex-col items-center">
<div className="grid w-full grid-cols-1 gap-6 mb-12 md:grid-cols-2 lg:grid-cols-3">
{articles.map((article, i) => (
<div
key={i}
className="flex flex-col h-full overflow-hidden transition-all border cursor-pointer bg-panel border-border rounded-xl group hover:border-accent/50 hover:-translate-y-1"
>
{/* تصویر کاور کارت */}
<div className={`h-48 w-full bg-gradient-to-br ${article.gradient} relative border-b border-border`}>
<div className="absolute top-4 right-4 bg-accent text-background text-[10px] md:text-xs font-bold px-2.5 py-1 rounded-md shadow-lg">
{article.category}
</div>
</div>
{/* محتوای کارت */}
<div className="flex flex-col flex-1 p-5">
<div className="flex justify-between items-center text-[11px] text-muted mb-3">
<span className="flex items-center gap-1">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
{article.date}
</span>
<span className="flex items-center gap-1">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{article.readTime}
</span>
</div>
<h3 className="mb-2 text-lg font-bold transition-colors md:text-xl group-hover:text-accent">{article.title}</h3>
<p className="flex-1 mb-6 text-xs leading-relaxed text-muted md:text-sm">{article.desc}</p>
<div className="flex flex-wrap gap-2 mt-auto">
{article.tags.map((tag) => (
<span key={tag} className="text-[10px] bg-background border border-border px-2 py-1 rounded text-muted">
{tag}
</span>
))}
</div>
</div>
</div>
))}
</div>
{/* Pagination (صفحه‌بندی) */}
<div className="flex items-center gap-2 font-mono text-sm">
<button className="flex items-center justify-center w-8 h-8 transition border rounded-lg border-border bg-panel text-muted hover:text-foreground">
&gt; {/* راست چین (قبلی) */}
</button>
<button className="flex items-center justify-center w-8 h-8 transition border rounded-lg border-border bg-panel text-muted hover:text-foreground">
4
</button>
<button className="flex items-center justify-center w-8 h-8 transition border rounded-lg border-border bg-panel text-muted hover:text-foreground">
3
</button>
<button className="flex items-center justify-center w-8 h-8 transition border rounded-lg border-border bg-panel text-muted hover:text-foreground">
2
</button>
<button className="w-8 h-8 rounded-lg border border-accent bg-accent text-background font-bold flex items-center justify-center transition shadow-[0_0_10px_rgba(232,107,53,0.4)]">
1
</button>
<button className="flex items-center justify-center w-8 h-8 transition border rounded-lg border-border bg-panel text-muted hover:text-foreground">
&lt; {/* چپ چین (بعدی) */}
</button>
</div>
</div>
);
}

View File

@@ -1,52 +0,0 @@
export default function FeaturedArticle() {
return (
<div className="flex flex-col mb-8 overflow-hidden transition-colors border cursor-pointer bg-panel border-border rounded-2xl md:mb-12 md:flex-row group hover:border-accent/50">
<div className="flex flex-col justify-center flex-1 order-2 p-6 md:p-10 md:order-1">
<div className="flex items-center gap-4 mb-4 text-xs text-muted">
<span className="flex items-center gap-1">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
۲ روز پیش
</span>
<span className="flex items-center gap-1">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
۱۵ دقیقه
</span>
</div>
<h2 className="mb-4 text-2xl font-bold leading-tight transition-colors md:text-3xl lg:text-4xl group-hover:text-accent">
راهنمای کامل پیکربندی MikroTik برای شبکههای سازمانی
</h2>
<p className="mb-8 text-sm leading-relaxed text-muted md:text-base">
در این مقاله به صورت جامع نحوه پیکربندی روتر MikroTik برای یک شبکه سازمانی با ۱۰۰+ کاربر را بررسی میکنیم. از VLAN Segmentation تا QoS و
Firewall Rules.
</p>
<div className="flex flex-wrap gap-2 mt-auto">
{["MikroTik", "VLAN", "Routing"].map((tag) => (
<span
key={tag}
className="px-3 py-1 text-xs transition border rounded-md bg-background border-border text-muted hover:text-accent hover:border-accent/50"
>
{tag}
</span>
))}
</div>
</div>
{/* بخش تصویر کاور (از گرادیانت به عنوان جایگزین تصویر استفاده شده) */}
<div className="w-full md:w-[45%] h-64 md:h-auto bg-gradient-to-br from-[#2a1b18] to-[#121418] relative order-1 md:order-2 border-b md:border-b-0 md:border-r border-border">
<div className="absolute top-4 right-4 bg-accent text-background text-xs font-bold px-3 py-1.5 rounded-md">شبکه</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,65 @@
import { formatToPersianDate } from "@/utilities/lib/format-persian-date";
import { Blog } from "@/utilities/types/blog.type";
import { BookOpen, User, ArrowLeft, ArrowRight } from "lucide-react";
import Link from "next/link";
import { useTranslations, useLocale } from "next-intl";
import { formatDateByLocale } from "@/utilities/lib/format-date-by-locale";
export default function Academy({ data }: { data: Blog[] }) {
const t = useTranslations("home.academy");
const locale = useLocale();
// تشخیص جهت چیدمان برای آیکون فلش
const isRtl = locale === "fa" || locale === "ar";
const ArrowIcon = isRtl ? ArrowLeft : ArrowRight;
return (
<section className="py-12 mb-12">
<div className="flex flex-col justify-between gap-6 mb-8 md:flex-row md:items-end">
<div>
<h2 className="flex items-center gap-3 mb-4 text-3xl font-bold text-white">
<BookOpen className="text-orange-500" />
{t("title")}
</h2>
<p className="text-gray-400">{t("subtitle")}</p>
</div>
<Link href={`/${locale}/academy`} className="flex items-center gap-2 font-medium text-orange-500 transition-colors hover:text-orange-400">
{t("viewAll")}
<ArrowIcon className="w-5 h-5" />
</Link>
</div>
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
{data.map((b) => {
const translation = b.translations.find((item) => item.language === locale) || b.translations[0];
return (
<Link
href={`/${locale}/academy/${b.href}`}
key={b.id}
className="flex flex-col justify-between p-6 transition-all duration-300 border group bg-slate-900/40 border-slate-800 hover:border-orange-500/50 rounded-2xl"
>
<div>
<div className="flex items-center gap-3 mb-4">
<span className="px-3 py-1 text-xs text-orange-500 border rounded-full bg-orange-500/10 border-orange-500/20">{t("newBadge")}</span>
<span className="flex items-center gap-1 text-xs text-gray-500">
<User className="w-3 h-3" />
{b.writer.username}
</span>
</div>
<h3 className="mb-4 text-lg font-bold leading-relaxed text-gray-200 transition-colors group-hover:text-white">{translation?.title}</h3>
</div>
<div className="flex items-center justify-between pt-4 text-sm text-gray-500 border-t border-slate-800/50">
<span>{formatDateByLocale(b.publishedAt.toString(), locale)}</span>
<span className="text-orange-500 transition-opacity opacity-0 group-hover:opacity-100">{t("read")}</span>
</div>
</Link>
);
})}
</div>
</section>
);
}

View File

@@ -1,77 +0,0 @@
import React from "react";
import { BookOpen, Clock, ArrowLeft } from "lucide-react";
import Link from "next/link";
const articlesData = [
{
id: 1,
title: "آموزش راه‌اندازی سرورهای مجازی با VMware ESXi",
category: "شبکه",
readTime: "۱۰ دقیقه",
date: "۲۲ فروردین ۱۴۰۵",
},
{
id: 2,
title: "بررسی تفاوت‌های React و Next.js در سال ۲۰۲۶",
category: "برنامه‌نویسی",
readTime: "۸ دقیقه",
date: "۱۵ فروردین ۱۴۰۵",
},
{
id: 3,
title: "اصول طراحی رابط کاربری (UI) برای اپلیکیشن‌های مالی",
category: "طراحی",
readTime: "۱۲ دقیقه",
date: "۵ فروردین ۱۴۰۵",
},
];
export default function HomeAcademy() {
return (
<section className="py-12 mb-12">
<div className="flex flex-col justify-between gap-6 mb-8 md:flex-row md:items-end">
<div>
<h2 className="flex items-center gap-3 mb-4 text-3xl font-bold text-white">
<BookOpen className="text-orange-500" />
تازههای آکادمی
</h2>
<p className="text-gray-400">آخرین مقالات، آموزشها و اخبار دنیای فناوری.</p>
</div>
<Link href="/academy" className="flex items-center gap-2 font-medium text-orange-500 transition-colors hover:text-orange-400">
مشاهده همه مقالات
<ArrowLeft className="w-5 h-5" />
</Link>
</div>
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
{articlesData.map((article) => (
<Link
href={`/academy/${article.id}`}
key={article.id}
className="flex flex-col justify-between p-6 transition-all duration-300 border group bg-slate-900/40 border-slate-800 hover:border-orange-500/50 rounded-2xl"
>
<div>
<div className="flex items-center gap-3 mb-4">
<span className="px-3 py-1 text-xs text-orange-500 border rounded-full bg-orange-500/10 border-orange-500/20">
{article.category}
</span>
<span className="flex items-center gap-1 text-xs text-gray-500">
<Clock className="w-3 h-3" />
{article.readTime}
</span>
</div>
<h3 className="mb-4 text-lg font-bold leading-relaxed text-gray-200 transition-colors group-hover:text-white">{article.title}</h3>
</div>
<div className="flex items-center justify-between pt-4 text-sm text-gray-500 border-t border-slate-800/50">
<span>{article.date}</span>
<span className="text-orange-500 transition-opacity opacity-0 group-hover:opacity-100">مطالعه</span>
</div>
</Link>
))}
</div>
</section>
);
}

View File

@@ -1,37 +0,0 @@
import React from "react";
import { ChevronLeft } from "lucide-react";
export default function HomeHero() {
return (
<section className="flex flex-col items-center justify-center max-w-4xl px-4 mx-auto text-center">
{/* Top Badge */}
<div className="inline-flex items-center gap-2 px-4 py-2 mb-8 text-sm text-orange-400 border rounded-full border-orange-500/20 bg-orange-500/10">
<span className="w-2 h-2 bg-orange-500 rounded-full animate-pulse"></span>
معماری سیستمهای مقیاسپذیر
</div>
{/* Main Title */}
<h1 className="mb-6 text-5xl font-bold leading-tight md:text-7xl">
ما گرههای فنی شما را
<br />
باز میکنیم
</h1>
{/* Subtitle */}
<p className="max-w-2xl mb-10 text-lg leading-relaxed text-gray-400 md:text-xl">
ترکیبی از اقتدار مهندسی در زیرساخت و نوآوری مدرن در توسعه نرمافزار، از راهاندازی شبکههای پیچیده تا توسعه پلتفرمهای ابری سفارشی.
</p>
{/* CTA Buttons */}
<div className="flex flex-col items-center gap-4 sm:flex-row">
<button className="flex items-center justify-center w-full gap-2 px-8 py-3 font-medium text-white transition-colors bg-orange-600 rounded-lg sm:w-auto hover:bg-orange-500">
دریافت مشاوره رایگان
<ChevronLeft className="w-4 h-4" />
</button>
<button className="flex items-center justify-center w-full px-8 py-3 font-medium text-white transition-colors border rounded-lg sm:w-auto border-slate-700 bg-slate-800/50 hover:bg-slate-800">
مشاهده نمونهکارها
</button>
</div>
</section>
);
}

View File

@@ -1,92 +0,0 @@
"use client";
import React, { useState } from "react";
import { ArrowUpLeft, FolderKanban } from "lucide-react";
const projectsData = [
{
id: 1,
title: "ارتقای زیرساخت شبکه هلدینگ دارویی",
category: "شبکه",
description: "طراحی مجدد توپولوژی شبکه و پیاده‌سازی فایروال‌های سخت‌افزاری برای امنیت حداکثری.",
image: "bg-slate-800", // در پروژه واقعی از آدرس تصویر استفاده کنید
},
{
id: 2,
title: "پلتفرم مدیریت منابع انسانی (HRM)",
category: "نرم‌افزار",
description: "توسعه سیستم جامع مدیریت پرسنل با استفاده از Next.js و .NET Core.",
image: "bg-slate-800",
},
{
id: 3,
title: "طراحی هویت بصری استارتاپ فین‌تک",
category: "برندینگ",
description: "طراحی کامل UI/UX اپلیکیشن موبایل و دیزاین سیستم سازمانی.",
image: "bg-slate-800",
},
];
const tabs = ["همه", "شبکه", "نرم‌افزار", "برندینگ"];
export default function HomeProjects() {
const [activeTab, setActiveTab] = useState("همه");
const filteredProjects = projectsData.filter((project) => (activeTab === "همه" ? true : project.category === activeTab));
return (
<section className="py-12 border-t border-slate-800/50">
<div className="flex flex-col justify-between gap-6 mb-12 md:flex-row md:items-end">
<div>
<h2 className="flex items-center gap-3 mb-4 text-3xl font-bold text-white">
<FolderKanban className="text-orange-500" />
پروژههای منتخب
</h2>
<p className="text-gray-400">گزیدهای از چالشهایی که با موفقیت پشت سر گذاشتیم.</p>
</div>
{/* Tabs */}
<div className="flex flex-wrap items-center gap-2 p-1 border bg-slate-900/50 rounded-xl border-slate-800 w-fit">
{tabs.map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
activeTab === tab ? "bg-orange-500 text-white shadow-lg shadow-orange-500/20" : "text-gray-400 hover:text-white hover:bg-slate-800/50"
}`}
>
{tab}
</button>
))}
</div>
</div>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
{filteredProjects.map((project) => (
<div
key={project.id}
className="overflow-hidden transition-all duration-300 border cursor-pointer group bg-slate-900/30 border-slate-800 rounded-2xl hover:border-orange-500/50"
>
{/* Image placeholder */}
<div className={`h-48 w-full ${project.image} relative overflow-hidden`}>
<div className="absolute inset-0 bg-gradient-to-t from-slate-900 via-transparent to-transparent opacity-80"></div>
<div className="absolute px-3 py-1 text-xs text-gray-300 border rounded-full top-4 right-4 bg-black/50 backdrop-blur-sm border-slate-700">
{project.category}
</div>
</div>
<div className="p-6">
<h3 className="mb-2 text-xl font-bold text-white transition-colors group-hover:text-orange-500">{project.title}</h3>
<p className="mb-6 text-sm text-gray-400 line-clamp-2">{project.description}</p>
<div className="flex items-center justify-between text-sm font-medium text-orange-500">
<span>مشاهده پروژه</span>
<ArrowUpLeft className="w-5 h-5 transition-transform group-hover:-translate-y-1 group-hover:translate-x-1" />
</div>
</div>
</div>
))}
</div>
</section>
);
}

View File

@@ -0,0 +1,89 @@
"use client";
import { useState } from "react";
import { ArrowUpLeft, ArrowUpRight, FolderKanban } from "lucide-react";
import { Portfolio } from "@/utilities/types/portfolio.type";
import { useTranslations, useLocale } from "next-intl";
import Image from "next/image";
import { BACKEND_URL } from "@/utilities/constants/urls.constant";
const tabs = ["all", "software", "network"] as const;
export default function Projects({ data }: { data: Portfolio[] }) {
const [activeTab, setActiveTab] = useState<string>("all");
const t = useTranslations("home.projects");
const locale = useLocale();
const filteredPortfolios = data.filter((p) => (activeTab === "all" ? true : p.category === activeTab));
const ArrowIcon = locale === "en" ? ArrowUpRight : ArrowUpLeft;
return (
<section className="py-12 border-t border-slate-800/50" id="portfolios_form">
<div className="flex flex-col justify-between gap-6 mb-12 md:flex-row md:items-end">
<div>
<h2 className="flex items-center gap-3 mb-4 text-3xl font-bold text-white">
<FolderKanban className="text-orange-500" />
{t("title")}
</h2>
<p className="text-gray-400">{t("subtitle")}</p>
</div>
{/* Tabs */}
<div className="flex flex-wrap items-center gap-2 p-1 border bg-slate-900/50 rounded-xl border-slate-800 w-fit">
{tabs.map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
activeTab === tab ? "bg-orange-500 text-white shadow-lg shadow-orange-500/20" : "text-gray-400 hover:text-white hover:bg-slate-800/50"
}`}
>
{t(`tabs.${tab}`)}
</button>
))}
</div>
</div>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
{filteredPortfolios.map((p) => (
<div
key={p.id}
className="flex flex-col h-full overflow-hidden transition-all duration-300 border cursor-pointer group bg-slate-900/30 border-slate-800 rounded-2xl hover:border-orange-500/50"
>
{/* Image placeholder */}
<div className="relative w-full h-48 overflow-hidden shrink-0">
<Image
src={`${BACKEND_URL}/uploads/${p.featuredImage}`}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="object-cover transition-transform duration-500 group-hover:scale-105"
alt={`تصویر ${p.title}`}
/>
<div className="absolute px-3 py-1 text-xs text-gray-300 border rounded-full top-4 right-4 rtl:right-auto rtl:left-4 bg-black/50 backdrop-blur-sm border-slate-700">
{t(`tabs.${p.category}`)}
</div>
</div>
{/* Content wrapper taking remaining height */}
<div className="flex flex-col flex-1 p-6">
{/* Title & Desc wrapper pushed to top */}
<div className="mb-auto">
<h3 className="mb-2 text-xl font-bold text-white transition-colors group-hover:text-orange-500 line-clamp-2">{p.title}</h3>
<p className="mb-6 text-sm text-gray-400 line-clamp-2">{p.description}</p>
</div>
{/* View Project button pushed to bottom */}
<div className="flex items-center justify-between mt-4 text-sm font-medium text-orange-500">
<span>{t("view_project")}</span>
<ArrowIcon
className={`w-5 h-5 transition-transform group-hover:-translate-y-1 ${locale === "en" ? "group-hover:translate-x-1" : "group-hover:-translate-x-1"}`}
/>
</div>
</div>
</div>
))}
</div>
</section>
);
}

View File

@@ -1,12 +1,16 @@
import React from "react";
import { Network, Code, PenTool, GraduationCap } from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";
export default function Services() {
const t = useTranslations("home.services");
export default function BentoServices() {
return (
<section>
<div className="mb-12 text-center">
<h2 className="mb-4 text-3xl font-bold text-white">راهکارهای یکپارچه</h2>
<p className="text-gray-400">خدمات ما پازلهای تکمیلکننده کسبوکار شما هستند.</p>
<h2 className="mb-4 text-3xl font-bold text-white">{t("title")}</h2>
<p className="text-gray-400">{t("subtitle")}</p>
</div>
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
@@ -17,11 +21,9 @@ export default function BentoServices() {
<div className="p-3 text-orange-500 bg-slate-800 rounded-xl">
<Network className="w-6 h-6" />
</div>
<h3 className="text-2xl font-bold">زیرساخت و امنیت شبکه</h3>
<h3 className="text-2xl font-bold">{t("network.title")}</h3>
</div>
<p className="max-w-md leading-relaxed text-gray-400">
طراحی، پیادهسازی و ایمنسازی شبکههای پیچیده. از کانفیگ روترهای سختافزاری تا مانیتورینگ لایه هفت.
</p>
<p className="max-w-md leading-relaxed text-gray-400">{t("network.desc")}</p>
</div>
<div className="relative w-full h-32 mt-8 overflow-hidden border bg-slate-800/50 rounded-xl border-slate-700/50">
{/* Decorative element representing network */}
@@ -40,9 +42,9 @@ export default function BentoServices() {
<div className="p-2 text-orange-500 rounded-lg bg-slate-800">
<Code className="w-5 h-5" />
</div>
<h3 className="text-xl font-bold">توسعه نرمافزار اختصاصی</h3>
<h3 className="text-xl font-bold">{t("software.title")}</h3>
</div>
<p className="text-sm text-gray-400">ساخت پلتفرمهای ابری قدرتمند و اتوماسیون فرآیندهای سازمانی.</p>
<p className="text-sm text-gray-400">{t("software.desc")}</p>
</div>
{/* Bottom Two Cards */}
@@ -51,19 +53,22 @@ export default function BentoServices() {
<div className="flex flex-col justify-between p-6 transition-colors border bg-slate-900/40 border-slate-800 rounded-2xl hover:border-orange-500/50">
<div className="flex items-center gap-2 mb-2">
<PenTool className="w-5 h-5 text-orange-500" />
<h3 className="text-lg font-bold">هویت بصری</h3>
<h3 className="text-lg font-bold">{t("branding.title")}</h3>
</div>
<p className="mt-2 text-xs text-gray-400">طراحی رابط کاربری (UI/UX) و برندینگ مدرن.</p>
<p className="mt-2 text-xs text-gray-400">{t("branding.desc")}</p>
</div>
{/* Academy Card */}
<div className="relative flex flex-col items-center justify-center p-6 overflow-hidden text-center transition-colors border bg-gradient-to-br from-slate-900 to-slate-800 border-slate-700 rounded-2xl hover:border-orange-500/50">
<div className="absolute inset-0 bg-orange-500/5"></div>
<GraduationCap className="relative z-10 w-8 h-8 mb-3 text-orange-500" />
<h3 className="relative z-10 mb-3 text-lg font-bold">آکادمی</h3>
<button className="relative z-10 px-6 py-2 text-sm text-white transition-colors border rounded-lg bg-slate-800 hover:bg-slate-700 border-slate-600">
ورود
</button>
<h3 className="relative z-10 mb-3 text-lg font-bold">{t("academy.title")}</h3>
<Link
href={"/academy"}
className="relative z-10 px-6 py-2 text-sm text-white transition-colors border rounded-lg bg-slate-800 hover:bg-slate-700 border-slate-600"
>
{t("academy.btn_enter")}
</Link>
</div>
</div>
</div>

View File

@@ -0,0 +1,35 @@
import { useTranslations } from "next-intl";
import HeroButtons from "./HeroButtons";
import HeroTech from "./HeroTech";
export default function Hero() {
const t = useTranslations("home.hero");
return (
<>
<section className="flex flex-col items-center justify-center max-w-4xl px-4 mx-auto text-center">
{/* Top Badge */}
<div className="inline-flex items-center gap-2 px-4 py-2 mb-8 text-sm text-orange-400 border rounded-full border-orange-500/20 bg-orange-500/10">
<span className="w-2 h-2 bg-orange-500 rounded-full animate-pulse"></span>
{t("badge")}
</div>
{/* Main Title */}
<h1 className="mb-6 text-5xl font-bold leading-tight md:text-7xl">
{t("title1")}
<br />
{t("title2")}
</h1>
{/* Subtitle */}
<p className="max-w-2xl mb-10 text-lg leading-relaxed text-gray-400 md:text-xl">{t("subtitle")}</p>
{/* CTA Buttons */}
<div className="flex flex-col items-center gap-4 sm:flex-row">
<HeroButtons />
</div>
</section>
<HeroTech />
</>
);
}

View File

@@ -0,0 +1,31 @@
"use client";
import { handleScrollToId } from "@/utilities/lib/scroll";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { useTranslations, useLocale } from "next-intl";
export default function HeroButtons() {
const t = useTranslations("home.hero");
const locale = useLocale();
// Choose the right chevron based on text direction
const isRtl = locale === "fa" || locale === "ar";
return (
<>
<button
onClick={() => handleScrollToId("portfolios_form")}
className="flex items-center justify-center w-full px-8 py-3 font-medium text-white transition-colors border rounded-lg sm:w-auto border-slate-700 bg-slate-800/50 hover:bg-slate-800"
>
{t("btn_portfolio")}
</button>
<button
onClick={() => handleScrollToId("contact_form")}
className="flex items-center justify-center w-full gap-2 px-8 py-3 font-medium text-white transition-colors bg-orange-600 rounded-lg sm:w-auto hover:bg-orange-500"
>
{t("btn_consulting")}
{isRtl ? <ChevronLeft className="w-4 h-4" /> : <ChevronRight className="w-4 h-4" />}
</button>
</>
);
}

View File

@@ -1,14 +1,12 @@
export default function TechMarquee() {
export default function HeroTech() {
const techs = ["Node.js", "Next.js", "MikroTik", "Figma", "Ollama AI", "Docker & Coolify", ".NET"];
return (
<div className="relative w-full py-4 overflow-hidden border-y border-slate-800/50 bg-slate-900/20">
{/* Gradient masks for fading edges */}
<div className="absolute inset-y-0 right-0 w-32 bg-gradient-to-l from-[#0B1120] to-transparent z-10"></div>
<div className="absolute inset-y-0 left-0 w-32 bg-gradient-to-r from-[#0B1120] to-transparent z-10"></div>
<div className="flex flex-wrap justify-center gap-12 px-4 opacity-50 whitespace-nowrap">
{/* In a real app, you'd use a CSS animation here for infinite scrolling */}
{techs.map((tech, index) => (
<div key={index} className="flex items-center gap-2 text-lg font-medium text-gray-400">
<span className="flex items-center justify-center w-6 h-6 text-xs border rounded bg-slate-800 border-slate-700"></span>

View File

@@ -1,6 +1,6 @@
export default function Consultation() {
return (
<div className="w-full max-w-6xl px-5 mx-auto mt-10">
<div className="w-full max-w-6xl px-5 mx-auto mt-10" id="contact_form">
<div className="relative grid grid-cols-1 gap-10 p-10 overflow-hidden border border-b-0 shadow-2xl bg-panel border-border rounded-t-3xl lg:grid-cols-2">
<div className="absolute -top-12 left-1/2 -translate-x-1/2 w-[80%] h-[100px] bg-[radial-gradient(ellipse,rgba(199,92,67,0.15),transparent_70%)] pointer-events-none" />

View File

@@ -1,8 +1,14 @@
"use client";
import { useState, useEffect } from "react";
import { useTranslations, useLocale } from "next-intl";
import { handleScrollToId } from "@/utilities/lib/scroll";
export default function Hero() {
const t = useTranslations("network.hero");
const locale = useLocale();
const isRTL = locale === "fa" || locale === "ar";
const [activeIndicators, setActiveIndicators] = useState({ sys: true, act: false, pwr: true });
const [activeSfp, setActiveSfp] = useState([true, false]);
const [activeRj45, setActiveRj45] = useState<boolean[]>(Array(24).fill(false));
@@ -22,18 +28,19 @@ export default function Hero() {
return (
<div className="flex flex-col items-center text-center mt-8 md:mt-16 px-4 md:px-5 w-full max-w-[1440px] mx-auto">
<div className="bg-accent/10 border border-accent/20 text-accent px-4 py-1.5 rounded-full text-[10px] md:text-xs mb-4 md:mb-6">
مهندسی شبکه و امنیت
{t("badge")}
</div>
<h1 className="mb-4 text-3xl font-bold leading-tight sm:text-4xl md:text-5xl lg:text-6xl">
زیرساخت شبکه <span className="block mt-1 text-accent md:mt-2">پایدار و امن</span>
{t("title1")} <span className="block mt-1 text-accent md:mt-2">{t("title2")}</span>
</h1>
<p className="max-w-2xl px-2 mb-8 text-sm text-muted md:text-base md:mb-12">
طراحی، پیادهسازی و مدیریت زیرساختهای شبکه پیچیده با تمرکز بر امنیت، پایداری و مقیاسپذیری
</p>
<p className="max-w-2xl px-2 mb-8 text-sm text-muted md:text-base md:mb-12">{t("subtitle")}</p>
{/* روتر با max-width برای محدود کردن سایز در دسکتاپ و overflow-x-auto برای موبایل */}
<div className="relative mb-12 md:mb-16 w-full max-w-4xl mx-auto shadow-[0_15px_40px_-10px_rgba(0,0,0,0.8)] md:shadow-[0_25px_60px_-10px_rgba(0,0,0,0.9)] rounded-2xl overflow-x-auto pb-4 scrollbar-thin scrollbar-thumb-[#2a2d36] scrollbar-track-transparent">
{/* Hardware Interface - Forced LTR to keep technical layout consistent in all languages */}
<div
dir="ltr"
className="relative mb-12 md:mb-16 w-full max-w-4xl mx-auto shadow-[0_15px_40px_-10px_rgba(0,0,0,0.8)] md:shadow-[0_25px_60px_-10px_rgba(0,0,0,0.9)] rounded-2xl overflow-x-auto pb-4 scrollbar-thin scrollbar-thumb-[#2a2d36] scrollbar-track-transparent"
>
<div className="min-w-[700px] w-full bg-[#181a1f] border border-[#2a2d36] rounded-2xl p-6 md:p-8 flex flex-col relative z-10">
<div className="flex items-center justify-between mb-3">
<div className="text-[#5c616f] font-mono text-xs md:text-sm tracking-wider">+CRS326-24G-2S</div>
@@ -97,11 +104,17 @@ export default function Hero() {
</div>
<div className="flex flex-col w-full gap-3 mb-12 sm:flex-row sm:w-auto md:gap-4 md:mb-16">
<button className="justify-center w-full px-6 py-3 text-sm font-bold text-center transition border rounded-lg sm:w-auto border-border text-muted hover:text-foreground hover:bg-white/5">
مشاهده نمونهکارها
<button
onClick={() => handleScrollToId("portfolios")}
className="justify-center w-full px-6 py-3 text-sm font-bold text-center transition border rounded-lg sm:w-auto border-border text-muted hover:text-foreground hover:bg-white/5"
>
{t("viewPortfolio")}
</button>
<button className="w-full sm:w-auto px-6 py-3 rounded-lg text-sm font-bold border border-border bg-panel text-foreground hover:bg-[#1a2030] hover:border-accent transition flex items-center justify-center gap-2">
دریافت مشاوره رایگان <span>&#x2039;</span>
<button
onClick={() => handleScrollToId("footer")}
className="w-full sm:w-auto px-6 py-3 rounded-lg text-sm font-bold border border-border bg-panel text-foreground hover:bg-[#1a2030] hover:border-accent transition flex items-center justify-center gap-2"
>
{t("freeConsultation")} <span>{""}</span>
</button>
</div>
</div>

View File

@@ -1,141 +1,75 @@
"use client";
import { useRef, useState } from "react";
import { useTranslations, useLocale } from "next-intl";
import { Portfolio } from "@/utilities/types/portfolio.type";
export default function Projects() {
const projects = [
{
category: "شبکه بی‌سیم",
client: "هتل پارسیان",
title: "WiFi سراسری هتل",
desc: "پوشش کامل WiFi با امنیت بالا و تفکیک شبکه مهمان",
tag: "Wireless 📡",
year: "۲۰۲۵",
},
{
category: "امنیتی",
client: "بانک سامان",
title: "پیاده‌سازی فایروال",
desc: "نصب و پیکربندی تجهیزات Fortinet جهت ارتقای امنیت شبکه",
tag: "Security 🛡️",
year: "۲۰۲۴",
},
{
category: "سازمانی",
client: "شرکت مپنا",
title: "زیرساخت دیتاسنتر",
desc: "طراحی و اجرای پسیو و اکتیو شبکه دیتاسنتر مرکزی",
tag: "Data Center 🏢",
year: "۲۰۲۴",
},
{
category: "ارتباطات",
client: "بیمه پاسارگاد",
title: "ارتباط شعب",
desc: "برقراری ارتباط امن بین شعب از طریق تونل‌های IPsec",
tag: "VPN 🔗",
year: "۲۰۲۳",
},
{
category: "امنیتی",
client: "بانک سامان",
title: "پیاده‌سازی فایروال",
desc: "نصب و پیکربندی تجهیزات Fortinet جهت ارتقای امنیت شبکه",
tag: "Security 🛡️",
year: "۲۰۲۴",
},
{
category: "سازمانی",
client: "شرکت مپنا",
title: "زیرساخت دیتاسنتر",
desc: "طراحی و اجرای پسیو و اکتیو شبکه دیتاسنتر مرکزی",
tag: "Data Center 🏢",
year: "۲۰۲۴",
},
{
category: "ارتباطات",
client: "بیمه پاسارگاد",
title: "ارتباط شعب",
desc: "برقراری ارتباط امن بین شعب از طریق تونل‌های IPsec",
tag: "VPN 🔗",
year: "۲۰۲۳",
},
];
export default function Projects({ data }: { data: Portfolio[] }) {
const t = useTranslations("network.projects");
const locale = useLocale();
const isRtl = locale === "fa" || locale === "ar";
const sliderRef = useRef<any>(null);
const sliderRef = useRef<HTMLDivElement>(null);
const [isDragging, setIsDragging] = useState(false);
const [startX, setStartX] = useState(0);
const [scrollLeft, setScrollLeft] = useState(0);
const scrollAmount = 320;
const nextSlide = () => {
if (sliderRef.current) {
sliderRef.current.scrollBy({ left: -320, behavior: "smooth" });
sliderRef.current.scrollBy({ left: isRtl ? -scrollAmount : scrollAmount, behavior: "smooth" });
}
};
const prevSlide = () => {
if (sliderRef.current) {
sliderRef.current.scrollBy({ left: 320, behavior: "smooth" });
sliderRef.current.scrollBy({ left: isRtl ? scrollAmount : -scrollAmount, behavior: "smooth" });
}
};
// Mouse drag handlers for desktop
const handleMouseDown = (e: any) => {
const handleMouseDown = (e: React.MouseEvent) => {
setIsDragging(true);
setStartX(e.pageX - sliderRef.current.offsetLeft);
setScrollLeft(sliderRef.current.scrollLeft);
if (sliderRef.current) {
setStartX(e.pageX - sliderRef.current.offsetLeft);
setScrollLeft(sliderRef.current.scrollLeft);
}
};
const handleMouseLeave = () => {
setIsDragging(false);
};
const handleMouseLeave = () => setIsDragging(false);
const handleMouseUp = () => setIsDragging(false);
const handleMouseUp = () => {
setIsDragging(false);
};
const handleMouseMove = (e: any) => {
if (!isDragging) return;
const handleMouseMove = (e: React.MouseEvent) => {
if (!isDragging || !sliderRef.current) return;
e.preventDefault();
const x = e.pageX - sliderRef.current.offsetLeft;
const walk = (x - startX) * 2; // Scroll speed multiplier
const walk = (x - startX) * 2;
sliderRef.current.scrollLeft = scrollLeft - walk;
};
return (
<div className="flex justify-center w-full px-5 py-20 bg-bg">
<div className="w-full max-w-6xl">
<div className="mb-8 text-right">
<h2 className="mb-2 text-3xl font-bold">پروژههای شبکه</h2>
<p className="text-sm text-muted">نمونهای از پروژههای شبکه اجرا شده</p>
</div>
const getYear = (date: Date | string) => {
return new Date(date).toLocaleDateString(locale, { year: "numeric" });
};
<div className="flex flex-wrap items-center justify-between gap-4 mb-8">
<div className="flex gap-2">
{["همه", "سازمانی", "امنیتی", "بی‌سیم"].map((tab, i) => (
<button
key={i}
className={`px-4 py-2 rounded-lg text-sm border transition ${
i === 0 ? "bg-accent text-white border-accent" : "bg-transparent border-border text-muted hover:text-white"
}`}
>
{tab}
</button>
))}
return (
<div className="flex justify-center w-full px-5 py-20 bg-bg" id="portfolios">
<div className="w-full max-w-6xl">
<div className="flex items-end justify-between mb-8">
<div className="text-start">
<h2 className="mb-2 text-3xl font-bold">{t("title")}</h2>
<p className="text-sm text-muted">{t("subtitle")}</p>
</div>
<div className="flex gap-2">
<button className="px-4 py-2 text-sm bg-transparent border rounded-lg border-border text-foreground hover:bg-card">
مشاهده همه پروژهها
</button>
<button onClick={prevSlide} className="flex items-center justify-center transition border rounded-lg w-9 h-9 border-border hover:bg-card">
&lt;
{"<"}
</button>
<button onClick={nextSlide} className="flex items-center justify-center transition border rounded-lg w-9 h-9 border-border hover:bg-card">
&gt;
{">"}
</button>
</div>
</div>
{/* Slider Container with Drag Events */}
{/* Slider Container */}
<div
ref={sliderRef}
onMouseDown={handleMouseDown}
@@ -146,22 +80,25 @@ export default function Projects() {
isDragging ? "cursor-grabbing snap-none" : "cursor-grab snap-x snap-mandatory scroll-smooth"
}`}
>
{projects.map((proj, i) => (
{data.map((p) => (
<div
key={i}
// Changed mobile width to 85% to show 1.5 items
className="bg-card border border-border rounded-2xl p-6 relative overflow-hidden shadow-2xl flex flex-col group min-h-[200px] shrink-0 snap-start select-none w-[85%] sm:w-[calc(50%-10px)] lg:w-[calc(25%-15px)]"
key={p.id}
className="bg-card border border-border rounded-2xl p-6 relative overflow-hidden shadow-2xl flex flex-col group min-h-[200px] shrink-0 snap-start select-none w-[85%] sm:w-[calc(50%-10px)] lg:w-[calc(25%-15px)] text-start"
>
<div className="absolute top-0 right-0 w-full h-full bg-[radial-gradient(circle_at_top_right,rgba(199,92,67,0.15),transparent_60%)] pointer-events-none" />
<div
className={`absolute top-0 w-full h-full pointer-events-none ${isRtl ? "left-0 bg-[radial-gradient(circle_at_top_left,rgba(199,92,67,0.15),transparent_60%)]" : "right-0 bg-[radial-gradient(circle_at_top_right,rgba(199,92,67,0.15),transparent_60%)]"}`}
/>
<div className="flex items-center justify-between mb-5 text-xs">
<span className="px-3 py-1 border rounded-full text-accent bg-accent/10 border-accent/30">{proj.category}</span>
<span className="text-muted">{proj.client}</span>
<span className="px-3 py-1 border rounded-full text-accent bg-accent/10 border-accent/30">{t(`categories.${p.category}`)}</span>
<span className="text-muted">{p.employer}</span>
</div>
<h3 className="mb-3 text-lg">{proj.title}</h3>
<p className="flex-grow mb-5 text-sm text-muted">{proj.desc}</p>
<div className="flex items-center justify-between pt-4 mt-auto text-xs border-t border-border text-muted">
<span>{proj.tag}</span>
<span>{proj.year}</span>
<h3 className="mb-3 text-lg">{p.title}</h3>
<p className="flex-grow mb-5 text-sm text-muted line-clamp-3">{p.description}</p>
<div className="flex items-center justify-end pt-4 mt-auto text-xs border-t border-border text-muted">
<span>{getYear(new Date(p.createdAt))}</span>
</div>
</div>
))}

View File

@@ -1,29 +1,35 @@
export default function ServicesSection() {
const services = [
{ title: "شبکه بی‌سیم", subtitle: "WIFI Enterprise", icon: "📶" },
{ title: "امنیت", subtitle: "IDS/IPS و Firewall", icon: "🛡️" },
{ title: "طراحی شبکه", subtitle: "معماری و توپولوژی", icon: "📐" },
{ title: "پشتیبانی", subtitle: "Support ۲۴/۷", icon: "🎧" },
{ title: "VPN & WAN", subtitle: "اتصال بین سایت", icon: "🔗" },
{ title: "مانیتورینگ", subtitle: "نظارت ۲۴/۷", icon: "📈" },
"use client";
import { useTranslations } from "next-intl";
export default function Services() {
const t = useTranslations("network.services");
const serviceItems = [
{ key: "wifi", icon: "📶" },
{ key: "security", icon: "🛡️" },
{ key: "design", icon: "📐" },
{ key: "support", icon: "🎧" },
{ key: "vpn", icon: "🔗" },
{ key: "monitoring", icon: "📈" },
];
return (
<section className="relative w-full px-5 pt-20 pb-10 border-t border-border/50 bg-bg">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[80%] h-[400px] bg-[radial-gradient(ellipse_at_center,rgba(160,45,20,0.15)_0%,transparent_60%)] pointer-events-none" />
<div className="relative z-10 max-w-6xl mx-auto">
<div className="mb-12 text-right">
<h2 className="mb-2 text-3xl font-bold">خدمات شبکه</h2>
<p className="text-sm text-muted">راهکارهای جامع برای زیرساخت شبکه سازمان شما</p>
<div className="mb-12 text-start">
<h2 className="mb-2 text-3xl font-bold">{t("sectionTitle")}</h2>
<p className="text-sm text-muted">{t("sectionSubtitle")}</p>
</div>
<div className="grid grid-cols-1 gap-5 mb-20 md:grid-cols-2 lg:grid-cols-3">
{services.map((svc, i) => (
{serviceItems.map((svc, i) => (
<div key={i} className="flex flex-col items-center p-10 text-center border bg-card rounded-xl border-border">
<div className="flex items-center justify-center mb-5 text-2xl w-14 h-14 rounded-2xl bg-gradient-to-br from-accent-light to-accent">
{svc.icon}
</div>
<h3 className="text-lg mb-1.5">{svc.title}</h3>
<p className="font-sans text-xs tracking-wide text-muted">{svc.subtitle}</p>
<h3 className="text-lg mb-1.5">{t(`items.${svc.key}.title`)}</h3>
<p className="font-sans text-xs tracking-wide text-muted">{t(`items.${svc.key}.subtitle`)}</p>
</div>
))}
</div>

View File

@@ -1,4 +1,10 @@
"use client";
import { useTranslations } from "next-intl";
export default function Technologies() {
const t = useTranslations("network.technologies");
const techs = [
{
name: "VPN",
@@ -53,9 +59,9 @@ export default function Technologies() {
return (
<div className="w-full bg-[#0e131d] border-t border-white/5 py-20 px-5 flex justify-center">
<div className="w-full max-w-6xl mx-auto">
<div className="mb-12 text-right">
<h2 className="mb-2 text-3xl font-bold">تکنولوژیها</h2>
<p className="text-sm text-muted">تجهیزات و فناوریهای مورد استفاده</p>
<div className="mb-12 text-start">
<h2 className="mb-2 text-3xl font-bold">{t("title")}</h2>
<p className="text-sm text-muted">{t("subtitle")}</p>
</div>
<div className="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-6">

View File

@@ -1,34 +1,118 @@
"use client";
import { useState } from "react";
import Link from "next/link";
import { useTranslations, useLocale } from "next-intl";
import { usePathname, useRouter } from "next/navigation";
export default function Navbar() {
const [isOpen, setIsOpen] = useState(false);
const t = useTranslations("navbar");
const locale = useLocale();
const pathname = usePathname();
const router = useRouter();
const languages = [
{
code: "fa",
label: "فارسی",
icon: (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480" className="w-5 h-4 rounded-sm">
<path fill="#239f40" d="M0 0h640v160H0z" />
<path fill="#fff" d="M0 160h640v160H0z" />
<path fill="#da0000" d="M0 320h640v160H0z" />
<text x="320" y="265" fontSize="70" textAnchor="middle" fill="#da0000" fontFamily="sans-serif" fontWeight="bold">
</text>
</svg>
),
},
{
code: "en",
label: "English",
icon: (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480" className="w-5 h-4 rounded-sm">
<path fill="#012169" d="M0 0h640v480H0z" />
<path fill="#FFF" d="m75 0 244 181L562 0h78v62L400 241l240 178v61h-80L320 301 81 480H0v-60l239-178L0 64V0h75z" />
<path
fill="#C8102E"
d="m424 281 216 159v40L369 281h55zm-184 20 6 35L22 480H0v-50l240-129zM640 0v3L391 191l2-44L590 0h50zM0 0l239 176h-60L0 42V0z"
/>
<path fill="#FFF" d="M241 0v480h160V0H241zM0 160v160h640V160H0z" />
<path fill="#C8102E" d="M0 193v96h640v-96H0zM273 0v480h96V0h-96z" />
</svg>
),
},
{
code: "ar",
label: "العربية",
icon: (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480" className="w-5 h-4 rounded-sm">
<path fill="#138808" d="M0 0h640v480H0z" />
<path
fill="#fff"
d="M433.8 193c-11.8 11.2-30.8 14-43.5 6.7-10.7-5.8-15.6-18.4-11.7-29.6 4-11.4 17.5-18 29-14.4 9 2.7 15.6 10.7 17 20 1 6.5-2 13-6.6 17.8l-12.7-12.2c2.5-2.5 4.3-6.3 3-10-1.5-4-6-6-10-5-4.4 1-7.2 5.5-6.5 10 .8 4.7 5.5 8.2 10.2 8l10.4-10-10.3-10c-8 1.4-12.5 10-9.8 17.6 2.5 7.2 10.6 10.8 17.8 8.4 5.7-1.8 10-6.8 11.4-12.7.8-3.7.3-7.5-1.5-10.8-2.6-4.8-7.5-8-13-8.8-7.8-1-15.5 2.5-19.8 9-4.8 7.3-4.7 17.3.5 24.3 6.6 8.8 20 11.3 29.8 5.6 7-4 12-10.8 14-18.8z"
/>
<path fill="#fff" d="M120 330h400v15H120zM480 315l20 20-20 20v-40z" />
</svg>
),
},
];
const handleLanguageChange = (newLocale: string) => {
const newPath = pathname.replace(new RegExp(`^/${locale}`), `/${newLocale}`);
router.replace(newPath);
};
return (
<header className="sticky top-0 z-50 border-b backdrop-blur-lg bg-bg/80 border-white/5">
<div className="flex items-center justify-between px-6 py-4 mx-auto max-w-7xl">
{/* Logo */}
<div className="text-xl font-bold">
رابین <span className="text-accent">شبکه</span>
</div>
<Link href={`/${locale}`} className="text-xl font-bold">
{t("logo1")} <span className="text-accent">{t("logo2")}</span>
</Link>
{/* Desktop Navigation */}
<nav className="hidden md:block">
<ul className="flex gap-8 text-sm text-muted">
<li className="transition cursor-pointer hover:text-foreground">صفحه اصلی</li>
<li className="transition cursor-pointer hover:text-foreground">خدمات</li>
<li className="transition cursor-pointer hover:text-foreground">تکنولوژی</li>
<li className="transition cursor-pointer hover:text-foreground">فرآیند</li>
<li className="transition cursor-pointer hover:text-foreground">پروژهها</li>
<li>
<Link href={`/${locale}`} className="transition cursor-pointer hover:text-foreground">
{t("home")}
</Link>
</li>
<li>
<Link href={`/${locale}/software`} className="transition cursor-pointer hover:text-foreground">
{t("software")}
</Link>
</li>
<li>
<Link href={`/${locale}/network`} className="transition cursor-pointer hover:text-foreground">
{t("network")}
</Link>
</li>
<li>
<Link href={`/${locale}/academy`} className="transition cursor-pointer hover:text-foreground">
{t("academy")}
</Link>
</li>
</ul>
</nav>
{/* Desktop Actions & Mobile Menu Toggle */}
<div className="flex items-center gap-3">
<div className="hidden gap-3 md:flex">
<button className="border border-border px-3 py-1.5 rounded-md text-sm hover:bg-card transition">EN</button>
<button className="border border-border px-3 py-1.5 rounded-md text-sm hover:bg-card transition"></button>
<div className="hidden gap-2 md:flex">
{languages.map((language) => (
<button
key={language.code}
onClick={() => handleLanguageChange(language.code)}
className={`border px-3 py-1.5 rounded-md flex items-center justify-center transition ${
locale === language.code ? "border-accent bg-accent/10" : "border-border hover:bg-card"
}`}
title={language.label}
>
{language.icon}
</button>
))}
</div>
{/* Mobile Menu Button */}
@@ -51,17 +135,42 @@ export default function Navbar() {
<div className="border-t md:hidden border-white/5 bg-bg/95 backdrop-blur-lg">
<nav className="px-6 py-4">
<ul className="flex flex-col gap-4 text-sm text-muted">
<li className="transition cursor-pointer hover:text-foreground">صفحه اصلی</li>
<li className="transition cursor-pointer hover:text-foreground">خدمات</li>
<li className="transition cursor-pointer hover:text-foreground">تکنولوژی</li>
<li className="transition cursor-pointer hover:text-foreground">فرآیند</li>
<li className="transition cursor-pointer hover:text-foreground">پروژهها</li>
<li>
<Link href={`/${locale}`} className="transition cursor-pointer hover:text-foreground" onClick={() => setIsOpen(false)}>
{t("home")}
</Link>
</li>
<li>
<Link href={`/${locale}/software`} className="transition cursor-pointer hover:text-foreground" onClick={() => setIsOpen(false)}>
{t("software")}
</Link>
</li>
<li>
<Link href={`/${locale}/network`} className="transition cursor-pointer hover:text-foreground" onClick={() => setIsOpen(false)}>
{t("network")}
</Link>
</li>
<li>
<Link href={`/${locale}/academy`} className="transition cursor-pointer hover:text-foreground" onClick={() => setIsOpen(false)}>
{t("academy")}
</Link>
</li>
</ul>
{/* Mobile Actions */}
<div className="flex gap-3 pt-4 mt-6 border-t border-white/5">
<button className="border border-border px-3 py-1.5 rounded-md text-sm hover:bg-card transition flex-1">EN</button>
<button className="border border-border px-3 py-1.5 rounded-md text-sm hover:bg-card transition flex-1"></button>
{/* Mobile Language Switcher */}
<div className="flex gap-2 pt-4 mt-6 border-t border-white/5">
{languages.map((language) => (
<button
key={language.code}
onClick={() => handleLanguageChange(language.code)}
className={`border px-3 py-1.5 flex items-center justify-center gap-2 rounded-md text-sm transition flex-1 ${
locale === language.code ? "border-accent bg-accent/10 text-foreground" : "border-border hover:bg-card text-muted"
}`}
>
{language.icon}
{language.label}
</button>
))}
</div>
</nav>
</div>

View File

@@ -0,0 +1,78 @@
import { useLocale, useTranslations } from "next-intl";
import Link from "next/link";
import FooterForm from "./FooterForm";
export default function Footer() {
const t = useTranslations("footer");
const locale = useLocale();
const isRtl = locale === "fa" || locale === "ar";
return (
<footer id="footer">
<div className="w-full max-w-6xl px-5 mx-auto mt-10" id="contact_form">
<div className="relative grid grid-cols-1 gap-10 p-10 overflow-hidden border border-b-0 shadow-2xl bg-panel border-border rounded-t-3xl lg:grid-cols-2">
<div className="absolute -top-12 left-1/2 -translate-x-1/2 w-[80%] h-[100px] bg-[radial-gradient(ellipse,rgba(199,92,67,0.15),transparent_70%)] pointer-events-none" />
<div className="relative z-10">
<span className="inline-block text-accent border border-accent/50 bg-accent/5 px-3 py-1.5 rounded-full text-xs mb-5">{t("badge")}</span>
<h2 className="mb-4 text-3xl font-bold">{t("title")}</h2>
<p className="mb-8 text-sm leading-relaxed text-muted">{t("description")}</p>
<ul className="space-y-4 text-sm text-muted">
<li className="flex items-center gap-2">
<span className="font-bold text-accent"></span> {t("feature1")}
</li>
<li className="flex items-center gap-2">
<span className="font-bold text-accent"></span> {t("feature2")}
</li>
<li className="flex items-center gap-2">
<span className="font-bold text-accent"></span> {t("feature3")}
</li>
</ul>
</div>
<FooterForm />
</div>
</div>
<div className="w-full max-w-6xl px-5 mx-auto">
<div className="flex flex-col items-start justify-between gap-8 p-8 border border-t-0 bg-panel border-border rounded-b-3xl md:flex-row">
<div className="max-w-[300px]">
<h3 className="mb-3 text-xl font-bold">{t("brand")}</h3>
<p className="text-xs leading-relaxed text-muted">{t("brandDesc")}</p>
</div>
<div className="flex flex-col gap-3">
<Link href={`/${locale}`} className="text-sm text-muted hover:text-foreground">
{t("links.home")}
</Link>
<Link href={`/${locale}/software`} className="text-sm text-muted hover:text-foreground">
{t("links.software")}
</Link>
<Link href={`/${locale}/network`} className="text-sm text-muted hover:text-foreground">
{t("links.network")}
</Link>
<Link href={`/${locale}/academy`} className="text-sm text-muted hover:text-foreground">
{t("links.academy")}
</Link>
</div>
<div className="flex flex-col gap-3 text-sm text-muted" dir={isRtl ? "rtl" : "ltr"}>
<a href="tel:+989129739554" className="flex items-center gap-2 hover:text-foreground" dir="ltr">
📱 0912 973 9554
</a>
<a href="mailto:robinnetworkltd@gmail.com" className="flex items-center gap-2 hover:text-foreground" dir="ltr">
robinnetworkltd@gmail.com
</a>
<Link
href={`/${locale}/projects`}
className="px-4 py-2 mt-2 text-center transition border rounded-lg border-border text-foreground hover:bg-card"
>
{t("allProjects")}
</Link>
</div>
</div>
<div className="py-5 text-xs text-center text-muted" dir="ltr">
{t("copyright")}
</div>
</div>
</footer>
);
}

View File

@@ -0,0 +1,70 @@
"use client";
import { useLocale, useTranslations } from "next-intl";
import { useState } from "react";
import { ChevronLeft, ChevronRight } from "lucide-react";
export default function FooterForm() {
const [fullName, setFullName] = useState<string>("");
const [phone, setPhone] = useState<string>("");
const [type, setType] = useState<string>("");
const [text, setText] = useState<string>("");
const t = useTranslations("footer");
const locale = useLocale();
const isRtl = locale === "fa" || locale === "ar";
const ArrowIcon = isRtl ? ChevronLeft : ChevronRight;
return (
<div className="z-10 p-6 border bg-white/5 border-border rounded-2xl">
<div className="flex flex-col gap-4 mb-4 md:flex-row">
<div className="flex flex-col flex-1 gap-2">
<label className="text-xs text-muted">{t("form.nameLabel")}</label>
<input
type="text"
className="px-4 py-3 text-sm border rounded-lg outline-none bg-bg border-border text-foreground focus:border-muted"
placeholder={t("form.namePlaceholder")}
value={fullName}
onChange={({ target }) => setFullName(target.value)}
/>
</div>
<div className="flex flex-col flex-1 gap-2">
<label className="text-xs text-muted">{t("form.phoneLabel")}</label>
<input
type="text"
className="px-4 py-3 text-sm border rounded-lg outline-none bg-bg border-border text-foreground focus:border-muted"
placeholder={t("form.phonePlaceholder")}
dir="ltr"
value={phone}
onChange={({ target }) => setPhone(target.value)}
/>
</div>
</div>
<div className="flex flex-col gap-2 mb-4">
<label className="text-xs text-muted">{t("form.serviceLabel")}</label>
<select
value={type}
onChange={({ target }) => setType(target.value)}
className="px-4 py-3 text-sm border rounded-lg outline-none bg-bg border-border text-foreground focus:border-muted"
>
<option value="">{t("form.servicePlaceholder")}</option>
<option value="network">{t("form.serviceNetwork")}</option>
<option value="software">{t("form.serviceSoftware")}</option>
<option value="marketing">{t("form.serviceMarketing")}</option>
</select>
</div>
<div className="flex flex-col gap-2 mb-4">
<label className="text-xs text-muted">{t("form.descLabel")}</label>
<textarea
className="h-24 px-4 py-3 text-sm border rounded-lg outline-none resize-none bg-bg border-border text-foreground focus:border-muted"
placeholder={t("form.descPlaceholder")}
value={text}
onChange={({ target }) => setText(target.value)}
/>
</div>
<div className="flex justify-end">
<button className="flex items-center gap-2 px-6 py-3 mt-2 text-sm transition border rounded-lg border-border hover:border-muted w-fit">
{t("form.submit")} <ArrowIcon className="w-4 h-4" />
</button>
</div>
</div>
);
}

View File

@@ -1,55 +1,95 @@
import React from "react";
import { List, BarChart2, Tag, Lightbulb, CheckCircle2, Share2, Copy } from "lucide-react";
"use client";
import { useEffect, useState } from "react";
import { List, BarChart2, Share2, Copy, Check } from "lucide-react";
import { Blog, BlogTranslation, HeadingBlock } from "@/utilities/types/blog.type";
import Image from "next/image";
import { BACKEND_URL } from "@/utilities/constants/urls.constant";
import { useTranslations } from "next-intl";
import { handleScrollToId } from "@/utilities/lib/scroll";
interface Props {
blog: Blog;
translation: BlogTranslation;
}
export default function ArticleBody({ blog, translation }: Props) {
const t = useTranslations("academy.single");
const [readingProgress, setReadingProgress] = useState(0);
const [isCopied, setIsCopied] = useState(false);
useEffect(() => {
const handleScroll = () => {
const scrollY = window.scrollY;
const docHeight = document.documentElement.scrollHeight;
const winHeight = window.innerHeight;
const scrollPercent = scrollY / (docHeight - winHeight);
setReadingProgress(Math.min(100, Math.max(0, scrollPercent * 100)));
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(window.location.href);
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
} catch (err) {
null;
}
};
const handleShare = async () => {
if (navigator.share) {
try {
await navigator.share({
title: translation.title,
url: window.location.href,
});
} catch (err) {
null;
}
} else {
handleCopy();
}
};
const headings = translation.editor.filter((block): block is HeadingBlock => block.type === "heading");
export default function ArticleBody() {
return (
<div className="flex flex-col gap-12 mb-20 lg:flex-row">
{/* Sidebar */}
<aside className="w-full lg:w-1/4 shrink-0">
<div className="sticky space-y-6 top-24">
{/* Table of Contents */}
<div className="p-5 border bg-slate-900/50 border-slate-800 rounded-xl">
<div className="flex items-center gap-2 mb-4 text-sm font-medium text-gray-300">
<List className="w-4 h-4 text-orange-500" />
فهرست مطالب
{headings.length > 0 && (
<div className="p-5 border bg-slate-900/50 border-slate-800 rounded-xl">
<div className="flex items-center gap-2 mb-4 text-sm font-medium text-gray-300">
<List className="w-4 h-4 text-orange-500" />
{t("tableOfContents")}
</div>
<ul className="space-y-3 text-sm text-gray-500">
{headings.map((heading, index) => (
<li
key={index}
onClick={() => handleScrollToId(`blog_heading_${heading.sort}`)}
className="transition-colors cursor-pointer hover:text-orange-400"
>
{heading.content}
</li>
))}
</ul>
</div>
<ul className="space-y-3 text-sm text-gray-500">
<li className="transition-colors cursor-pointer hover:text-orange-400">مسئله اصلی: نمادها، نه مقدار</li>
<li className="transition-colors cursor-pointer hover:text-orange-400">مرحله اضافی مغز</li>
<li className="transition-colors cursor-pointer hover:text-orange-400">دیسکلکولیا چیست؟</li>
</ul>
</div>
)}
{/* Reading Progress */}
<div className="p-5 border bg-slate-900/50 border-slate-800 rounded-xl">
<div className="flex items-center gap-2 mb-4 text-sm font-medium text-gray-300">
<BarChart2 className="w-4 h-4 text-orange-500" />
پیشرفت مطالعه
{t("readingProgress")}
</div>
<div className="w-full bg-slate-800 rounded-full h-1.5 mb-2">
<div className="bg-orange-500 h-1.5 rounded-full w-[35%]"></div>
</div>
</div>
{/* Tags */}
<div className="p-5 border bg-slate-900/50 border-slate-800 rounded-xl">
<div className="flex items-center gap-2 mb-4 text-sm font-medium text-gray-300">
<Tag className="w-4 h-4 text-orange-500" />
برچسبها
</div>
<div className="flex flex-wrap gap-2">
<span className="text-xs px-2.5 py-1 rounded-md border border-slate-700 text-gray-400 hover:text-white hover:border-orange-500/50 transition-colors cursor-pointer">
دیسکلکولیا
</span>
<span className="text-xs px-2.5 py-1 rounded-md border border-slate-700 text-gray-400 hover:text-white hover:border-orange-500/50 transition-colors cursor-pointer">
علوم اعصاب
</span>
<span className="text-xs px-2.5 py-1 rounded-md border border-slate-700 text-gray-400 hover:text-white hover:border-orange-500/50 transition-colors cursor-pointer">
یادگیری
</span>
<span className="text-xs px-2.5 py-1 rounded-md border border-slate-700 text-gray-400 hover:text-white hover:border-orange-500/50 transition-colors cursor-pointer">
کودکان
</span>
<div className="w-full bg-slate-800 rounded-full h-1.5 mb-2 overflow-hidden">
<div className="bg-orange-500 h-1.5 rounded-full transition-all duration-150 ease-out" style={{ width: `${readingProgress}%` }}></div>
</div>
</div>
</div>
@@ -57,94 +97,67 @@ export default function ArticleBody() {
{/* Main Content */}
<article className="w-full space-y-8 leading-8 text-gray-300 lg:w-3/4">
<p>
برخی کودکان در یادگیری ریاضی مشکل دارند و حل مسائل برایشان سخت است، در حالی که همکلاسیهایشان همان مسائل را راحت انجام میدهند. سالهاست که
پژوهشگران متوجه شدهاند بخشی از این تفاوت به چیزی به نام «اختلال یادگیری ریاضی» یا <strong>دیسکلکولیا</strong> مربوط میشود.
</p>
<p>
این وضعیت باعث میشود کودک در درک و کار با اعداد مشکل داشته باشد، حتی اگر هوش کلی او طبیعی باشد. اکنون مطالعهای تازه با استفاده از
تصویربرداری مغزی، سرنخهای دقیقتری از دلیل این اختلافها ارائه کرده است.
</p>
{/* Highlight Box */}
<div className="flex items-start gap-4 p-6 border bg-orange-500/5 border-orange-500/20 rounded-xl">
<Lightbulb className="w-6 h-6 mt-1 text-orange-500 shrink-0" />
<p className="m-0 font-medium leading-relaxed text-orange-100/90">
دانشمندان نشان دادهاند کودکانی که اختلال یادگیری ریاضی دارند، هنگام کار با نمادهای عددی رویکرد متفاوتی نسبت به دیگر کودکان دارند.
</p>
</div>
<p>
آنها در پاسخدادن احتیاط کمتری نشان میدهند و وقتی اشتباه میکنند، برخلاف همسالانشان سرعت خود را کم نمیکنند تا عملکردشان را اصلاح کنند.
اما نکته جالب اینجاست: وقتی همان مسئلهها به جای عدد، با «نقطه» نمایش داده شد، این تفاوتها از بین رفت.
</p>
<h2 className="pr-3 mt-12 mb-6 text-2xl font-bold text-white border-r-4 border-orange-500">مسئله اصلی: نمادها، نه مقدار</h2>
<p>
این ایده که «نمادهای عددی» برای برخی کودکان چالشبرانگیز است، موضوع تازهای نیست. <strong>برت دسمت</strong>، عصبپژوه آموزشی در دانشگاه
کییو لوون بلژیک که در پژوهش حضور نداشت، میگوید سالهاست مشاهده میشود که مشکل اصلی این کودکان بیشتر در «پردازش نمادین» است؛ یعنی درک اینکه
یک علامت نوشتاری مثل «۷» نماینده یک مقدار مشخص است.
</p>
{/* Blockquote */}
<blockquote className="p-6 my-8 border-r-2 border-orange-500/50 bg-slate-900/50 rounded-l-xl">
<p className="mb-4 text-lg font-medium leading-relaxed text-white">
مشکل اصلی این کودکان بیشتر در «پردازش نمادین» است؛ یعنی درک اینکه یک علامت نوشتاری مثل «۷» نماینده یک مقدار مشخص است.
</p>
<footer className="flex items-center gap-2 text-sm text-orange-500/80">
<div className="w-4 h-[1px] bg-orange-500/50"></div>
برت دسمت عصبپژوه آموزشی، دانشگاه کییو لوون
</footer>
</blockquote>
<h2 className="pr-3 mt-12 mb-6 text-2xl font-bold text-white border-r-4 border-orange-500">مرحله اضافی مغز</h2>
<p>
به بیان ساده، بسیاری از کودکان میتوانند وقتی با چند شیء واقعی یا چند نقطه روبهرو میشوند، مقدار را حدس بزنند یا مقایسه کنند؛ اما وقتی همان
مقدار به شکل یک نماد انتزاعی نوشته میشود، مغزشان باید یک مرحله اضافی برای ترجمه آن نماد به مفهوم عدد طی کند.
</p>
{/* Info List Box */}
<div className="p-8 my-10 border bg-slate-900/40 border-slate-800 rounded-xl">
<h3 className="flex items-center gap-2 mb-6 text-xl font-bold text-white">
<span className="w-2 h-2 bg-orange-500 rounded-full"></span>
دیسکلکولیا چیست؟
</h3>
<ul className="space-y-4">
<li className="flex items-center gap-3 text-gray-300">
<CheckCircle2 className="w-5 h-5 text-orange-500 shrink-0" />
<span>اختلال یادگیری خاص در حوزه ریاضی</span>
</li>
<li className="flex items-center gap-3 text-gray-300">
<CheckCircle2 className="w-5 h-5 text-orange-500 shrink-0" />
<span>ربطی به هوش کلی کودک ندارد</span>
</li>
<li className="flex items-center gap-3 text-gray-300">
<CheckCircle2 className="w-5 h-5 text-orange-500 shrink-0" />
<span>ریشه در پردازش نمادین اعداد دارد</span>
</li>
<li className="flex items-center gap-3 text-gray-300">
<CheckCircle2 className="w-5 h-5 text-orange-500 shrink-0" />
<span>با روشهای آموزشی مناسب قابل مدیریت است</span>
</li>
</ul>
</div>
{translation.editor
.sort((a, b) => a.sort - b.sort)
.map((block, index) => {
switch (block.type) {
case "heading":
return (
<h2
id={`blog_heading_${block.sort}`}
key={index}
className="pr-3 mt-12 mb-6 text-2xl font-bold text-white border-r-4 border-orange-500"
>
{block.content}
</h2>
);
case "paragraph":
return (
<p key={index} className="whitespace-pre-line">
{block.content}
</p>
);
case "image":
const imageSrc = blog.gallery[block.content];
return imageSrc ? (
<div key={index} className="relative w-full my-8 overflow-hidden border aspect-video rounded-xl border-slate-800">
<Image fill src={`${BACKEND_URL}/uploads/${imageSrc}`} alt="Article Attachment" className="object-cover w-full h-auto" />
</div>
) : null;
case "link":
return (
<a
key={index}
href={block.content.link}
target="_blank"
rel="noopener noreferrer"
className="text-orange-500 underline transition-colors hover:text-orange-400"
>
{block.content.content}
</a>
);
default:
return null;
}
})}
{/* Article Footer Actions */}
<div className="flex flex-wrap items-center justify-between gap-4 pt-8 border-t border-slate-800">
<div className="flex flex-wrap items-center justify-between gap-4 pt-8 mt-12 border-t border-slate-800">
<div className="flex items-center gap-3">
<span className="text-sm text-gray-400">اشتراکگذاری:</span>
<button className="flex items-center justify-center text-gray-400 transition-colors border rounded w-9 h-9 border-slate-700 hover:text-white hover:border-slate-500">
<span className="text-sm text-gray-400">{t("share")}:</span>
<button
onClick={handleShare}
className="flex items-center justify-center text-gray-400 transition-colors border rounded w-9 h-9 border-slate-700 hover:text-white hover:border-slate-500"
>
<Share2 className="w-4 h-4" />
</button>
<button className="flex items-center justify-center text-gray-400 transition-colors border rounded w-9 h-9 border-slate-700 hover:text-white hover:border-slate-500">
<Copy className="w-4 h-4" />
<button
onClick={handleCopy}
className={`flex items-center justify-center transition-colors border rounded w-9 h-9 border-slate-700 hover:border-slate-500 ${isCopied ? "text-green-500 border-green-500/50" : "text-gray-400 hover:text-white"}`}
>
{isCopied ? <Check className="w-4 h-4" /> : <Copy className="w-4 h-4" />}
</button>
</div>
<div className="flex gap-2">
<span className="text-xs px-3 py-1.5 rounded bg-slate-900 border border-slate-800 text-gray-400">دیسکلکولیا</span>
<span className="text-xs px-3 py-1.5 rounded bg-slate-900 border border-slate-800 text-gray-400">علوم اعصاب</span>
</div>
</div>
</article>
</div>

View File

@@ -1,69 +1,101 @@
import React from "react";
import { ChevronRight, Clock, Calendar, Bookmark, Share2, User } from "lucide-react";
"use client";
import { useState } from "react";
import { ChevronRight, Clock, Calendar, Bookmark, Share2, User, Link as LinkIcon, Check } from "lucide-react";
import { Blog, BlogTranslation } from "@/utilities/types/blog.type";
import Image from "next/image";
import { BACKEND_URL } from "@/utilities/constants/urls.constant";
import { formatDateByLocale } from "@/utilities/lib/format-date-by-locale";
import { useLocale, useTranslations } from "next-intl";
interface Props {
blog: Blog;
translation: BlogTranslation;
readingTime: number;
}
export default function ArticleHeader({ blog, translation, readingTime }: Props) {
const locale = useLocale();
const t = useTranslations("academy.single");
const isRtl = locale === "fa" || locale === "ar";
const [isCopied, setIsCopied] = useState(false);
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(window.location.href);
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
} catch (err) {
null;
}
};
const handleShare = async () => {
if (navigator.share) {
try {
await navigator.share({
title: translation.title,
url: window.location.href,
});
} catch (err) {
null;
}
} else {
handleCopy();
}
};
export default function ArticleHeader() {
return (
<header className="mb-16">
{/* Back Button */}
<a href="/academy" className="inline-flex items-center mb-8 text-sm text-gray-400 transition-colors hover:text-orange-500">
<ChevronRight className="w-4 h-4 ml-1" />
بازگشت به آکادمی
<ChevronRight className={`w-4 h-4 ${isRtl ? "ml-1" : "mr-1 rotate-180"}`} />
{t("backToAcademy")}
</a>
{/* Meta Info */}
<div className="flex flex-wrap items-center gap-4 mb-6 text-xs text-gray-400">
<span className="px-3 py-1 text-orange-500 border rounded-full bg-orange-500/10 border-orange-500/20">علوم اعصاب</span>
<span className="px-3 py-1 text-orange-500 border rounded-full bg-orange-500/10 border-orange-500/20">{t("badge")}</span>
<div className="flex items-center">
<Clock className="w-4 h-4 ml-1.5 text-orange-500" />۸ دقیقه مطالعه
<Clock className={`w-4 h-4 text-orange-500 ${isRtl ? "ml-1.5" : "mr-1.5"}`} />
{readingTime} {t("readTime")}
</div>
<div className="flex items-center">
<Calendar className="w-4 h-4 ml-1.5 text-orange-500" />
۱۰ فروردین ۱۴۰۴
<Calendar className={`w-4 h-4 text-orange-500 ${isRtl ? "ml-1.5" : "mr-1.5"}`} />
{formatDateByLocale(blog.publishedAt.toString(), locale)}
</div>
</div>
{/* Title & Excerpt */}
<h1 className="mb-6 text-4xl font-bold leading-tight md:text-5xl">دیسکلکولیا: وقتی مغز با اعداد کنار نمیآید</h1>
<div className="pr-4 mb-8 border-r-2 border-orange-500">
<p className="text-lg leading-relaxed text-gray-300">
پژوهش جدید با تصویربرداری مغزی نشان میدهد مشکل اصلی کودکان مبتلا به اختلال یادگیری ریاضی، نه در درک مقدار، بلکه در پردازش نمادهای عددی
نهفته است.
</p>
<h1 className="mb-6 text-4xl font-bold leading-tight md:text-5xl">{translation.title}</h1>
<div className={`mb-8 border-orange-500 ${isRtl ? "pr-4 border-r-2" : "pl-4 border-l-2"}`}>
<p className="text-lg leading-relaxed text-gray-300">{translation.description}</p>
</div>
{/* Author & Actions */}
<div className="flex items-center justify-between mb-12">
<div className="flex items-center gap-3">
<div className="flex items-center justify-center w-10 h-10 text-orange-500 border rounded-full bg-slate-800 border-slate-700">
<User className="w-5 h-5" />
</div>
<div>
<div className="text-sm font-medium">تیم آکادمی رابین</div>
<div className="text-xs text-gray-400">علم و فناوری</div>
<div className="text-sm font-medium">{blog.writer.username}</div>
<div className="text-xs text-gray-400">{t("authorRole")}</div>
</div>
</div>
<div className="flex items-center gap-2">
<button className="flex items-center justify-center w-10 h-10 text-gray-400 transition-all border rounded-lg border-slate-800 bg-slate-900/50 hover:text-white hover:border-slate-600">
<button
onClick={handleShare}
className="flex items-center justify-center w-10 h-10 text-gray-400 transition-all border rounded-lg border-slate-800 bg-slate-900/50 hover:text-white hover:border-slate-600"
>
<Share2 className="w-4 h-4" />
</button>
<button className="flex items-center justify-center w-10 h-10 text-gray-400 transition-all border rounded-lg border-slate-800 bg-slate-900/50 hover:text-white hover:border-slate-600">
<Bookmark className="w-4 h-4" />
<button
onClick={handleCopy}
className={`flex items-center justify-center w-10 h-10 transition-all border rounded-lg border-slate-800 bg-slate-900/50 hover:border-slate-600 ${isCopied ? "text-green-500 border-green-500/50" : "text-gray-400 hover:text-white"}`}
>
{isCopied ? <Check className="w-4 h-4" /> : <LinkIcon className="w-4 h-4" />}
</button>
</div>
</div>
{/* Hero Image */}
<div className="w-full aspect-[21/9] rounded-2xl border border-slate-800 bg-gradient-to-br from-slate-900 to-slate-800 relative overflow-hidden flex items-center justify-center">
{/* Placeholder for actual image - representing the brain and numbers network */}
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-orange-500/10 via-transparent to-transparent"></div>
<div className="flex flex-col items-center text-orange-500/50">
<span className="mb-4 text-6xl">🧠</span>
<div className="absolute p-2 text-sm border rounded-lg top-1/4 left-1/3 border-slate-700 bg-slate-800/50">۳</div>
<div className="absolute p-2 text-sm border rounded-lg bottom-1/3 right-1/4 border-slate-700 bg-slate-800/50">۷</div>
<div className="absolute p-2 text-sm border rounded-lg top-1/2 left-1/4 border-slate-700 bg-slate-800/50">۵</div>
<div className="absolute p-2 text-sm border rounded-lg top-1/3 right-1/3 border-slate-700 bg-slate-800/50">۹</div>
{blog.featuredImage && (
<div className="relative flex items-center justify-center w-full overflow-hidden border aspect-video rounded-2xl border-slate-800 bg-gradient-to-br from-slate-900 to-slate-800">
<Image src={`${BACKEND_URL}/uploads/${blog.featuredImage}`} alt={translation.title} fill className="object-cover w-full h-full" />
</div>
</div>
)}
</header>
);
}

View File

@@ -1,57 +1,53 @@
import React from "react";
import { BookOpen, Clock } from "lucide-react";
import { Blog } from "@/utilities/types/blog.type";
import { calculateReadingTime } from "@/utilities/lib/calculate-reading-time";
import Image from "next/image";
import { BACKEND_URL } from "@/utilities/constants/urls.constant";
import { useTranslations, useLocale } from "next-intl";
import Link from "next/link";
import { formatDateByLocale } from "@/utilities/lib/format-date-by-locale";
export default function RelatedArticles() {
const articles = [
{
id: 1,
category: "امنیت",
title: "۱۰ نکته طلایی برای امن‌سازی Firewall",
time: "۱۲ دقیقه",
gradient: "from-slate-800 to-slate-900",
},
{
id: 2,
category: "شبکه",
title: "راهنمای کامل پیکربندی MikroTik",
time: "۱۵ دقیقه",
gradient: "from-slate-800 to-slate-900",
},
{
id: 3,
category: "DEVOPS",
title: "راه‌اندازی CI/CD Pipeline با GitHub Actions",
time: "۲۵ دقیقه",
gradient: "from-orange-900/20 to-slate-900",
},
];
export default function RelatedArticles({ data }: { data: Blog[] }) {
const t = useTranslations("academy.single");
const locale = useLocale();
const isRtl = locale === "fa" || locale === "ar";
if (!data || data.length === 0) return null;
return (
<section className="pt-16 mb-16 border-t border-slate-800">
<div className="flex items-center justify-between mb-8">
<h2 className="flex items-center gap-2 text-xl font-bold text-white">
<BookOpen className="w-5 h-5 text-orange-500" />
مقالات مرتبط
{t("relatedArticles")}
</h2>
</div>
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
{articles.map((article) => (
<div
key={article.id}
className="overflow-hidden transition-all duration-300 border cursor-pointer group rounded-2xl border-slate-800 bg-slate-900/50 hover:border-orange-500/50"
>
<div className={`w-full h-32 bg-gradient-to-br ${article.gradient} opacity-50 group-hover:opacity-100 transition-opacity`}></div>
<div className="p-5">
<span className="block mb-3 text-xs font-medium text-orange-500">{article.category}</span>
<h3 className="mb-4 font-medium text-white transition-colors group-hover:text-orange-400 line-clamp-2">{article.title}</h3>
<div className="flex items-center text-xs text-gray-500">
<Clock className="w-3.5 h-3.5 ml-1" />
{article.time}
{data.map((b) => {
const translation = b.translations.find((t) => t.language === locale) || b.translations[0];
return (
<Link
href={`/${locale}/academy/${b.href}`}
key={b.id}
className="overflow-hidden transition-all duration-300 border cursor-pointer group rounded-2xl border-slate-800 bg-slate-900/50 hover:border-orange-500/50"
>
<div className="relative w-full transition-opacity aspect-video bg-gradient-to-br">
<Image src={`${BACKEND_URL}/uploads/${b.featuredImage}`} fill alt={translation.title} className="object-cover" />
</div>
</div>
</div>
))}
<div className="p-5">
<span className="flex gap-1 mb-3 text-xs font-medium text-orange-500">
<Clock className={`w-3.5 h-3.5 ${isRtl ? "ml-1" : "mr-1"}`} />
{calculateReadingTime(translation)} {t("readTime")}
</span>
<h3 className="mb-4 font-medium text-white transition-colors group-hover:text-orange-400 line-clamp-2">{translation.title}</h3>
<div className="flex items-center gap-1 text-xs text-gray-500">{formatDateByLocale(b.publishedAt.toString(), locale)}</div>
</div>
</Link>
);
})}
</div>
</section>
);

View File

@@ -1,49 +0,0 @@
// components/software/Hero.tsx
export default function Hero() {
return (
<section className="px-4 py-12 text-center sm:py-16 md:py-20">
<div className="inline-block px-3 py-1.5 sm:px-4 sm:py-2 mb-4 sm:mb-6 text-xs sm:text-sm border rounded-full bg-accent/10 border-accent/20 text-accent">
توسعه نرمافزار سفارشی
</div>
<h1 className="mb-3 text-3xl font-bold leading-tight sm:mb-4 sm:text-4xl md:text-5xl">
از ایده تا <br />
<span className="text-accent">پلتفرم زنده</span>
</h1>
<p className="max-w-2xl px-4 mx-auto mb-8 text-sm sm:mb-12 sm:text-base text-muted">
ساخت اپلیکیشنهای وب مدرن، API های مقیاسپذیر و سیستمهای سازمانی با معماری تمیز و کد قابل نگهداری.
</p>
<div className="flex flex-col justify-center gap-6 px-4 py-4 mx-auto mb-8 border sm:flex-row sm:gap-12 sm:mb-12 bg-white/[0.02] sm:px-12 sm:py-6 rounded-xl border-border w-fit">
<div className="text-center">
<h3 className="mb-1 text-2xl font-bold sm:text-3xl text-accent" style={{ direction: "ltr" }}>
+۵۰
</h3>
<p className="text-xs text-muted">پروژه تحویلی</p>
</div>
<div className="text-center">
<h3 className="mb-1 text-2xl font-bold sm:text-3xl text-accent" style={{ direction: "ltr" }}>
+۱۲
</h3>
<p className="text-xs text-muted">تکنولوژی</p>
</div>
<div className="text-center">
<h3 className="mb-1 text-2xl font-bold sm:text-3xl text-accent" style={{ direction: "ltr" }}>
٪۹۸
</h3>
<p className="text-xs text-muted">رضایت مشتری</p>
</div>
</div>
<div className="flex flex-col justify-center gap-3 px-4 sm:flex-row sm:gap-4">
<button className="px-5 py-2.5 sm:px-6 sm:py-3 text-sm sm:text-base text-white transition rounded-lg bg-accent hover:bg-accent/90">
شروع پروژه &lt;
</button>
<button className="px-5 py-2.5 sm:px-6 sm:py-3 text-sm sm:text-base transition border rounded-lg border-border hover:bg-card">
مشاهده نمونهکارها
</button>
</div>
</section>
);
}

View File

@@ -1,67 +1,38 @@
// components/software/Process.tsx
export default function Process() {
const steps = [
{
number: "۰۱",
icon: "🔍",
title: "Discovery",
description: "تحلیل نیازمندی و تعریف محدوده پروژه",
tags: ["Requirements", "Research"],
},
{
number: "۰۲",
icon: "🎨",
title: "Design",
description: "طراحی UI/UX و معماری سیستم",
tags: ["Wireframes", "Architecture"],
},
{
number: "۰۳",
icon: "💻",
title: "Development",
description: "کدنویسی با استانداردهای بالا",
tags: ["Clean Code", "Git Flow"],
},
{
number: "۰۴",
icon: "🐛",
title: "Testing",
description: "تست و کنترل کیفیت کامل",
tags: ["Unit Tests", "QA"],
},
{
number: "۰۵",
icon: "🚀",
title: "Deployment",
description: "استقرار و راه‌اندازی Production",
tags: ["CI/CD", "Docker"],
},
{
number: "۰۶",
icon: "⚙️",
title: "Support",
description: "پشتیبانی و نگهداری مستمر",
tags: ["Monitoring", "Updates"],
},
];
import { useTranslations } from "next-intl";
const badges = [
{ icon: "⏱", title: "۲-۴", subtitle: "هفته Sprint" },
{ icon: "🔄", title: "Agile", subtitle: "Methodology" },
{ icon: "🎧", title: "۲۴/۷", subtitle: "Support" },
];
// Define the types for typescript based on our JSON structure
type Step = {
number: string;
icon: string;
title: string;
description: string;
tags: string[];
};
type Badge = {
icon: string;
title: string;
subtitle: string;
};
export default function Process() {
const t = useTranslations("software.process");
// Fetch the arrays from JSON
const steps = t.raw("steps") as Step[];
const badges = t.raw("badges") as Badge[];
return (
<section className="px-4 py-12 sm:px-6 sm:py-16 bg-black/30 border-t border-white/[0.02]">
<div className="mx-auto mb-8 text-center sm:mb-12 max-w-7xl">
<h2 className="mb-2 text-2xl font-bold sm:text-3xl">فرآیند توسعه نرمافزار</h2>
<p className="text-sm sm:text-base text-muted">از ایده تا محصول نهایی با متدولوژی Agile</p>
<h2 className="mb-2 text-2xl font-bold sm:text-3xl">{t("title")}</h2>
<p className="text-sm sm:text-base text-muted">{t("subtitle")}</p>
</div>
<div className="grid gap-6 px-4 mx-auto mb-8 sm:gap-8 sm:px-6 sm:mb-12 max-w-7xl sm:grid-cols-2 lg:grid-cols-3">
{steps.map((step, i) => (
<div key={i} className="relative p-6 text-center transition-transform border sm:p-8 bg-card border-border rounded-xl hover:-translate-y-1">
<div className="absolute -top-3 sm:-top-4 right-4 sm:right-5 w-7 h-7 sm:w-8 sm:h-8 bg-accent text-white rounded-full flex items-center justify-center text-xs sm:text-sm font-bold shadow-[0_4px_10px_rgba(249,115,22,0.3)]">
<div className="absolute -top-3 sm:-top-4 rtl:right-4 rtl:sm:right-5 ltr:left-4 ltr:sm:left-5 w-7 h-7 sm:w-8 sm:h-8 bg-accent text-white rounded-full flex items-center justify-center text-xs sm:text-sm font-bold shadow-[0_4px_10px_rgba(249,115,22,0.3)]">
{step.number}
</div>
<div className="flex items-center justify-center w-10 h-10 mx-auto mb-4 text-xl border sm:w-12 sm:h-12 sm:mb-6 sm:text-2xl bg-accent/10 border-accent/20 rounded-xl">
@@ -83,7 +54,8 @@ export default function Process() {
<div className="flex flex-col flex-wrap justify-center gap-4 sm:flex-row sm:gap-6">
{badges.map((badge, i) => (
<div key={i} className="flex items-center justify-between gap-4 px-6 py-4 border bg-card border-border rounded-xl">
<div className="text-right">
{/* Changed text-right to text-start for proper LTR/RTL support */}
<div className="text-start">
<h4 className="text-lg font-bold sm:text-xl">{badge.title}</h4>
<p className="text-sm text-muted">{badge.subtitle}</p>
</div>

View File

@@ -1,153 +1,121 @@
// components/software/Projects.tsx
"use client";
import { useState, useRef } from "react";
import { Portfolio } from "@/utilities/types/portfolio.type";
import { useLocale, useTranslations } from "next-intl";
import { useRef, useState } from "react";
const projects = [
{
type: "وب اپلیکیشن",
company: "شرکت آتی‌ساز",
title: "پلتفرم مدیریت پروژه",
description: "سیستم جامع مدیریت پروژه با قابلیت ردیابی وظایف و گزارش‌گیری Real-time",
tech: ["⚛ Next.js + TypeScript", "🐘 PostgreSQL + Prisma"],
stat: "👤 +۵۰۰ کاربر",
year: "۲۰۲۶",
},
{
type: "داشبورد تحلیلی",
company: "گروه مالی پارسیان",
title: "داشبورد تحلیل داده",
description: "پنل تحلیلی با نمودارهای تعاملی و گزارش‌های لحظه‌ای برای تیم مدیریت",
tech: ["⚛ React + Chart.js", "🟢 Node.js + WebSocket"],
stat: "📈 Real-time",
year: "۲۰۲۶",
},
{
type: "Backend API",
company: "استارتاپ دیجی‌مارکت",
title: "API فروشگاه آنلاین",
description: "RESTful API مقیاس‌پذیر با معماری Microservices برای پلتفرم e-commerce",
tech: ["🟢 NestJS + GraphQL", "🍃 MongoDB + Redis"],
stat: "⚡ 10k req/s",
year: "۲۰۲۵",
},
{
type: "اپلیکیشن موبایل",
company: "شرکت حمل‌ونقل سریع",
title: "اپ درخواست تاکسی",
description: "اپلیکیشن کراس‌پلتفرم با قابلیت ردیابی مسیر و پرداخت آنلاین",
tech: ["📱 React Native", "🔥 Firebase + Maps API"],
stat: "📍 GPS Tracking",
year: "۲۰۲۵",
},
{
type: "پنل مدیریتی",
company: "آکادمی آنلاین دانش",
title: "سیستم مدیریت آموزش",
description: "LMS کامل با قابلیت برگزاری کلاس آنلاین و مدیریت دوره‌ها",
tech: ["⚛ Next.js + Tailwind", "🎥 WebRTC + AWS S3"],
stat: "🎓 +۲۰۰۰ دانشجو",
year: "۲۰۲۵",
},
];
export default function Projects() {
const [filter, setFilter] = useState("همه");
export default function Projects({ data }: { data: Portfolio[] }) {
const scrollRef = useRef<HTMLDivElement>(null);
const locale = useLocale();
const t = useTranslations("software.projects");
const filters = ["همه", "وب اپلیکیشن", "داشبورد تحلیلی", "Backend API", "اپلیکیشن موبایل", "پنل مدیریتی"];
const [isDragging, setIsDragging] = useState(false);
const [startX, setStartX] = useState(0);
const [scrollLeft, setScrollLeft] = useState(0);
const filtered = filter === "همه" ? projects : projects.filter((p) => p.type === filter);
const isRTL = locale === "fa" || locale === "ar";
const scroll = (direction: "left" | "right") => {
const scroll = (direction: "next" | "prev") => {
if (scrollRef.current) {
const scrollAmount = 350;
const modifier = isRTL ? -1 : 1;
const move = direction === "next" ? scrollAmount * modifier : -scrollAmount * modifier;
scrollRef.current.scrollBy({
left: direction === "left" ? -scrollAmount : scrollAmount,
left: move,
behavior: "smooth",
});
}
};
const handleMouseDown = (e: React.MouseEvent) => {
if (!scrollRef.current) return;
setIsDragging(true);
setStartX(e.pageX - scrollRef.current.offsetLeft);
setScrollLeft(scrollRef.current.scrollLeft);
};
const handleMouseLeave = () => {
setIsDragging(false);
};
const handleMouseUp = () => {
setIsDragging(false);
};
const handleMouseMove = (e: React.MouseEvent) => {
if (!isDragging || !scrollRef.current) return;
e.preventDefault();
const x = e.pageX - scrollRef.current.offsetLeft;
const walk = (x - startX) * 2;
scrollRef.current.scrollLeft = scrollLeft - walk;
};
const getYear = (date: Date | string) => {
return new Date(date).toLocaleDateString(locale, { year: "numeric" });
};
return (
<section className="px-4 py-12 sm:px-6 sm:py-16">
<div className="mx-auto mb-8 text-right sm:mb-12 max-w-7xl">
<h2 className="mb-2 text-2xl font-bold sm:text-3xl">نمونهکارهای اخیر</h2>
<p className="text-sm sm:text-base text-muted">پروژههای موفق تحویلشده به مشتریان</p>
</div>
<section className="px-4 py-12 sm:px-6 sm:py-16" id="portfolios">
<div className="flex flex-wrap items-end justify-between gap-4 mx-auto mb-8 sm:mb-12 max-w-7xl">
<div className="text-start">
<h2 className="mb-2 text-2xl font-bold sm:text-3xl">{t("sectionTitle")}</h2>
<p className="text-sm sm:text-base text-muted">{t("sectionSubtitle")}</p>
</div>
<div className="flex flex-wrap justify-center gap-2 px-4 mx-auto mb-8 sm:gap-3 sm:mb-12 max-w-7xl">
{filters.map((f) => (
{/* Navigation Arrows moved to header */}
<div className="flex gap-2">
<button
key={f}
onClick={() => setFilter(f)}
className={`px-3 py-1.5 sm:px-4 sm:py-2 text-xs sm:text-sm rounded-lg transition ${
filter === f ? "bg-accent text-white" : "bg-card border border-border hover:border-accent/50"
}`}
onClick={() => scroll("prev")}
className="flex items-center justify-center transition border rounded-lg w-9 h-9 sm:w-10 sm:h-10 border-border hover:bg-card bg-card text-foreground"
aria-label="Scroll previous"
>
{f}
{"<"}
</button>
))}
<button
onClick={() => scroll("next")}
className="flex items-center justify-center transition border rounded-lg w-9 h-9 sm:w-10 sm:h-10 border-border hover:bg-card bg-card text-foreground"
aria-label="Scroll next"
>
{">"}
</button>
</div>
</div>
<div className="relative px-4 mx-auto max-w-7xl sm:px-6">
<button
onClick={() => scroll("right")}
className="absolute z-10 flex items-center justify-center w-8 h-8 transition -translate-y-1/2 border rounded-full sm:w-10 sm:h-10 -right-2 sm:-right-4 top-1/2 bg-card border-border hover:bg-accent hover:border-accent"
aria-label="Scroll right"
<div className="px-4 mx-auto max-w-7xl sm:px-6">
<div
ref={scrollRef}
onMouseDown={handleMouseDown}
onMouseLeave={handleMouseLeave}
onMouseUp={handleMouseUp}
onMouseMove={handleMouseMove}
className={`flex gap-4 pb-4 overflow-x-auto sm:gap-6 [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none] ${
isDragging ? "cursor-grabbing snap-none" : "cursor-grab snap-x snap-mandatory scroll-smooth"
}`}
>
&lt;
</button>
<div ref={scrollRef} className="flex gap-4 pb-4 overflow-x-auto sm:gap-6 scrollbar-hide snap-x snap-mandatory">
{filtered.map((project, i) => (
{data.map((p) => (
<div
key={i}
className="flex-shrink-0 w-[280px] sm:w-[320px] lg:w-[380px] p-5 sm:p-6 text-right border bg-card border-border rounded-xl snap-start"
key={p.id}
className="flex-shrink-0 w-[85%] sm:w-[320px] lg:w-[380px] p-5 sm:p-6 text-start border bg-card border-border rounded-xl snap-start select-none flex flex-col min-h-[200px]"
>
<div className="flex items-start justify-between mb-3 sm:mb-4">
<span className="px-2 py-1 text-xs border rounded sm:px-3 text-accent border-accent/30 bg-accent/10">{project.type}</span>
<span className="text-xs sm:text-sm text-muted">{project.year}</span>
<span className="px-2 py-1 text-xs border rounded sm:px-3 text-accent border-accent/30 bg-accent/10">{t(p.category)}</span>
<span className="text-xs sm:text-sm text-muted">{getYear(p.createdAt)}</span>
</div>
<h3 className="mb-1 text-base font-bold sm:mb-2 sm:text-lg">{project.title}</h3>
<p className="mb-2 text-xs sm:mb-3 sm:text-sm text-muted">{project.company}</p>
<p className="mb-4 text-xs leading-relaxed sm:mb-6 sm:text-sm text-muted">{project.description}</p>
<h3 className="mb-1 text-base font-bold sm:mb-2 sm:text-lg">{p.title}</h3>
<p className="mb-2 text-xs sm:mb-3 sm:text-sm text-muted">{p.employer}</p>
<p className="flex-grow mb-4 text-xs leading-relaxed pointer-events-none sm:mb-6 sm:text-sm text-muted">{p.description}</p>
<div className="flex flex-wrap gap-2 mb-4 sm:mb-6">
{project.tech.map((t, j) => (
<span key={j} className="px-2 py-1 text-xs border rounded bg-background border-border">
{t}
</span>
))}
</div>
<div className="flex items-center justify-between pt-3 border-t sm:pt-4 border-border">
<button className="text-xs sm:text-sm text-accent hover:underline">مشاهده جزئیات &lt;</button>
<span className="text-xs sm:text-sm text-muted">{project.stat}</span>
<div className="flex items-center justify-between pt-3 mt-auto border-t sm:pt-4 border-border">
<button className="text-xs transition-colors sm:text-sm text-accent hover:text-accent/80">
{t("viewDetails")} {">"}
</button>
</div>
</div>
))}
</div>
<button
onClick={() => scroll("left")}
className="absolute z-10 flex items-center justify-center w-8 h-8 transition -translate-y-1/2 border rounded-full sm:w-10 sm:h-10 -left-2 sm:-left-4 top-1/2 bg-card border-border hover:bg-accent hover:border-accent"
aria-label="Scroll left"
>
&gt;
</button>
</div>
<style jsx>{`
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
`}</style>
</section>
);
}

View File

@@ -1,53 +1,32 @@
// components/software/Services.tsx
const services = [
{
icon: "💻",
title: "توسعه وب اپلیکیشن",
description: "ساخت اپلیکیشن‌های وب پیشرفته با React, Next.js و Vue.js",
},
{
icon: "⚡",
title: "طراحی و توسعه API",
description: "ساخت RESTful و GraphQL API های مقیاس‌پذیر",
},
{
icon: "☁",
title: "راه‌اندازی ابری",
description: "استقرار و مدیریت اپلیکیشن در زیرساخت ابری",
},
{
icon: "🎛",
title: "پنل‌های مدیریتی",
description: "داشبوردهای تحلیلی و سیستم‌های مدیریت محتوا",
},
{
icon: "📱",
title: "اپلیکیشن موبایل",
description: "توسعه اپ‌های کراس‌پلتفرم با React Native",
},
{
icon: "⚙",
title: "اتوماسیون فرآیندها",
description: "خودکارسازی وظایف تکراری و بهینه‌سازی جریان کار",
},
];
import { useTranslations } from "next-intl";
export default function Services() {
const t = useTranslations("software.services");
const servicesKeys = [
{ icon: "💻", key: "webApp" },
{ icon: "⚡", key: "api" },
{ icon: "☁", key: "cloud" },
{ icon: "🎛", key: "dashboards" },
{ icon: "📱", key: "mobile" },
{ icon: "⚙", key: "automation" },
];
return (
<section className="px-4 py-12 mx-auto sm:px-6 sm:py-16 max-w-7xl">
<div className="mb-8 text-right sm:mb-12">
<h2 className="mb-2 text-2xl font-bold sm:text-3xl">خدمات توسعه نرمافزار</h2>
<p className="text-sm sm:text-base text-muted">راهکارهای جامع از طراحی تا استقرار و نگهداری</p>
<div className="mb-8 text-start sm:mb-12">
<h2 className="mb-2 text-2xl font-bold sm:text-3xl">{t("header.title")}</h2>
<p className="text-sm sm:text-base text-muted">{t("header.subtitle")}</p>
</div>
<div className="grid gap-4 sm:gap-6 sm:grid-cols-2 lg:grid-cols-3">
{services.map((service, i) => (
<div key={i} className="p-6 text-right transition-transform border sm:p-8 bg-card border-border rounded-xl hover:-translate-y-1">
{servicesKeys.map((service, i) => (
<div key={i} className="p-6 transition-transform border text-start sm:p-8 bg-card border-border rounded-xl hover:-translate-y-1">
<div className="flex items-center justify-center w-10 h-10 mb-4 text-xl border rounded-lg sm:w-12 sm:h-12 sm:mb-6 sm:text-2xl bg-accent/10 border-accent/20 text-accent">
{service.icon}
</div>
<h3 className="mb-2 text-lg font-bold sm:mb-3 sm:text-xl">{service.title}</h3>
<p className="text-sm leading-relaxed text-muted">{service.description}</p>
<h3 className="mb-2 text-lg font-bold sm:mb-3 sm:text-xl">{t(`items.${service.key}.title`)}</h3>
<p className="text-sm leading-relaxed text-muted">{t(`items.${service.key}.description`)}</p>
</div>
))}
</div>

View File

@@ -1,15 +1,14 @@
// components/software/TechStack.tsx
"use client";
import { useState, useEffect } from "react";
import { useTranslations } from "next-intl";
const techItems = [
// ================= ORBIT 1 =================
{
name: "React",
key: "react",
orbit: 1,
startAngle: 0,
description: "کتابخانه قدرتمند برای ساخت رابط‌های تعاملی",
icon: (
<svg viewBox="-11.5 -10.2 23 20.4" className="w-6 h-6 sm:w-8 sm:h-8 text-[#61DAFB]">
<circle r="2.05" fill="currentColor" />
@@ -22,10 +21,9 @@ const techItems = [
),
},
{
name: "Next.js",
key: "nextjs",
orbit: 1,
startAngle: 180,
description: "فریم‌ورک پیشرفته React برای پروژه‌های تولیدی",
icon: (
<svg viewBox="0 0 100 100" className="w-6 h-6 sm:w-8 sm:h-8 text-foreground" fill="currentColor">
<path d="M50 0C22.4 0 0 22.4 0 50s22.4 50 50 50 50-22.4 50-50S77.6 0 50 0zm0 90c-22.1 0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40zm-4.9-57.5h6v32.1l-20.8-32.1h-6.6v41.7h6v-32.2l21 32.5h5.9V32.5h-6v..." />
@@ -38,10 +36,9 @@ const techItems = [
// ================= ORBIT 2 =================
{
name: "PostgreSQL",
key: "postgresql",
orbit: 2,
startAngle: 90,
description: "پایگاه داده رابطه‌ای قدرتمند با امکانات پیشرفته",
icon: (
<svg viewBox="0 0 100 100" className="w-6 h-6 sm:w-8 sm:h-8 text-[#336791]" fill="currentColor">
<path d="M82.8 68.6c-1.3-4.7-5-9.2-10-12.2 4.1-1.3 7.8-3.4 10.9-6.3-4.6 1.8-9.8 2.6-15 2.2-5-4.5-12.1-7.1-19.5-6.5-12.8 1.1-23.4 11-25.5 23.7C20.6 88 35.8 100 50 100c14.2 0 26.6-9.5 30.6-22.7 1.1-3.6 1.8-7.3 2.2-8.7z" />
@@ -50,10 +47,9 @@ const techItems = [
),
},
{
name: "MongoDB",
key: "mongodb",
orbit: 2,
startAngle: 270,
description: "پایگاه داده NoSQL سریع و مقیاس‌پذیر",
icon: (
<svg viewBox="0 0 100 100" className="w-6 h-6 sm:w-8 sm:h-8 text-[#47A248]" fill="currentColor">
<path d="M49.9 8.7c0 0-17.8 24.3-17.8 45.4 0 17 8.3 29.8 15.6 37.1 1.2 1.2 3.1 1.2 4.3 0 7.3-7.3 15.6-20.1 15.6-37.1 0-21.1-17.7-45.4-17.7-45.4zM49.9 96.5c-2.4 0-4.3-1.9-4.3-4.3s1.9-4.3 4.3-4.3 4.3 1.9 4.3 4.3-1.9 4.3-4.3 4.3z" />
@@ -63,10 +59,9 @@ const techItems = [
// ================= ORBIT 3 =================
{
name: "Docker",
key: "docker",
orbit: 3,
startAngle: 45,
description: "کانتینرسازی برای استقرار و توسعه یکپارچه",
icon: (
<svg viewBox="0 0 100 100" className="w-6 h-6 sm:w-8 sm:h-8 text-[#2496ED]" fill="currentColor">
<path d="M12.4 62.6c0 10.6 19 19.3 42.4 19.3 23.4 0 42.4-8.6 42.4-19.3 0-5.1-4.4-9.7-11.7-13.4H12.4v13.4zM24.7 41.5h11v11h-11v-11zm15.4 0h11v11h-11v-11zm15.4 0h11v11h-11v-11zm-15.4-15h11v11h-11v-11zm15.4 0h11v11h-11v-11z" />
@@ -74,10 +69,9 @@ const techItems = [
),
},
{
name: "Django",
key: "django",
orbit: 3,
startAngle: 225,
description: "فریم‌ورک سطح بالای پایتون برای توسعه سریع",
icon: (
<svg viewBox="0 0 100 100" className="w-6 h-6 sm:w-8 sm:h-8">
<rect width="100" height="100" rx="20" fill="#092E20" />
@@ -90,10 +84,9 @@ const techItems = [
// ================= ORBIT 4 =================
{
name: "NestJS",
key: "nestjs",
orbit: 4,
startAngle: 135,
description: "فریم‌ورک پیشرونده Node.js برای ساخت برنامه‌های مقیاس‌پذیر",
icon: (
<svg viewBox="0 0 100 100" className="w-6 h-6 sm:w-8 sm:h-8 text-[#E0234E]" fill="currentColor">
<path d="M49.3 5.2L5.2 30.6v40.3L49.3 96.3l44.1-25.4V30.6L49.3 5.2zm0 9.3l36.6 20.8v41.6L49.3 97.7 12.7 76.9V35.3L49.3 14.5z" />
@@ -102,10 +95,9 @@ const techItems = [
),
},
{
name: "TypeScript",
key: "typescript",
orbit: 4,
startAngle: 315,
description: "جاوااسکریپت تایپ‌دار برای کد امن‌تر و قابل نگهداری",
icon: (
<svg viewBox="0 0 24 24" className="w-6 h-6 sm:w-8 sm:h-8 text-[#3178C6]" fill="currentColor">
<path d="M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zM11.734 18.906c-1.39 0-2.437-.36-3.14-.984l.875-1.516c.547.453 1.344.828 2.234.828 1.078 0 1.547-.468 1.547-1.078 0-1.844-4.578-.89-4.578-4.218 0-1.688 1.36-2.907 3.656-2.907 1.25 0 2.28.313 2.922.75l-.75 1.516c-.469-.328-1.188-.64-2.031-.64-.938 0-1.39.422-1.39.953 0 1.844 4.578.86 4.578 4.266 0 1.765-1.36 2.953-3.922 2.953zM16.14 8.781h5.813v2.094h-3.64v8.031h-2.172v-8.03h-3.64v-2.094h3.64z" />
@@ -115,6 +107,7 @@ const techItems = [
];
export default function TechStack() {
const t = useTranslations("software.techStack");
const [selected, setSelected] = useState(techItems[0]);
const [dimensions, setDimensions] = useState({
size: 800,
@@ -144,9 +137,9 @@ export default function TechStack() {
return (
<section className="flex flex-col items-center px-4 py-12 overflow-hidden sm:px-6 sm:py-16">
<div className="w-full mb-8 text-right sm:mb-12 max-w-7xl">
<h2 className="mb-2 text-2xl font-bold sm:text-3xl">تکنولوژیهای ما</h2>
<p className="text-sm sm:text-base text-muted">اکوسیستم کامل توسعه نرمافزار در یک نگاه</p>
<div className="w-full mb-8 text-start sm:mb-12 max-w-7xl">
<h2 className="mb-2 text-2xl font-bold sm:text-3xl">{t("title")}</h2>
<p className="text-sm sm:text-base text-muted">{t("subtitle")}</p>
</div>
<div className="flex flex-col items-center justify-center w-full gap-8 lg:flex-row lg:flex-nowrap lg:gap-16 max-w-7xl">
@@ -160,7 +153,7 @@ export default function TechStack() {
>
{/* Sun (Center) */}
<div className="absolute text-center text-sm top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-10 flex items-center justify-center w-12 h-12 sm:w-16 sm:h-16 lg:w-24 lg:h-24 sm:text-base lg:text-lg rounded-full bg-accent shadow-[0_0_40px_rgba(249,115,22,0.6)] sm:shadow-[0_0_60px_rgba(249,115,22,0.6)]">
Tech Stack
{t("center")}
</div>
{/* Orbits (Rings 1 to 4) */}
@@ -203,7 +196,7 @@ export default function TechStack() {
-translate-x-1/2 -translate-y-1/2
w-10 h-10 sm:w-12 sm:h-12 lg:w-14 lg:h-14 rounded-full flex items-center justify-center transition-all z-20 bg-background
${
selected.name === tech.name
selected.key === tech.key
? "border-2 border-accent shadow-[0_0_15px_rgba(249,115,22,0.5)] sm:shadow-[0_0_20px_rgba(249,115,22,0.5)] scale-110"
: "border border-border hover:border-accent/50 hover:scale-110"
}
@@ -220,37 +213,14 @@ export default function TechStack() {
</div>
{/* Information Card */}
<div className="w-full max-w-md p-6 text-right border sm:p-8 bg-card border-border rounded-xl">
<div className="w-full max-w-md p-6 border text-start sm:p-8 bg-card border-border rounded-xl">
<div className="flex items-center justify-center w-12 h-12 mb-4 border sm:w-16 sm:h-16 sm:mb-6 bg-accent/10 border-accent/20 rounded-xl">
{selected.icon}
</div>
<h3 className="mb-3 text-xl font-bold sm:mb-4 sm:text-2xl">{selected.name}</h3>
<p className="text-sm leading-relaxed sm:text-base text-muted">{selected.description}</p>
<h3 className="mb-3 text-xl font-bold sm:mb-4 sm:text-2xl">{t(`items.${selected.key}.name`)}</h3>
<p className="text-sm leading-relaxed sm:text-base text-muted">{t(`items.${selected.key}.description`)}</p>
</div>
</div>
<style>{`
@keyframes orbit-1 {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes orbit-2 {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes orbit-3 {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes orbit-4 {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes counter-rotate {
from { transform: translate(-50%, -50%) rotate(0deg); }
to { transform: translate(-50%, -50%) rotate(-360deg); }
}
`}</style>
</section>
);
}

View File

@@ -0,0 +1,44 @@
import { useTranslations } from "next-intl";
import HeroButtons from "./HeroButtons";
export default function Hero() {
const t = useTranslations("software.hero");
return (
<section className="px-4 py-12 text-center sm:py-16 md:py-20">
<div className="inline-block px-3 py-1.5 sm:px-4 sm:py-2 mb-4 sm:mb-6 text-xs sm:text-sm border rounded-full bg-accent/10 border-accent/20 text-accent">
{t("badge")}
</div>
<h1 className="mb-3 text-3xl font-bold leading-tight sm:mb-4 sm:text-4xl md:text-5xl">
{t("title1")} <br />
<span className="text-accent">{t("title2")}</span>
</h1>
<p className="max-w-2xl px-4 mx-auto mb-8 text-sm sm:mb-12 sm:text-base text-muted">{t("description")}</p>
<div className="flex justify-center gap-6 px-4 py-4 mx-auto mb-8 border sm:gap-12 sm:mb-12 bg-white/[0.02] sm:px-12 sm:py-6 rounded-xl border-border w-fit">
<div className="text-center">
<h3 className="mb-1 text-2xl font-bold sm:text-3xl text-accent" dir="ltr">
{t("stats.deliveredProjects")}
</h3>
<p className="text-xs text-muted">{t("stats.deliveredProjectsLabel")}</p>
</div>
<div className="text-center">
<h3 className="mb-1 text-2xl font-bold sm:text-3xl text-accent" dir="ltr">
{t("stats.technologies")}
</h3>
<p className="text-xs text-muted">{t("stats.technologiesLabel")}</p>
</div>
<div className="text-center">
<h3 className="mb-1 text-2xl font-bold sm:text-3xl text-accent" dir="ltr">
{t("stats.customerSatisfaction")}
</h3>
<p className="text-xs text-muted">{t("stats.customerSatisfactionLabel")}</p>
</div>
</div>
<HeroButtons />
</section>
);
}

View File

@@ -0,0 +1,30 @@
"use client";
import { handleScrollToId } from "@/utilities/lib/scroll";
import { ArrowUpIcon, ChevronLeft, ChevronRight } from "lucide-react";
import { useLocale, useTranslations } from "next-intl";
export default function HeroButtons() {
const t = useTranslations("software.hero");
const locale = useLocale();
const isRtl = locale === "fa" || locale === "ar";
const ArrowIcon = isRtl ? ChevronLeft : ChevronRight;
return (
<div className="flex flex-col justify-center gap-3 px-4 sm:flex-row sm:gap-4">
<button
onClick={() => handleScrollToId("footer")}
className="flex items-center justify-center gap-2 px-5 py-2.5 sm:px-6 sm:py-3 text-sm sm:text-base text-white transition rounded-lg bg-accent hover:bg-accent/90"
>
{t("buttons.startProject")} <ArrowIcon className="w-4 h-4" />
</button>
<button
onClick={() => handleScrollToId("portfolios")}
className="px-5 py-2.5 sm:px-6 sm:py-3 text-sm sm:text-base transition border rounded-lg border-border hover:bg-card"
>
{t("buttons.viewPortfolio")}
</button>
</div>
);
}

18
src/i18n.ts Normal file
View File

@@ -0,0 +1,18 @@
// src/i18n.ts
import { getRequestConfig } from "next-intl/server";
import { notFound } from "next/navigation";
const locales = ["fa", "en", "ar"];
export default getRequestConfig(async ({ requestLocale }) => {
let locale = await requestLocale;
if (!locale || !locales.includes(locale as string)) {
locale = "fa";
}
return {
locale,
messages: (await import(`./messages/${locale}.json`)).default,
};
});

278
src/messages/ar.json Normal file
View File

@@ -0,0 +1,278 @@
{
"navbar": {
"home": "الرئيسية",
"software": "برمجة",
"network": "شبكة",
"academy": "أكاديمية",
"logo1": "رابين",
"logo2": "شبكة"
},
"academy": {
"single": {
"notFound": "المقال غير موجود",
"backToAcademy": "العودة إلى الأكاديمية",
"badge": "تكنولوجيا",
"readTime": "دقيقة للقراءة",
"authorRole": "العلوم والتكنولوجيا",
"tableOfContents": "جدول المحتويات",
"readingProgress": "تقدم القراءة",
"share": "مشاركة",
"relatedArticles": "مقالات ذات صلة"
},
"page": {
"hero": {
"badge": "أكاديمية روبين للشبكات",
"title1": "المعرفة والخبرة،",
"title2": "مشاركة",
"subtitle": "مقالات متخصصة، دروس تعليمية، وتجارب واقعية من مشاريع الشبكات والبرمجيات"
},
"searchPlaceholder": "البحث عن المقالات...",
"featuredInCard": "شبكة",
"minutesRead": "دقيقة للقراءة",
"pagination": {
"previous": ">",
"next": "<"
},
"tags": {
"mikrotik": "ميكروتيك",
"vlan": "VLAN",
"routing": "توجيه",
"network": "شبكة"
}
}
},
"network": {
"hero": {
"badge": "هندسة الشبكات والأمن",
"title1": "البنية التحتية للشبكات",
"title2": "مستقرة وآمنة",
"subtitle": "تصميم وتنفيذ وإدارة البنى التحتية المعقدة للشبكات مع التركيز على الأمان والاستقرار وقابلية التوسع.",
"viewPortfolio": "عرض الأعمال",
"freeConsultation": "احصل على استشارة مجانية"
},
"services": {
"sectionTitle": "خدمات الشبكات",
"sectionSubtitle": "حلول شاملة للبنية التحتية لشبكة مؤسستك",
"items": {
"wifi": { "title": "الشبكة اللاسلكية", "subtitle": "شبكة واي فاي للمؤسسات" },
"security": { "title": "الأمن", "subtitle": "جدار الحماية و IDS/IPS" },
"design": { "title": "تصميم الشبكات", "subtitle": "الهيكلة والطوبولوجيا" },
"support": { "title": "الدعم", "subtitle": "دعم على مدار الساعة ٢٤/٧" },
"vpn": { "title": "VPN و WAN", "subtitle": "الاتصال بين المواقع" },
"monitoring": { "title": "المراقبة", "subtitle": "مراقبة على مدار الساعة ٢٤/٧" }
}
},
"technologies": {
"title": "التقنيات",
"subtitle": "المعدات والتقنيات المستخدمة"
},
"projects": {
"title": "مشاريع الشبكات",
"subtitle": "مجموعة مختارة من مشاريع الشبكات المنفذة",
"categories": {
"network": "شبكة",
"software": "برمجة"
}
}
},
"software": {
"hero": {
"badge": "⚙ تطوير برمجيات مخصصة",
"title1": "من الفكرة إلى",
"title2": "منصة حية",
"description": "بناء تطبيقات ويب حديثة، وواجهات برمجة تطبيقات (APIs) قابلة للتطوير، وأنظمة مؤسسية بهندسة نظيفة وكود قابل للصيانة.",
"stats": {
"deliveredProjects": "+٥٠",
"deliveredProjectsLabel": "مشروع منجز",
"technologies": "+١٢",
"technologiesLabel": "تقنية",
"customerSatisfaction": "٪٩٨",
"customerSatisfactionLabel": "رضا العملاء"
},
"buttons": {
"startProject": "ابدأ المشروع",
"viewPortfolio": "عرض الأعمال"
}
},
"projects": {
"sectionTitle": "مشاريعنا",
"sectionSubtitle": "استكشف أحدث أعمالنا ودراسات الحالة الناجحة.",
"viewDetails": "عرض التفاصيل",
"network": "شبكة",
"software": "برمجيات"
},
"techStack": {
"title": "تقنياتنا",
"subtitle": "نظرة سريعة على النظام البيئي الكامل لتطوير البرمجيات",
"center": "Tech Stack",
"items": {
"react": {
"name": "React",
"description": "مكتبة قوية لبناء واجهات مستخدم تفاعلية"
},
"nextjs": {
"name": "Next.js",
"description": "إطار عمل React متقدم للمشاريع الإنتاجية"
},
"postgresql": {
"name": "PostgreSQL",
"description": "قاعدة بيانات علائقية قوية بميزات متقدمة"
},
"mongodb": {
"name": "MongoDB",
"description": "قاعدة بيانات NoSQL سريعة وقابلة للتطوير"
},
"docker": {
"name": "Docker",
"description": "حاويات للنشر والتطوير السلس"
},
"django": {
"name": "Django",
"description": "إطار عمل بايثون عالي المستوى للتطوير السريع"
},
"nestjs": {
"name": "NestJS",
"description": "إطار عمل Node.js متقدم لبناء تطبيقات قابلة للتطوير"
},
"typescript": {
"name": "TypeScript",
"description": "جافا سكريبت بنظام كتابة قوي لكود آمن وقابل للصيانة"
}
}
},
"process": {
"title": "عملية تطوير البرمجيات",
"subtitle": "من الفكرة إلى المنتج النهائي باستخدام منهجية Agile",
"steps": [
{
"number": "٠١",
"icon": "🔍",
"title": "Discovery",
"description": "تحليل المتطلبات وتحديد نطاق المشروع",
"tags": ["Requirements", "Research"]
},
{ "number": "٠٢", "icon": "🎨", "title": "Design", "description": "تصميم UI/UX وهيكلية النظام", "tags": ["Wireframes", "Architecture"] },
{ "number": "٠٣", "icon": "💻", "title": "Development", "description": "البرمجة بمعايير عالية", "tags": ["Clean Code", "Git Flow"] },
{ "number": "٠٤", "icon": "🐛", "title": "Testing", "description": "اختبار شامل وضمان الجودة", "tags": ["Unit Tests", "QA"] },
{ "number": "٠٥", "icon": "🚀", "title": "Deployment", "description": "النشر والإطلاق في بيئة الإنتاج", "tags": ["CI/CD", "Docker"] },
{ "number": "٠٦", "icon": "⚙️", "title": "Support", "description": "دعم وصيانة مستمرة", "tags": ["Monitoring", "Updates"] }
],
"badges": [
{ "icon": "⏱", "title": "٢-٤", "subtitle": "أسابيع Sprint" },
{ "icon": "🔄", "title": "Agile", "subtitle": "Methodology" },
{ "icon": "🎧", "title": "٢٤/٧", "subtitle": "Support" }
]
},
"services": {
"header": {
"title": "خدمات تطوير البرمجيات",
"subtitle": "حلول شاملة من التصميم إلى النشر والصيانة"
},
"items": {
"webApp": {
"title": "تطوير تطبيقات الويب",
"description": "بناء تطبيقات ويب متقدمة باستخدام React و Next.js و Vue.js"
},
"api": {
"title": "تصميم وتطوير API",
"description": "بناء واجهات برمجة تطبيقات RESTful و GraphQL قابلة للتطوير"
},
"cloud": {
"title": "البنية التحتية السحابية",
"description": "نشر التطبيقات وإدارتها على البنية التحتية السحابية"
},
"dashboards": {
"title": "لوحات الإدارة",
"description": "لوحات معلومات تحليلية وأنظمة إدارة المحتوى (CMS)"
},
"mobile": {
"title": "تطبيقات الهاتف المحمول",
"description": "تطوير تطبيقات متعددة المنصات باستخدام React Native"
},
"automation": {
"title": "أتمتة العمليات",
"description": "أتمتة المهام المتكررة وتحسين سير العمل"
}
}
}
},
"home": {
"hero": {
"badge": "هندسة الأنظمة القابلة للتطوير",
"title1": "نحن نفك عقدك",
"title2": "التقنية",
"subtitle": "مزيج من السلطة الهندسية في البنية التحتية والابتكار الحديث في تطوير البرمجيات، من إعداد الشبكات المعقدة إلى تطوير منصات سحابية مخصصة.",
"btn_consulting": "احصل على استشارة مجانية",
"btn_portfolio": "عرض الأعمال"
},
"services": {
"title": "حلول متكاملة",
"subtitle": "خدماتنا هي القطع المكملة للغز عملك.",
"network": {
"title": "البنية التحتية وأمن الشبكات",
"desc": "تصميم وتنفيذ وتأمين الشبكات المعقدة. من تكوين أجهزة التوجيه إلى مراقبة الطبقة السابعة."
},
"software": {
"title": "تطوير برمجيات مخصصة",
"desc": "بناء منصات سحابية قوية وأتمتة العمليات المؤسسية."
},
"branding": {
"title": "الهوية البصرية",
"desc": "تصميم واجهة المستخدم (UI/UX) والعلامات التجارية الحديثة."
},
"academy": {
"title": "أكاديمية",
"btn_enter": "مشاهدة"
}
},
"projects": {
"title": "مشاريع مختارة",
"subtitle": "مجموعة مختارة من التحديات التي تغلبنا عليها بنجاح.",
"view_project": "عرض المشروع",
"tabs": {
"all": "الكل",
"software": "برمجيات",
"network": "شبكات"
}
},
"academy": {
"title": "أخبار الأكاديمية",
"subtitle": "أحدث المقالات والبرامج التعليمية وأخبار التكنولوجيا.",
"viewAll": "عرض جميع المقالات",
"newBadge": "جديد",
"read": "اقرأ"
}
},
"footer": {
"badge": "📞 استشارة متخصصة",
"title": "هل أنت مستعد لترقية مشروعك؟",
"description": "سجل احتياجاتك وسيقوم فريقنا بتقديم خارطة طريق فنية وجدول زمني بناءً على احتياجات عملك.",
"feature1": "اتصال أولي في أقل من 24 ساعة عمل",
"feature2": "تحليل أولي مصمم خصيصًا لنوع مشروعك",
"feature3": "خطة التنفيذ + تقدير الجدول الزمني",
"form": {
"nameLabel": "الاسم الكامل",
"namePlaceholder": "مثال: أحمد علي",
"phoneLabel": "رقم الهاتف",
"phonePlaceholder": "05xxxxxxxx",
"serviceLabel": "نوع الخدمة",
"servicePlaceholder": "اختر",
"serviceNetwork": "شبكات",
"serviceSoftware": "برمجيات",
"serviceMarketing": "تسويق",
"descLabel": "وصف قصير للمشروع",
"descPlaceholder": "ما هو التحدي الرئيسي وما هي النتيجة المطلوبة؟",
"submit": "إرسال الطلب"
},
"brand": "روبن نتورك",
"brandDesc": "هندسة النظم وإنشاء المحتوى والحلول الفنية لنمو الأعمال المستدام.",
"links": {
"home": "الرئيسية",
"software": "برمجيات",
"network": "شبكات",
"academy": "الأكاديمية"
},
"allProjects": "عرض جميع المشاريع",
"copyright": "© 2026 روبن نتورك. جميع الحقوق محفوظة."
}
}

290
src/messages/en.json Normal file
View File

@@ -0,0 +1,290 @@
{
"navbar": {
"home": "Home",
"software": "Software",
"network": "Network",
"academy": "Academy",
"logo1": "Robin",
"logo2": "Network"
},
"academy": {
"single": {
"notFound": "Article not found",
"backToAcademy": "Back to Academy",
"badge": "Technology",
"readTime": "min read",
"authorRole": "Science & Technology",
"tableOfContents": "Table of Contents",
"readingProgress": "Reading Progress",
"share": "Share",
"relatedArticles": "Related Articles"
},
"page": {
"hero": {
"badge": "Robin Network Academy",
"title1": "Knowledge & Experience",
"title2": "Shared",
"subtitle": "Specialized articles, tutorials, and real-world experiences from network and software projects"
},
"searchPlaceholder": "Search articles...",
"featuredInCard": "Network",
"minutesRead": "min read",
"pagination": {
"previous": "<",
"next": ">"
},
"tags": {
"mikrotik": "MikroTik",
"vlan": "VLAN",
"routing": "Routing",
"network": "Network"
}
}
},
"network": {
"hero": {
"badge": "Network & Security Engineering",
"title1": "Network Infrastructure",
"title2": "Stable & Secure",
"subtitle": "Designing, implementing, and managing complex network infrastructures with a focus on security, stability, and scalability.",
"viewPortfolio": "View Portfolio",
"freeConsultation": "Get Free Consultation"
},
"services": {
"sectionTitle": "Network Services",
"sectionSubtitle": "Comprehensive solutions for your organization's network infrastructure",
"items": {
"wifi": { "title": "Wireless Network", "subtitle": "Enterprise WIFI" },
"security": { "title": "Security", "subtitle": "IDS/IPS & Firewall" },
"design": { "title": "Network Design", "subtitle": "Architecture & Topology" },
"support": { "title": "Support", "subtitle": "24/7 Support" },
"vpn": { "title": "VPN & WAN", "subtitle": "Site-to-Site Connectivity" },
"monitoring": { "title": "Monitoring", "subtitle": "24/7 Surveillance" }
}
},
"technologies": {
"title": "Technologies",
"subtitle": "Equipment and technologies used"
},
"projects": {
"title": "Network Projects",
"subtitle": "A selection of our implemented network projects",
"categories": {
"network": "Network",
"software": "Software"
}
}
},
"software": {
"hero": {
"badge": "⚙ Custom Software Development",
"title1": "From Idea to",
"title2": "Live Platform",
"description": "Building modern web applications, scalable APIs, and enterprise systems with clean architecture and maintainable code.",
"stats": {
"deliveredProjects": "+50",
"deliveredProjectsLabel": "Projects Delivered",
"technologies": "+12",
"technologiesLabel": "Technologies",
"customerSatisfaction": "98%",
"customerSatisfactionLabel": "Customer Satisfaction"
},
"buttons": {
"startProject": "Start Project",
"viewPortfolio": "View Portfolio"
}
},
"projects": {
"sectionTitle": "Our Projects",
"sectionSubtitle": "Explore our latest work and successful case studies.",
"viewDetails": "View Details",
"network": "Network",
"software": "Software"
},
"techStack": {
"title": "Our Technologies",
"subtitle": "A complete software development ecosystem at a glance",
"center": "Tech Stack",
"items": {
"react": {
"name": "React",
"description": "A powerful library for building interactive user interfaces"
},
"nextjs": {
"name": "Next.js",
"description": "Advanced React framework for production-grade projects"
},
"postgresql": {
"name": "PostgreSQL",
"description": "Powerful relational database with advanced features"
},
"mongodb": {
"name": "MongoDB",
"description": "Fast and scalable NoSQL database"
},
"docker": {
"name": "Docker",
"description": "Containerization for seamless deployment and development"
},
"django": {
"name": "Django",
"description": "High-level Python web framework for rapid development"
},
"nestjs": {
"name": "NestJS",
"description": "Progressive Node.js framework for building scalable server-side applications"
},
"typescript": {
"name": "TypeScript",
"description": "Strongly typed JavaScript for safer and maintainable code"
}
}
},
"process": {
"title": "Software Development Process",
"subtitle": "From idea to final product using Agile methodology",
"steps": [
{
"number": "01",
"icon": "🔍",
"title": "Discovery",
"description": "Requirements analysis and project scope definition",
"tags": ["Requirements", "Research"]
},
{
"number": "02",
"icon": "🎨",
"title": "Design",
"description": "UI/UX design and system architecture",
"tags": ["Wireframes", "Architecture"]
},
{ "number": "03", "icon": "💻", "title": "Development", "description": "Coding with high standards", "tags": ["Clean Code", "Git Flow"] },
{
"number": "04",
"icon": "🐛",
"title": "Testing",
"description": "Comprehensive testing and quality assurance",
"tags": ["Unit Tests", "QA"]
},
{ "number": "05", "icon": "🚀", "title": "Deployment", "description": "Production deployment and launch", "tags": ["CI/CD", "Docker"] },
{ "number": "06", "icon": "⚙️", "title": "Support", "description": "Continuous support and maintenance", "tags": ["Monitoring", "Updates"] }
],
"badges": [
{ "icon": "⏱", "title": "2-4", "subtitle": "Weeks Sprint" },
{ "icon": "🔄", "title": "Agile", "subtitle": "Methodology" },
{ "icon": "🎧", "title": "24/7", "subtitle": "Support" }
]
},
"services": {
"header": {
"title": "Software Development Services",
"subtitle": "Comprehensive solutions from design to deployment and maintenance"
},
"items": {
"webApp": {
"title": "Web App Development",
"description": "Building advanced web applications with React, Next.js, and Vue.js"
},
"api": {
"title": "API Design & Development",
"description": "Building scalable RESTful and GraphQL APIs"
},
"cloud": {
"title": "Cloud Infrastructure",
"description": "Application deployment and management on cloud infrastructure"
},
"dashboards": {
"title": "Admin Dashboards",
"description": "Analytical dashboards and Content Management Systems (CMS)"
},
"mobile": {
"title": "Mobile Applications",
"description": "Developing cross-platform apps with React Native"
},
"automation": {
"title": "Process Automation",
"description": "Automating repetitive tasks and optimizing workflows"
}
}
}
},
"home": {
"hero": {
"badge": "Scalable Systems Architecture",
"title1": "We untangle your",
"title2": "technical knots",
"subtitle": "A combination of engineering authority in infrastructure and modern innovation in software development, from setting up complex networks to developing custom cloud platforms.",
"btn_consulting": "Get Free Consultation",
"btn_portfolio": "View Portfolio"
},
"services": {
"title": "Integrated Solutions",
"subtitle": "Our services are the missing pieces of your business puzzle.",
"network": {
"title": "Network Infrastructure & Security",
"desc": "Designing, implementing, and securing complex networks. From hardware router configuration to Layer 7 monitoring."
},
"software": {
"title": "Custom Software Development",
"desc": "Building powerful cloud platforms and enterprise process automation."
},
"branding": {
"title": "Visual Identity",
"desc": "Modern UI/UX design and branding."
},
"academy": {
"title": "Academy",
"btn_enter": "View"
}
},
"projects": {
"title": "Featured Projects",
"subtitle": "A selection of challenges we have successfully overcome.",
"view_project": "View Project",
"tabs": {
"all": "All",
"software": "Software",
"network": "Network"
}
},
"academy": {
"title": "Academy News",
"subtitle": "Latest articles, tutorials, and tech news.",
"viewAll": "View All Articles",
"newBadge": "New",
"read": "Read More"
}
},
"footer": {
"badge": "📞 Expert Consultation",
"title": "Ready to upgrade your project?",
"description": "Submit your requirements, and our team will provide a technical roadmap and timeline based on your business needs.",
"feature1": "Initial contact in less than 24 business hours",
"feature2": "Initial analysis tailored to your project type",
"feature3": "Execution map + Timeline estimation",
"form": {
"nameLabel": "Full Name",
"namePlaceholder": "e.g., John Doe",
"phoneLabel": "Phone Number",
"phonePlaceholder": "+1xxxxxxxxxx",
"serviceLabel": "Service Type",
"servicePlaceholder": "Select",
"serviceNetwork": "Network",
"serviceSoftware": "Software",
"serviceMarketing": "Marketing",
"descLabel": "Short Project Description",
"descPlaceholder": "What is your main challenge and desired output?",
"submit": "Submit Request"
},
"brand": "Robin Network",
"brandDesc": "System architecture, content creation, and technical solutions for sustainable business growth.",
"links": {
"home": "Home",
"software": "Software",
"network": "Network",
"academy": "Academy"
},
"allProjects": "View All Projects",
"copyright": "© 2026 Robin Network. All rights reserved."
}
}

278
src/messages/fa.json Normal file
View File

@@ -0,0 +1,278 @@
{
"navbar": {
"home": "صفحه اصلی",
"software": "نرم افزار",
"network": "شبکه",
"academy": "آکادمی",
"logo1": "رابین",
"logo2": "شبکه"
},
"academy": {
"single": {
"notFound": "مقاله یافت نشد",
"backToAcademy": "بازگشت به آکادمی",
"badge": "تکنولوژی",
"readTime": "دقیقه مطالعه",
"authorRole": "علم و فناوری",
"tableOfContents": "فهرست مطالب",
"readingProgress": "پیشرفت مطالعه",
"share": "اشتراک‌گذاری",
"relatedArticles": "مقالات مرتبط"
},
"page": {
"hero": {
"badge": "آکادمی شبکه رابین",
"title1": "دانش و تجربه،",
"title2": "به اشتراک گذاشته شده",
"subtitle": "مقالات تخصصی، آموزش‌ها و تجربیات واقعی از پروژه‌های شبکه و نرم‌افزار"
},
"searchPlaceholder": "جستجوی مقالات...",
"featuredInCard": "شبکه",
"minutesRead": "دقیقه مطالعه",
"pagination": {
"previous": ">",
"next": "<"
},
"tags": {
"mikrotik": "میکروتیک",
"vlan": "VLAN",
"routing": "مسیریابی",
"network": "شبکه"
}
}
},
"network": {
"hero": {
"badge": "مهندسی شبکه و امنیت",
"title1": "زیرساخت شبکه",
"title2": "پایدار و امن",
"subtitle": "طراحی، پیاده‌سازی و مدیریت زیرساخت‌های شبکه پیچیده با تمرکز بر امنیت، پایداری و مقیاس‌پذیری",
"viewPortfolio": "مشاهده نمونه‌کارها",
"freeConsultation": "دریافت مشاوره رایگان"
},
"services": {
"sectionTitle": "خدمات شبکه",
"sectionSubtitle": "راهکارهای جامع برای زیرساخت شبکه سازمان شما",
"items": {
"wifi": { "title": "شبکه بی‌سیم", "subtitle": "WIFI Enterprise" },
"security": { "title": "امنیت", "subtitle": "IDS/IPS و Firewall" },
"design": { "title": "طراحی شبکه", "subtitle": "معماری و توپولوژی" },
"support": { "title": "پشتیبانی", "subtitle": "Support ۲۴/۷" },
"vpn": { "title": "VPN & WAN", "subtitle": "اتصال بین سایت" },
"monitoring": { "title": "مانیتورینگ", "subtitle": "نظارت ۲۴/۷" }
}
},
"technologies": {
"title": "تکنولوژی‌ها",
"subtitle": "تجهیزات و فناوری‌های مورد استفاده"
},
"projects": {
"title": "پروژه‌های شبکه",
"subtitle": "نمونه‌ای از پروژه‌های شبکه اجرا شده",
"categories": {
"network": "شبکه",
"software": "نرم‌افزار"
}
}
},
"software": {
"hero": {
"badge": "⚙ توسعه نرم‌افزار سفارشی",
"title1": "از ایده تا",
"title2": "پلتفرم زنده",
"description": "ساخت اپلیکیشن‌های وب مدرن، API های مقیاس‌پذیر و سیستم‌های سازمانی با معماری تمیز و کد قابل نگهداری.",
"stats": {
"deliveredProjects": "+۵۰",
"deliveredProjectsLabel": "پروژه تحویلی",
"technologies": "+۱۲",
"technologiesLabel": "تکنولوژی",
"customerSatisfaction": "٪۹۸",
"customerSatisfactionLabel": "رضایت مشتری"
},
"buttons": {
"startProject": "شروع پروژه",
"viewPortfolio": "مشاهده نمونه‌کارها"
}
},
"projects": {
"sectionTitle": "پروژه‌های ما",
"sectionSubtitle": "جدیدترین نمونه‌کارها و پروژه‌های موفق ما را بررسی کنید.",
"viewDetails": "مشاهده جزئیات",
"network": "شبکه",
"software": "نرم‌افزار"
},
"techStack": {
"title": "تکنولوژی‌های ما",
"subtitle": "اکوسیستم کامل توسعه نرم‌افزار در یک نگاه",
"center": "Tech Stack",
"items": {
"react": {
"name": "React",
"description": "کتابخانه قدرتمند برای ساخت رابط‌های تعاملی"
},
"nextjs": {
"name": "Next.js",
"description": "فریم‌ورک پیشرفته React برای پروژه‌های تولیدی"
},
"postgresql": {
"name": "PostgreSQL",
"description": "پایگاه داده رابطه‌ای قدرتمند با امکانات پیشرفته"
},
"mongodb": {
"name": "MongoDB",
"description": "پایگاه داده NoSQL سریع و مقیاس‌پذیر"
},
"docker": {
"name": "Docker",
"description": "کانتینرسازی برای استقرار و توسعه یکپارچه"
},
"django": {
"name": "Django",
"description": "فریم‌ورک سطح بالای پایتون برای توسعه سریع"
},
"nestjs": {
"name": "NestJS",
"description": "فریم‌ورک پیشرونده Node.js برای ساخت برنامه‌های مقیاس‌پذیر"
},
"typescript": {
"name": "TypeScript",
"description": "جاوااسکریپت تایپ‌دار برای کد امن‌تر و قابل نگهداری"
}
}
},
"process": {
"title": "فرآیند توسعه نرم‌افزار",
"subtitle": "از ایده تا محصول نهایی با متدولوژی Agile",
"steps": [
{
"number": "۰۱",
"icon": "🔍",
"title": "Discovery",
"description": "تحلیل نیازمندی و تعریف محدوده پروژه",
"tags": ["Requirements", "Research"]
},
{ "number": "۰۲", "icon": "🎨", "title": "Design", "description": "طراحی UI/UX و معماری سیستم", "tags": ["Wireframes", "Architecture"] },
{ "number": "۰۳", "icon": "💻", "title": "Development", "description": "کدنویسی با استانداردهای بالا", "tags": ["Clean Code", "Git Flow"] },
{ "number": "۰۴", "icon": "🐛", "title": "Testing", "description": "تست و کنترل کیفیت کامل", "tags": ["Unit Tests", "QA"] },
{ "number": "۰۵", "icon": "🚀", "title": "Deployment", "description": "استقرار و راه‌اندازی Production", "tags": ["CI/CD", "Docker"] },
{ "number": "۰۶", "icon": "⚙️", "title": "Support", "description": "پشتیبانی و نگهداری مستمر", "tags": ["Monitoring", "Updates"] }
],
"badges": [
{ "icon": "⏱", "title": "۲-۴", "subtitle": "هفته Sprint" },
{ "icon": "🔄", "title": "Agile", "subtitle": "Methodology" },
{ "icon": "🎧", "title": "۲۴/۷", "subtitle": "Support" }
]
},
"services": {
"header": {
"title": "خدمات توسعه نرم‌افزار",
"subtitle": "راهکارهای جامع از طراحی تا استقرار و نگهداری"
},
"items": {
"webApp": {
"title": "توسعه وب اپلیکیشن",
"description": "ساخت اپلیکیشن‌های وب پیشرفته با React, Next.js و Vue.js"
},
"api": {
"title": "طراحی و توسعه API",
"description": "ساخت RESTful و GraphQL API های مقیاس‌پذیر"
},
"cloud": {
"title": "راه‌اندازی ابری",
"description": "استقرار و مدیریت اپلیکیشن در زیرساخت ابری"
},
"dashboards": {
"title": "پنل‌های مدیریتی",
"description": "داشبوردهای تحلیلی و سیستم‌های مدیریت محتوا"
},
"mobile": {
"title": "اپلیکیشن موبایل",
"description": "توسعه اپ‌های کراس‌پلتفرم با React Native"
},
"automation": {
"title": "اتوماسیون فرآیندها",
"description": "خودکارسازی وظایف تکراری و بهینه‌سازی جریان کار"
}
}
}
},
"home": {
"hero": {
"badge": "معماری سیستم‌های مقیاس‌پذیر",
"title1": "ما گره‌های فنی شما را",
"title2": "باز می‌کنیم",
"subtitle": "ترکیبی از اقتدار مهندسی در زیرساخت و نوآوری مدرن در توسعه نرم‌افزار، از راه‌اندازی شبکه‌های پیچیده تا توسعه پلتفرم‌های ابری سفارشی.",
"btn_consulting": "دریافت مشاوره رایگان",
"btn_portfolio": "مشاهده نمونه‌کارها"
},
"services": {
"title": "راهکارهای یکپارچه",
"subtitle": "خدمات ما پازل‌های تکمیل‌کننده کسب‌وکار شما هستند.",
"network": {
"title": "زیرساخت و امنیت شبکه",
"desc": "طراحی، پیاده‌سازی و ایمن‌سازی شبکه‌های پیچیده. از کانفیگ روترهای سخت‌افزاری تا مانیتورینگ لایه هفت."
},
"software": {
"title": "توسعه نرم‌افزار اختصاصی",
"desc": "ساخت پلتفرم‌های ابری قدرتمند و اتوماسیون فرآیندهای سازمانی."
},
"branding": {
"title": "هویت بصری",
"desc": "طراحی رابط کاربری (UI/UX) و برندینگ مدرن."
},
"academy": {
"title": "آکادمی",
"btn_enter": "مشاهده"
}
},
"projects": {
"title": "پروژه‌های منتخب",
"subtitle": "گزیده‌ای از چالش‌هایی که با موفقیت پشت سر گذاشتیم.",
"view_project": "مشاهده پروژه",
"tabs": {
"all": "همه",
"software": "نرم‌افزار",
"network": "شبکه"
}
},
"academy": {
"title": "تازه‌های آکادمی",
"subtitle": "آخرین مقالات، آموزش‌ها و اخبار دنیای فناوری.",
"viewAll": "مشاهده همه مقالات",
"newBadge": "جدید",
"read": "مطالعه"
}
},
"footer": {
"badge": "📞 مشاوره تخصصی رابین",
"title": "آماده ارتقای پروژه خود هستید؟",
"description": "نیازت رو ثبت کن تا تیم ما بر اساس مسئله واقعی کسب‌وکارت، مسیر اجرای فنی و زمانی پیشنهادی ارائه بده.",
"feature1": "تماس اولیه در کمتر از ۲۴ ساعت کاری",
"feature2": "تحلیل اولیه متناسب با نوع پروژه",
"feature3": "نقشه اجرا + برآورد زمان‌بندی",
"form": {
"nameLabel": "نام و نام خانوادگی",
"namePlaceholder": "مثلاً: علی احمدی",
"phoneLabel": "شماره تماس",
"phonePlaceholder": "09xxxxxxxxx",
"serviceLabel": "نوع خدمت",
"servicePlaceholder": "انتخاب کنید",
"serviceNetwork": "شبکه",
"serviceSoftware": "نرم‌افزار",
"serviceMarketing": "مارکتینگ",
"descLabel": "توضیح کوتاه پروژه",
"descPlaceholder": "مسئله اصلی شما چیست و چه خروجی ای می خواهید؟",
"submit": "ارسال درخواست"
},
"brand": "رابین نتورک",
"brandDesc": "معماری سیستم، تولید محتوا و اجرای راهکارهای فنی برای رشد پایدار کسب‌وکارها.",
"links": {
"home": "صفحه اصلی",
"software": "نرم‌افزار",
"network": "شبکه",
"academy": "آکادمی"
},
"allProjects": "مشاهده همه پروژه‌ها",
"copyright": "© ۲۰۲۶ رابین نتورک. تمامی حقوق محفوظ است."
}
}

13
src/middleware.ts Normal file
View File

@@ -0,0 +1,13 @@
// src/middleware.ts
import createMiddleware from "next-intl/middleware";
export default createMiddleware({
locales: ["fa", "en", "ar"],
defaultLocale: "fa",
localePrefix: "always",
localeDetection: false,
});
export const config = {
matcher: ["/", "/(fa|en|ar)/:path*", "/((?!_next|_vercel|.*\\..*).*)"],
};

View File

@@ -0,0 +1,2 @@
export const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL;
export const BACKEND_URL_LOCAL = process.env.NEXT_PUBLIC_BACKEND_URL_LOCAL;

View File

@@ -0,0 +1,15 @@
import { BlogTranslation } from "../types/blog.type";
export function calculateReadingTime(translation: BlogTranslation): number {
let text = `${translation.title} ${translation.description}`;
translation.editor.forEach((block) => {
if (block.type === "paragraph" || block.type === "heading") {
text += ` ${block.content}`;
} else if (block.type === "link") {
text += ` ${block.content.content}`;
}
});
const wordCount = text.trim().split(/\s+/).length;
return Math.max(1, Math.ceil(wordCount / 200));
}

View File

@@ -0,0 +1,23 @@
import { formatToPersianDate } from "./format-persian-date";
export const formatDateByLocale = (dateString: string, locale: string) => {
console.log(dateString);
if (locale === "fa") {
return formatToPersianDate(new Date(dateString));
}
if (locale === "ar") {
return new Date(dateString).toLocaleDateString("ar-SA", {
year: "numeric",
month: "long",
day: "numeric",
calendar: "islamic",
});
}
return new Date(dateString).toLocaleDateString(locale, {
year: "numeric",
month: "short",
day: "numeric",
});
};

View File

@@ -0,0 +1,7 @@
export const formatToPersianDate = (date: Date) => {
return new Intl.DateTimeFormat("fa-IR", {
day: "numeric",
month: "long",
year: "numeric",
}).format(date);
};

View File

@@ -0,0 +1,10 @@
export const handleScrollToId = (id: string) => {
const target = document.getElementById(id);
if (target) {
target.scrollIntoView({
behavior: "smooth",
block: "center",
});
}
};

View File

@@ -0,0 +1,50 @@
export interface Blog {
id: string;
featuredImage: string;
gallery: string[];
href: string;
publishedAt: string;
createdAt: string;
translations: BlogTranslation[];
writer: {
username: string;
};
}
export interface BlogTranslation {
id: string;
title: string;
description: string;
editor: EditorBlock[];
language: string;
blogId: string;
}
export type EditorBlock = HeadingBlock | ParagraphBlock | ImageBlock | LinkBlock;
export interface HeadingBlock {
sort: number;
type: "heading";
content: string;
}
export interface ParagraphBlock {
sort: number;
type: "paragraph";
content: string;
}
export interface ImageBlock {
sort: number;
type: "image";
content: number;
}
export interface LinkBlock {
sort: number;
type: "link";
content: {
link: string;
content: string;
};
}

View File

@@ -0,0 +1,16 @@
export interface Portfolio {
id: string;
category: PortfolioCategoryType;
externalLink: string;
featuredImage: string;
gallery: string[];
href: string;
title: string;
description: string;
employer: string;
publishedAt: Date;
createdAt: Date;
updatedAt: Date;
}
export type PortfolioCategoryType = "network" | "software";