Compare commits
13 Commits
889e3f9213
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1900d6ac36 | ||
| 7e25aed979 | |||
| 8801dee4b5 | |||
| cd0d947528 | |||
| b54bb3d217 | |||
| 41ecc7ee77 | |||
| 71a114ccb5 | |||
|
|
3edf6ff351 | ||
|
|
fc97bc5f59 | ||
|
|
96dabc975e | ||
|
|
8c051c3533 | ||
| d1d9c309f8 | |||
| 29768f497e |
119
.dockerignore
Normal file
119
.dockerignore
Normal file
@@ -0,0 +1,119 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
npm-debug.log*
|
||||
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
|
||||
|
||||
# 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
|
||||
43
Dockerfile
Normal file
43
Dockerfile
Normal file
@@ -0,0 +1,43 @@
|
||||
FROM node:22-bookworm-slim AS base
|
||||
|
||||
FROM base AS deps
|
||||
WORKDIR /app
|
||||
|
||||
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 NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
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"]
|
||||
57
README.md
57
README.md
@@ -1,36 +1,51 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
# 🚀 Robin Nework New Client Setup
|
||||
|
||||
## Getting Started
|
||||
Follow these steps to run the frontend locally.
|
||||
|
||||
First, run the development server:
|
||||
---
|
||||
|
||||
## 1️⃣ Install Dependencies
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
npm install
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
---
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
## 2️⃣ Create `.env` File
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
Create a `.env` file in the root directory and add:
|
||||
|
||||
## Learn More
|
||||
```env
|
||||
NEXT_PUBLIC_BACKEND_URL=api.robinnetwork.ir (for example)
|
||||
NEXT_PUBLIC_BACKEND_URL_LOCAL=http://127.0.0.1:4000
|
||||
```
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
⚠️ Make sure your backend server is running on port `4000`.
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
---
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
## 3️⃣ Run the Application
|
||||
|
||||
## Deploy on Vercel
|
||||
```bash
|
||||
npm run build
|
||||
npm start
|
||||
```
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
---
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
## ✅ Application URL
|
||||
|
||||
After running the command, the app will be available at:
|
||||
|
||||
```
|
||||
http://localhost:3000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Notes
|
||||
|
||||
- Restart the server after changing environment variables.
|
||||
- Ensure the backend is running before testing API requests.
|
||||
- `NEXT_PUBLIC_` variables are exposed to the browser.
|
||||
|
||||
17
ecosystem.config.js
Normal file
17
ecosystem.config.js
Normal 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",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,7 +1,27 @@
|
||||
import type { NextConfig } from "next";
|
||||
const createNextIntlPlugin = require("next-intl/plugin");
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
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);
|
||||
|
||||
1095
package-lock.json
generated
1095
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
},
|
||||
|
||||
51
src/app/[locale]/academy/[slug]/page.tsx
Normal file
51
src/app/[locale]/academy/[slug]/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
20
src/app/[locale]/academy/page.tsx
Normal file
20
src/app/[locale]/academy/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
42
src/app/[locale]/layout.tsx
Normal file
42
src/app/[locale]/layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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
37
src/app/[locale]/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
25
src/app/[locale]/software/page.tsx
Normal file
25
src/app/[locale]/software/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
178
src/components/academy/AcademyClient.tsx
Normal file
178
src/components/academy/AcademyClient.tsx
Normal 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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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">
|
||||
> {/* راست چین (قبلی) */}
|
||||
</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">
|
||||
< {/* چپ چین (بعدی) */}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
65
src/components/home/Academy.tsx
Normal file
65
src/components/home/Academy.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
89
src/components/home/Projects.tsx
Normal file
89
src/components/home/Projects.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
35
src/components/home/hero/Hero.tsx
Normal file
35
src/components/home/hero/Hero.tsx
Normal 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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
31
src/components/home/hero/HeroButtons.tsx
Normal file
31
src/components/home/hero/HeroButtons.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -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>‹</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>
|
||||
|
||||
@@ -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);
|
||||
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;
|
||||
};
|
||||
|
||||
const getYear = (date: Date | string) => {
|
||||
return new Date(date).toLocaleDateString(locale, { year: "numeric" });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex justify-center w-full px-5 py-20 bg-bg">
|
||||
<div className="flex justify-center w-full px-5 py-20 bg-bg" id="portfolios">
|
||||
<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 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 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>
|
||||
))}
|
||||
</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">
|
||||
<
|
||||
{"<"}
|
||||
</button>
|
||||
<button onClick={nextSlide} className="flex items-center justify-center transition border rounded-lg w-9 h-9 border-border hover:bg-card">
|
||||
>
|
||||
{">"}
|
||||
</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>
|
||||
))}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
78
src/components/shared/footer/Footer.tsx
Normal file
78
src/components/shared/footer/Footer.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
70
src/components/shared/footer/FooterForm.tsx
Normal file
70
src/components/shared/footer/FooterForm.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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 */}
|
||||
{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">
|
||||
<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>
|
||||
{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>
|
||||
)}
|
||||
|
||||
{/* 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">
|
||||
دانشمندان نشان دادهاند کودکانی که اختلال یادگیری ریاضی دارند، هنگام کار با نمادهای عددی رویکرد متفاوتی نسبت به دیگر کودکان دارند.
|
||||
{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>
|
||||
|
||||
<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>
|
||||
) : 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>
|
||||
|
||||
@@ -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>
|
||||
</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>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
{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={`w-full h-32 bg-gradient-to-br ${article.gradient} opacity-50 group-hover:opacity-100 transition-opacity`}></div>
|
||||
<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 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}
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -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">
|
||||
شروع پروژه <
|
||||
</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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
<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"
|
||||
>
|
||||
{"<"}
|
||||
</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="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"
|
||||
}`}
|
||||
>
|
||||
{f}
|
||||
</button>
|
||||
))}
|
||||
</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"
|
||||
>
|
||||
<
|
||||
</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">مشاهده جزئیات <</button>
|
||||
<span className="text-xs sm:text-sm text-muted">{project.stat}</span>
|
||||
</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"
|
||||
>
|
||||
>
|
||||
<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>
|
||||
|
||||
<style jsx>{`
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
44
src/components/software/hero/Hero.tsx
Normal file
44
src/components/software/hero/Hero.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
30
src/components/software/hero/HeroButtons.tsx
Normal file
30
src/components/software/hero/HeroButtons.tsx
Normal 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
18
src/i18n.ts
Normal 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
278
src/messages/ar.json
Normal 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
290
src/messages/en.json
Normal 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
278
src/messages/fa.json
Normal 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
13
src/middleware.ts
Normal 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|.*\\..*).*)"],
|
||||
};
|
||||
2
src/utilities/constants/urls.constant.ts
Normal file
2
src/utilities/constants/urls.constant.ts
Normal 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;
|
||||
15
src/utilities/lib/calculate-reading-time.ts
Normal file
15
src/utilities/lib/calculate-reading-time.ts
Normal 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));
|
||||
}
|
||||
23
src/utilities/lib/format-date-by-locale.ts
Normal file
23
src/utilities/lib/format-date-by-locale.ts
Normal 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",
|
||||
});
|
||||
};
|
||||
7
src/utilities/lib/format-persian-date.ts
Normal file
7
src/utilities/lib/format-persian-date.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const formatToPersianDate = (date: Date) => {
|
||||
return new Intl.DateTimeFormat("fa-IR", {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
}).format(date);
|
||||
};
|
||||
10
src/utilities/lib/scroll.ts
Normal file
10
src/utilities/lib/scroll.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export const handleScrollToId = (id: string) => {
|
||||
const target = document.getElementById(id);
|
||||
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center",
|
||||
});
|
||||
}
|
||||
};
|
||||
50
src/utilities/types/blog.type.ts
Normal file
50
src/utilities/types/blog.type.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
16
src/utilities/types/portfolio.type.ts
Normal file
16
src/utilities/types/portfolio.type.ts
Normal 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";
|
||||
Reference in New Issue
Block a user