پروژه کامل: فرانت متصل به API + داکرایز

- اتصال کامل فرانت به API: همه محتوای سایت (پروژه‌ها، مقالات، خدمات، برند و...) از بک‌اند خوانده می‌شود (SiteProvider + useSite)
- فرم تماس و خبرنامه متصل به بک‌اند و تست‌شده در مرورگر
- ساختار مونوریپو: frontend/ + backend/
- Dockerfile برای هر دو بخش + docker-compose (PostgreSQL + FastAPI + nginx)
- nginx با پروکسی /api — بدون نیاز به CORS در حالت داکر
- README کامل فارسی
This commit is contained in:
Behrad
2026-08-29 13:26:58 +03:30
parent ed14435485
commit ad75f7a7bd
78 changed files with 8242 additions and 133 deletions
+71
View File
@@ -0,0 +1,71 @@
import React, { useState } from 'react';
import { PageHero } from '../components/PageHero';
import { Reveal } from '../components/Reveal';
import { ProjectCard } from '../components/ProjectCard';
import { Gallery } from '../components/home/Gallery';
import { ContactSection } from '../components/ContactSection';
import { IMAGES, type ProjectStatus } from '../data/site';
import { useSite } from '../api';
const FILTERS: {key: ProjectStatus;label: string;}[] = [
{ key: 'done', label: 'انجام شده' },
{ key: 'progress', label: 'در حال انجام' }];
export function Projects() {
const { projects } = useSite();
const [status, setStatus] = useState<ProjectStatus>('done');
const list = projects.filter((p) => p.status === status);
return (
<>
<PageHero
title="پروژه‌ها"
lead="مجموعه‌ای از برج‌های مسکونی، مراکز تجاری و ویلاهای اختصاصی؛ از پروژه‌های تحویل‌شده تا کارگاه‌های فعال امروز."
image={IMAGES[0]} />
<section className="bg-ink py-24 lg:py-32" aria-label="فهرست پروژه‌ها">
<div className="mx-auto max-w-site px-6 lg:px-10">
<div className="flex flex-wrap gap-3" role="tablist" aria-label="فیلتر وضعیت پروژه">
{FILTERS.map((filter) =>
<button
key={filter.key}
type="button"
role="tab"
aria-selected={status === filter.key}
onClick={() => setStatus(filter.key)}
className={`group relative overflow-hidden border px-7 py-3 text-sm transition-colors duration-300 ease-lux ${
status === filter.key ?
'border-copper bg-copper font-bold text-char' :
'border-ivory/15 font-light text-ivory/65'}`
}>
{status !== filter.key &&
<span
aria-hidden="true"
className="absolute inset-0 translate-y-full bg-copper transition-transform duration-300 ease-lux group-hover:translate-y-0" />
}
<span className="relative z-10 transition-colors duration-300 ease-lux group-hover:text-char">
{filter.label}
</span>
</button>
)}
</div>
<div className="mt-12 grid gap-6 lg:grid-cols-3">
{list.map((project, i) =>
<Reveal key={project.slug} delay={0.05 * i} className="h-full">
<ProjectCard project={project} imageClass="h-80" />
</Reveal>
)}
</div>
</div>
</section>
<Gallery />
<ContactSection />
</>);
}