پروژه کامل: فرانت متصل به 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
+85
View File
@@ -0,0 +1,85 @@
import React, { useRef } from 'react'
import { motion, useScroll, useTransform, useInView } from 'framer-motion'
import { useSite, type TimelineItem } from '../../api'
import { SectionHeading } from '../SectionHeading'
const ease = [0.23, 1, 0.32, 1] as const
type Milestone = TimelineItem
function Item({ item, index }: { item: Milestone; index: number }) {
const ref = useRef<HTMLLIElement>(null)
const active = useInView(ref, { once: true, amount: 0.6 })
const onRight = index % 2 === 0
return (
<li ref={ref} className="relative md:grid md:grid-cols-2 md:gap-16">
<span
aria-hidden="true"
className={`absolute right-[-5px] top-6 z-10 h-2.5 w-2.5 rounded-full transition-all duration-500 ease-lux md:right-1/2 md:translate-x-1/2 ${
active
? 'bg-copper shadow-[0_0_0_5px_rgba(213,135,76,0.22)]'
: 'bg-ivory/25'
}`}
/>
<motion.div
className={`pr-8 md:pr-0 ${
onRight ? 'md:col-start-1 md:pl-16 md:text-left' : 'md:col-start-2 md:pr-16'
}`}
initial={{ opacity: 0, y: 24 }}
animate={active ? { opacity: 1, y: 0 } : { opacity: 0, y: 24 }}
transition={{ duration: 0.55, ease }}
>
<div className="border border-ivory/10 bg-ink p-6 md:p-7">
<span className="text-sm font-light text-copper">{item.year}</span>
<h3 className="mt-3 text-lg font-bold text-ivory">{item.title}</h3>
<p className="mt-3 text-sm font-light leading-8 text-ivory/60">{item.desc}</p>
</div>
</motion.div>
</li>
)
}
export function Timeline() {
const { timeline } = useSite()
const ref = useRef<HTMLDivElement>(null)
const { scrollYProgress } = useScroll({
target: ref,
offset: ['start 70%', 'end 60%'],
})
const scaleY = useTransform(scrollYProgress, [0, 1], [0, 1])
return (
<section className="bg-char py-24 md:py-32" aria-labelledby="timeline-title">
<div className="mx-auto w-full max-w-site px-6 lg:px-10">
<div className="mb-16">
<SectionHeading
title="مسیر ما"
lead="از یک دفتر چهارنفره تا ۴۷ پروژه. خط زمان پروژه‌های ما را ببینید."
align="center"
/>
</div>
<div ref={ref} className="relative mx-auto max-w-4xl">
<span
aria-hidden="true"
className="absolute inset-y-0 right-0 w-px bg-ivory/12 md:right-1/2"
/>
<motion.span
aria-hidden="true"
className="absolute inset-y-0 right-0 w-px origin-top bg-copper md:right-1/2"
style={{ scaleY }}
/>
<ol className="flex flex-col gap-12 md:gap-16">
{timeline.map((item, i) => (
<Item key={item.year} item={item} index={i} />
))}
</ol>
</div>
</div>
</section>
)
}