پروژه کامل: فرانت متصل به 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
+105
View File
@@ -0,0 +1,105 @@
import { createContext, useCallback, useContext, useEffect, useState } from 'react'
import type { ReactNode } from 'react'
import type { Article, Project, Service } from './data/site'
import { API_URL } from './config'
// انواع داده‌هایی که بک‌اند در GET /api/site برمی‌گرداند
export type BrandInfo = {
id: number
name: string
logo: string
hero: string
contact_bg: string
phone: string
phone_href: string
email: string
address: string
}
export type TestimonialItem = { id: number; name: string; role: string; quote: string }
export type FaqItem = { id: number; question: string; answer: string }
export type TeamMemberItem = { id: number; name: string; role: string; bio: string }
export type TimelineItem = { id: number; year: string; title: string; desc: string; image: string }
export type StatItem = { id: number; value: number; suffix: string; label: string }
export type WhyUsItem = { id: number; title: string; desc: string }
export type BeforeAfterInfo = {
id: number
title: string
lead: string
before: string
after: string
before_label: string
after_label: string
}
export interface SiteData {
brand: BrandInfo
services: Service[]
projects: Project[]
articles: Article[]
testimonials: TestimonialItem[]
faqs: FaqItem[]
team: TeamMemberItem[]
timeline: TimelineItem[]
stats: StatItem[]
why_us: WhyUsItem[]
before_after: BeforeAfterInfo
}
interface SiteContextValue {
data: SiteData | null
error: boolean
reload: () => void
}
const SiteContext = createContext<SiteContextValue>({ data: null, error: false, reload: () => {} })
export function SiteProvider({ children }: { children: ReactNode }) {
const [data, setData] = useState<SiteData | null>(null)
const [error, setError] = useState(false)
const load = useCallback(() => {
let cancelled = false
setError(false)
fetch(`${API_URL}/api/site`)
.then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.json() as Promise<SiteData>
})
.then((json) => {
if (!cancelled) {
if (json.brand && json.before_after) setData(json)
else setError(true)
}
})
.catch(() => {
if (!cancelled) setError(true)
})
return () => {
cancelled = true
}
}, [])
useEffect(() => load(), [load])
const reload = useCallback(() => {
setData(null)
setError(false)
load()
}, [load])
return <SiteContext.Provider value={{ data, error, reload }}>{children}</SiteContext.Provider>
}
export function useSiteData(): SiteContextValue {
return useContext(SiteContext)
}
// فقط در کامپوننت‌هایی استفاده شود که پس از بارگذاری کامل داده‌ها رندر می‌شوند
export function useSite(): SiteData {
const { data } = useContext(SiteContext)
if (!data) {
throw new Error('useSite باید پس از بارگذاری داده‌های سایت استفاده شود')
}
return data
}