commit a146846cc007ebc43391516adc7133c646891702 Author: “dongming” <“lidongming@aituringflow.com”> Date: Fri Dec 19 17:06:23 2025 +0800 first commit diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..4041b5e --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,154 @@ +name: Deploy to Cloudflare Pages + +on: + push: + tags: + - 'deploy-*' # 只在推送 deploy-* 标签时触发 + +jobs: + deploy: + runs-on: ubuntu-latest + container: + image: swr.cn-south-1.myhuaweicloud.com/bws/node:20.19.6-bookworm-slim-ci + + steps: + # 1. 拉取代码 + - name: Checkout + uses: actions/checkout@v4 + + # 2. 设置 pnpm(使用 corepack,避免从 GitHub 拉取 action) + - name: Setup pnpm + run: | + corepack enable + corepack prepare pnpm@10.23.0 --activate + pnpm --version + + - name: Parse tag to env + shell: bash + run: | + # 获取 tag 名称(Gitea Actions 使用 GITHUB_REF_NAME) + TAG_NAME="${GITHUB_REF_NAME}" + echo "TAG_NAME=$TAG_NAME" + + # Tag 格式: deploy-{project_name}-{deploymentId_no_dashes} + # 例如: deploy-b7ea026a-cf09-4e31-9f29-b55d7c652b71-123e4567e89b12d3a456426614174000 + + # 去掉 "deploy-" 前缀 + PREFIX="deploy-" + REST="${TAG_NAME#$PREFIX}" + + # deploymentId(无破折号)固定是最后32个字符 + DEPLOYMENT_ID="${REST: -32}" + + # project_name 是剩余部分(去掉最后的 "-" 和 deploymentId) + PROJECT_NAME="${REST%-${DEPLOYMENT_ID}}" + + echo "PROJECT_NAME=$PROJECT_NAME" >> "$GITHUB_ENV" + echo "DEPLOYMENT_ID=$DEPLOYMENT_ID" >> "$GITHUB_ENV" + #echo "DOMAIN=${PROJECT_NAME}-preview.turingflowai.com" >> "$GITHUB_ENV" + + # 调试输出 + echo "Parsed PROJECT_NAME: $PROJECT_NAME" + echo "Parsed DEPLOYMENT_ID: $DEPLOYMENT_ID" + + - name: Check toolchain (debug only, 可选) + run: | + node -v || echo "node not found" + pnpm -v || echo "pnpm not found" + curl --version || echo "curl not found" + + - name: Use CN npm registry + run: | + pnpm config set registry http://repo.myhuaweicloud.com/repository/npm/ + + - name: Install dependencies + run: | + pnpm install --frozen-lockfile + + - name: Build + run: pnpm run build + + - name: Deploy to Cloudflare Pages + shell: bash + env: + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} + CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }} + PROJECT_NAME: ${{ env.PROJECT_NAME }} + DOMAIN: ${{ env.DOMAIN }} + run: | + set -euo pipefail + echo "[deploy] project: $PROJECT_NAME" + #echo "[deploy] domain: $DOMAIN" + + # 部署到 Cloudflare Pages (假定构建产物在 dist/) + # 使用项目本地安装的 wrangler + npx wrangler pages deploy dist \ + --project-name "$PROJECT_NAME" \ + --branch main + + # 绑定自定义域名:-preview.turingflowai.com + #echo "[deploy] 正在绑定自定义域名..." + #DOMAIN_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ + # "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/${PROJECT_NAME}/domains" \ + # -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ + # -H "Content-Type: application/json" \ + # -d '{"name":"'"${DOMAIN}"'"}') + + #HTTP_CODE=$(echo "$DOMAIN_RESPONSE" | tail -n1) + #RESPONSE_BODY=$(echo "$DOMAIN_RESPONSE" | sed '$d') + + #if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "409" ]; then + # echo "[deploy] 域名绑定成功或已存在 (HTTP $HTTP_CODE)" + #else + # echo "[deploy] 警告: 域名绑定失败 (HTTP $HTTP_CODE)" + # echo "[deploy] 响应: $RESPONSE_BODY" + # echo "[deploy] 继续执行,但域名可能未绑定成功" + #fi + + - name: Notify Deploy Service (success) + if: success() + shell: bash + env: + DEPLOY_SERVICE_CALLBACK_URL: ${{ secrets.DEPLOY_SERVICE_CALLBACK_URL }} + DEPLOY_SERVICE_TOKEN: ${{ secrets.DEPLOY_SERVICE_TOKEN }} + DEPLOYMENT_ID: ${{ env.DEPLOYMENT_ID }} + run: | + set -euo pipefail + + # 获取当前 commit SHA (Gitea Actions 使用 GITHUB_SHA) + COMMIT_SHA="${GITHUB_SHA}" + + curl -X POST "$DEPLOY_SERVICE_CALLBACK_URL" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $DEPLOY_SERVICE_TOKEN" \ + -d '{ + "deploymentId": "'"${DEPLOYMENT_ID}"'", + "status": "deployed", + "commitSha": "'"${COMMIT_SHA}"'", + "cfDeploymentId": "", + "errorMessage": null + }' + + - name: Notify Deploy Service (failure) + if: failure() + shell: bash + env: + DEPLOY_SERVICE_CALLBACK_URL: ${{ secrets.DEPLOY_SERVICE_CALLBACK_URL }} + DEPLOY_SERVICE_TOKEN: ${{ secrets.DEPLOY_SERVICE_TOKEN }} + DEPLOYMENT_ID: ${{ env.DEPLOYMENT_ID }} + run: | + set -euo pipefail + + # 获取当前 commit SHA + COMMIT_SHA="${GITHUB_SHA}" + + curl -X POST "$DEPLOY_SERVICE_CALLBACK_URL" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $DEPLOY_SERVICE_TOKEN" \ + -d '{ + "deploymentId": "'"${DEPLOYMENT_ID}"'", + "status": "failed", + "commitSha": "'"${COMMIT_SHA}"'", + "cfDeploymentId": "", + "errorMessage": "see Gitea Actions logs" + }' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf6fb2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,158 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# =================== +# Dependencies +# =================== +node_modules/ +/.pnp +.pnp.js +.yarn/install-state.gz +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +package-lock.json +yarn.lock +pnpm-lock.yaml +bun.lockb + +# =================== +# Next.js +# =================== +/.next/ +/out/ +/build/ +.next +out + +# =================== +# Production +# =================== +/dist/ +*.min.js +*.min.css + +# =================== +# Testing +# =================== +/coverage/ +.nyc_output +*.lcov +jest-results.json + +# =================== +# TypeScript +# =================== +*.tsbuildinfo +next-env.d.ts +tsconfig.tsbuildinfo + +# =================== +# Environment Variables +# =================== +.env +.env.* +.env.local +.env.development.local +.env.test.local +.env.production.local +!.env.example + +# =================== +# IDE & Editors +# =================== +.idea/ +.vscode/ +*.swp +*.swo +*.sublime-workspace +*.sublime-project +.project +.classpath +.c9/ +*.launch +.settings/ +*.code-workspace + +# =================== +# OS Generated Files +# =================== +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +Desktop.ini + +# =================== +# Logs +# =================== +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# =================== +# Cache +# =================== +.cache/ +.parcel-cache/ +.eslintcache +.stylelintcache +*.cache +.turbo/ +.tanstack +.pnpm-store + +# =================== +# Vercel +# =================== +.vercel + +# =================== +# Debug +# =================== +*.pem +*.key +*.crt +*.p12 + +# =================== +# Misc +# =================== +*.pid +*.seed +*.pid.lock +*.orig +.temp/ +.tmp/ +tmp/ +temp/ + +# =================== +# Storybook +# =================== +storybook-static/ + +# =================== +# PWA +# =================== +public/sw.js +public/workbox-*.js +public/sw.js.map +public/workbox-*.js.map + +# =================== +# Sentry +# =================== +.sentryclirc + +# =================== +# Docker +# =================== +docker-compose.override.yml diff --git a/README.md b/README.md new file mode 100644 index 0000000..3550811 --- /dev/null +++ b/README.md @@ -0,0 +1,297 @@ +# turingflow-brand-001 + +企业品牌官网模板,使用 React 19 + Vite 7 + Tailwind CSS 4 + TanStack Router 。 + +## 技术栈 + +| 技术 | 版本 | 说明 | +|------|------|------| +| React | 19.2.3 (锁定) | UI 框架 | +| Vite | ^7.0.0 | 构建工具 | +| Tailwind CSS | ^4.0.0 | 样式框架 | +| TanStack Router | ^1.114.0 | 文件路由 | +| Swiper | ^11.2.0 | 轮播组件 | +| TypeScript | ^5.7.0 | 类型系统 | + +## 快速开始 + +```bash +pnpm install +pnpm dev +``` + +访问 即可预览,支持 HMR 热更新。 + +## 目录结构 + +```text +turingflow-brand-001-react/ +├── index.html # HTML 入口 +├── package.json # 依赖配置 +├── vite.config.ts # Vite 配置 +├── tsconfig.json # TypeScript 配置 +├── src/ +│ ├── main.tsx # 应用入口 +│ ├── index.css # 全局样式 + Tailwind 主题 +│ ├── routeTree.gen.ts # 自动生成的路由树 +│ ├── routes/ # 页面路由 +│ │ ├── __root.tsx # 根布局 (Header + Footer) +│ │ ├── index.tsx # 首页 / +│ │ ├── about.tsx # 关于 /about +│ │ ├── services.tsx # 服务 /services +│ │ └── contact.tsx # 联系 /contact +│ ├── components/ # 组件目录 +│ │ ├── layout/ # 布局组件 +│ │ │ ├── Header.tsx # 导航栏 +│ │ │ ├── Footer.tsx # 页脚 +│ │ │ └── ScrollToTop.tsx # 回到顶部 +│ │ ├── home/ # 首页组件 +│ │ │ ├── HeroSlider.tsx # 轮播 (Swiper) +│ │ │ ├── Features.tsx # 特性展示 +│ │ │ ├── Services.tsx # 服务卡片 +│ │ │ ├── CTA.tsx # Call-to-Action +│ │ │ ├── Testimonials.tsx # 客户评价 +│ │ │ ├── Stats.tsx # 统计数据 +│ │ │ └── LatestNews.tsx # 最新文章 +│ │ ├── about/ # 关于页组件 +│ │ │ ├── Mission.tsx # 使命愿景 +│ │ │ ├── WhyChooseUs.tsx # 为什么选择我们 +│ │ │ ├── Statistics.tsx # 统计指标 +│ │ │ └── Team.tsx # 团队成员 +│ │ ├── services/ # 服务页组件 +│ │ │ ├── ServiceCards.tsx # 服务卡片 +│ │ │ ├── ProcessSteps.tsx # 流程步骤 +│ │ │ └── AdvanceFeatures.tsx # 高级特性 +│ │ ├── contact/ # 联系页组件 +│ │ │ ├── ContactForm.tsx # 联系表单 +│ │ │ └── Map.tsx # 地图 +│ │ └── shared/ # 共享组件 +│ │ └── Breadcrumb.tsx # 面包屑 +│ ├── data/ +│ │ └── siteData.ts # 静态数据 +│ └── assets/ +│ └── images/ # 图片资源 +└── public/ # 公共资源 +``` + +## 页面路由 + +| 路径 | 文件 | 组件 | +|------|------|------| +| `/` | routes/index.tsx | HeroSlider, Features, Services, CTA, Testimonials, Stats, LatestNews | +| `/about` | routes/about.tsx | Breadcrumb, Mission, WhyChooseUs, Statistics, Team | +| `/services` | routes/services.tsx | Breadcrumb, ServiceCards, ProcessSteps, AdvanceFeatures | +| `/contact` | routes/contact.tsx | Breadcrumb, ContactForm, Map | + +## 主题配置 + +主题色和变量在 `src/index.css` 中定义: + +```css +@theme { + --color-primary: #2e5deb; /* 主题蓝 */ + --color-secondary: #ff5b83; /* 次要红粉 */ + --color-text: #585858; /* 正文灰 */ + --color-title: #1A1D2D; /* 标题深灰 */ + --color-light-bg: #f6f6f6; /* 浅色背景 */ + --font-family-sans: 'Poppins', sans-serif; + --font-family-body: 'Hind', sans-serif; +} +``` + +## 数据文件 + +所有静态数据集中在 `src/data/siteData.ts`: + +```typescript +// 导航菜单 +export const menuItems = [...] + +// 轮播数据 +export const sliderData = [...] + +// 服务数据 +export const servicesData = [...] + +// 客户评价 +export const testimonialsData = [...] + +// 统计数据 +export const statsData = [...] + +// 团队成员 +export const teamData = [...] + +// 联系信息 +export const contactInfo = {...} + +// 页脚链接 +export const footerLinks = {...} +``` + +## 组件说明 + +### 布局组件 + +#### Header (src/components/layout/Header.tsx) +- 固定导航栏,滚动时背景变色 +- 响应式汉堡菜单 +- 搜索弹窗功能 + +#### Footer (src/components/layout/Footer.tsx) +- 4 列网格布局 +- 联系信息 + 社交链接 +- 新闻订阅表单 + +#### ScrollToTop (src/components/layout/ScrollToTop.tsx) +- 滚动超过 200px 显示 +- 点击平滑滚动到顶部 + +### 首页组件 + +#### HeroSlider (src/components/home/HeroSlider.tsx) +- 使用 Swiper 实现 +- 支持自动播放、导航、分页 +- 淡入淡出效果 + +```tsx + +``` + +#### Stats (src/components/home/Stats.tsx) +- 数字计数动画 +- 使用 IntersectionObserver 触发 +- 进入视口后开始计数 + +### 内页组件 + +#### Breadcrumb (src/components/shared/Breadcrumb.tsx) +```tsx + +``` + +#### Team (src/components/about/Team.tsx) +- 团队成员卡片 +- Hover 显示社交链接 + +## 常见修改任务 + +### 修改网站名称/Logo +```tsx +// src/components/layout/Header.tsx + + Finance Ideas + +``` + +### 修改导航菜单 +```typescript +// src/data/siteData.ts +export const menuItems = [ + { name: 'Home', href: '/' }, + { name: 'About', href: '/about' }, + // 添加新菜单项... +] +``` + +### 修改主题色 +```css +/* src/index.css */ +@theme { + --color-primary: #新颜色; + --color-secondary: #新颜色; +} +``` + +### 修改轮播内容 +```typescript +// src/data/siteData.ts +export const sliderData = [ + { + id: 1, + title: '新标题', + buttonText: '按钮文字', + buttonLink: '/services', + bgClass: 'bg-slider-1', + }, +] +``` + +### 修改团队成员 +```typescript +// src/data/siteData.ts +export const teamData = [ + { + id: 1, + name: '姓名', + role: '职位', + image: '/src/assets/images/team1.jpg', + social: { facebook: '#', twitter: '#', linkedin: '#' }, + }, +] +``` + +### 添加新页面 +1. 在 `src/routes/` 创建新文件 `newpage.tsx` +2. 使用 `createFileRoute` 定义路由 +3. 在 `menuItems` 添加导航项 + +```tsx +// src/routes/newpage.tsx +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/newpage')({ + component: NewPage, +}) + +function NewPage() { + return
新页面内容
+} +``` + +## Vite 配置 + +```typescript +// vite.config.ts +export default defineConfig({ + plugins: [ + TanStackRouterVite({ target: 'react', autoCodeSplitting: true }), + react(), + tailwindcss(), + ], + server: { + port: 3000, + host: '0.0.0.0', + allowedHosts: true, + }, +}) +``` + +## 图片资源 + +| 文件 | 用途 | +|------|------| +| 1-6.jpg | 轮播/横幅背景 | +| g1-12.jpg | 内容图片 | +| c1-3.jpg | 客户头像 | +| team1-4.jpg | 团队成员 | + +## 安全说明 + +React 版本已锁定为 19.2.3,修复 CVE-2025-55182 漏洞。请勿使用 `^` 前缀升级。 + +## 构建部署 + +```bash +pnpm build # 构建生产版本 +pnpm preview # 预览生产版本 +``` + +构建产物在 `dist/` 目录。 diff --git a/index.html b/index.html new file mode 100644 index 0000000..46801d7 --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ + + + + + + Finance Ideas - 企业金融服务 + + + + + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..abd1d0e --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "turingflow-brand-001-react", + "version": "1.0.0", + "description": "企业品牌官网模板 - React + Vite + Tailwind CSS 4", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "19.2.3", + "react-dom": "19.2.3", + "@tanstack/react-router": "^1.114.0", + "swiper": "^11.2.0" + }, + "devDependencies": { + "@tanstack/router-plugin": "^1.114.0", + "@tailwindcss/vite": "^4.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.5.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.7.0", + "vite": "^7.0.0" + } +} diff --git a/src/assets/images/1.jpg b/src/assets/images/1.jpg new file mode 100644 index 0000000..348ee3f Binary files /dev/null and b/src/assets/images/1.jpg differ diff --git a/src/assets/images/2.jpg b/src/assets/images/2.jpg new file mode 100644 index 0000000..1fce13c Binary files /dev/null and b/src/assets/images/2.jpg differ diff --git a/src/assets/images/3.jpg b/src/assets/images/3.jpg new file mode 100644 index 0000000..57f17dd Binary files /dev/null and b/src/assets/images/3.jpg differ diff --git a/src/assets/images/4.jpg b/src/assets/images/4.jpg new file mode 100644 index 0000000..4ccdf06 Binary files /dev/null and b/src/assets/images/4.jpg differ diff --git a/src/assets/images/5.jpg b/src/assets/images/5.jpg new file mode 100644 index 0000000..cba31c6 Binary files /dev/null and b/src/assets/images/5.jpg differ diff --git a/src/assets/images/6.jpg b/src/assets/images/6.jpg new file mode 100644 index 0000000..1c5f7cd Binary files /dev/null and b/src/assets/images/6.jpg differ diff --git a/src/assets/images/c1.jpg b/src/assets/images/c1.jpg new file mode 100644 index 0000000..0a17bf6 Binary files /dev/null and b/src/assets/images/c1.jpg differ diff --git a/src/assets/images/c2.jpg b/src/assets/images/c2.jpg new file mode 100644 index 0000000..2de6808 Binary files /dev/null and b/src/assets/images/c2.jpg differ diff --git a/src/assets/images/c3.jpg b/src/assets/images/c3.jpg new file mode 100644 index 0000000..9af7025 Binary files /dev/null and b/src/assets/images/c3.jpg differ diff --git a/src/assets/images/g1.jpg b/src/assets/images/g1.jpg new file mode 100644 index 0000000..ce2376b Binary files /dev/null and b/src/assets/images/g1.jpg differ diff --git a/src/assets/images/g10.jpg b/src/assets/images/g10.jpg new file mode 100644 index 0000000..2a0103a Binary files /dev/null and b/src/assets/images/g10.jpg differ diff --git a/src/assets/images/g11.jpg b/src/assets/images/g11.jpg new file mode 100644 index 0000000..3eb52af Binary files /dev/null and b/src/assets/images/g11.jpg differ diff --git a/src/assets/images/g12.jpg b/src/assets/images/g12.jpg new file mode 100644 index 0000000..3006dc9 Binary files /dev/null and b/src/assets/images/g12.jpg differ diff --git a/src/assets/images/g2.jpg b/src/assets/images/g2.jpg new file mode 100644 index 0000000..11c8130 Binary files /dev/null and b/src/assets/images/g2.jpg differ diff --git a/src/assets/images/g3.jpg b/src/assets/images/g3.jpg new file mode 100644 index 0000000..accf084 Binary files /dev/null and b/src/assets/images/g3.jpg differ diff --git a/src/assets/images/g4.jpg b/src/assets/images/g4.jpg new file mode 100644 index 0000000..0639585 Binary files /dev/null and b/src/assets/images/g4.jpg differ diff --git a/src/assets/images/g5.jpg b/src/assets/images/g5.jpg new file mode 100644 index 0000000..86d8926 Binary files /dev/null and b/src/assets/images/g5.jpg differ diff --git a/src/assets/images/g6.jpg b/src/assets/images/g6.jpg new file mode 100644 index 0000000..6a9f361 Binary files /dev/null and b/src/assets/images/g6.jpg differ diff --git a/src/assets/images/g7.jpg b/src/assets/images/g7.jpg new file mode 100644 index 0000000..f88128e Binary files /dev/null and b/src/assets/images/g7.jpg differ diff --git a/src/assets/images/g8.jpg b/src/assets/images/g8.jpg new file mode 100644 index 0000000..9c01fe9 Binary files /dev/null and b/src/assets/images/g8.jpg differ diff --git a/src/assets/images/g9.jpg b/src/assets/images/g9.jpg new file mode 100644 index 0000000..c7fab5f Binary files /dev/null and b/src/assets/images/g9.jpg differ diff --git a/src/assets/images/team1.jpg b/src/assets/images/team1.jpg new file mode 100644 index 0000000..173f0a0 Binary files /dev/null and b/src/assets/images/team1.jpg differ diff --git a/src/assets/images/team2.jpg b/src/assets/images/team2.jpg new file mode 100644 index 0000000..e736118 Binary files /dev/null and b/src/assets/images/team2.jpg differ diff --git a/src/assets/images/team3.jpg b/src/assets/images/team3.jpg new file mode 100644 index 0000000..982f7be Binary files /dev/null and b/src/assets/images/team3.jpg differ diff --git a/src/assets/images/team4.jpg b/src/assets/images/team4.jpg new file mode 100644 index 0000000..ee24be6 Binary files /dev/null and b/src/assets/images/team4.jpg differ diff --git a/src/components/about/Mission.tsx b/src/components/about/Mission.tsx new file mode 100644 index 0000000..b766de6 --- /dev/null +++ b/src/components/about/Mission.tsx @@ -0,0 +1,42 @@ +import { missionVisionData, commonDescriptions } from '../../data/siteData' + +export default function Mission() { + return ( +
+
+
+ {/* Left - Feature Cards */} +
+ {missionVisionData.map((item) => ( +
+ {item.title} +
+
+ {item.title} +
+

+ {item.description} +

+
+
+ ))} +
+ + {/* Right Content */} +
+
+ {commonDescriptions.aboutQuote} +
+

+ {commonDescriptions.aboutDesc} +

+
+
+
+
+ ) +} diff --git a/src/components/about/Statistics.tsx b/src/components/about/Statistics.tsx new file mode 100644 index 0000000..63397c1 --- /dev/null +++ b/src/components/about/Statistics.tsx @@ -0,0 +1,70 @@ +import { useState, useEffect, useRef } from 'react' +import { aboutStatsData } from '../../data/siteData' + +export default function Statistics() { + const [isVisible, setIsVisible] = useState(false) + const sectionRef = useRef(null) + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setIsVisible(true) + } + }, + { threshold: 0.3 } + ) + + if (sectionRef.current) { + observer.observe(sectionRef.current) + } + + return () => observer.disconnect() + }, []) + + return ( +
+
+
+ {aboutStatsData.map((stat) => ( +
+ +

+ +

+

{stat.label}

+
+ ))} +
+
+
+ ) +} + +function CountUp({ end, isVisible }: { end: number; isVisible: boolean }) { + const [count, setCount] = useState(0) + + useEffect(() => { + if (!isVisible) return + + let startTime: number + const duration = 2000 + + const step = (timestamp: number) => { + if (!startTime) startTime = timestamp + const progress = Math.min((timestamp - startTime) / duration, 1) + setCount(Math.floor(progress * end)) + if (progress < 1) { + requestAnimationFrame(step) + } + } + requestAnimationFrame(step) + }, [end, isVisible]) + + return <>{count.toLocaleString()} +} diff --git a/src/components/about/Team.tsx b/src/components/about/Team.tsx new file mode 100644 index 0000000..4bcb4be --- /dev/null +++ b/src/components/about/Team.tsx @@ -0,0 +1,63 @@ +import { teamData, commonDescriptions } from '../../data/siteData' + +export default function Team() { + return ( +
+
+ {/* Section Header */} +
+

+ {commonDescriptions.teamTitle} +

+

+ {commonDescriptions.sectionDesc} +

+
+ + {/* Team Grid */} +
+ {teamData.map((member) => ( +
+
+ + {member.name} + +
+
+

+ + {member.name} + +

+

{member.role}

+ +
+
+ ))} +
+
+
+ ) +} diff --git a/src/components/about/WhyChooseUs.tsx b/src/components/about/WhyChooseUs.tsx new file mode 100644 index 0000000..7478350 --- /dev/null +++ b/src/components/about/WhyChooseUs.tsx @@ -0,0 +1,39 @@ +import { whyChooseUsData, commonDescriptions } from '../../data/siteData' + +export default function WhyChooseUs() { + return ( +
+
+
+ {/* Left - Image */} +
+ Why Choose Us +
+ + {/* Right Content */} +
+

+ {commonDescriptions.whyChooseUsTitle} +

+

+ {commonDescriptions.whyChooseUsDesc} +

+ +
    + {whyChooseUsData.map((item, index) => ( +
  • + + {item} +
  • + ))} +
+
+
+
+
+ ) +} diff --git a/src/components/contact/ContactForm.tsx b/src/components/contact/ContactForm.tsx new file mode 100644 index 0000000..127542d --- /dev/null +++ b/src/components/contact/ContactForm.tsx @@ -0,0 +1,120 @@ +import { useState } from 'react' +import { contactFormInfo } from '../../data/siteData' + +export default function ContactForm() { + const [formData, setFormData] = useState({ + name: '', + email: '', + subject: '', + message: '', + }) + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + console.log('Form submitted:', formData) + alert('Message sent successfully!') + setFormData({ name: '', email: '', subject: '', message: '' }) + } + + const handleChange = (e: React.ChangeEvent) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value, + }) + } + + return ( +
+
+
+ {/* Contact Info */} +
+

+ {contactFormInfo.title} +

+
+ {contactFormInfo.subtitle} +
+
+ +
+
Address:
+

{contactFormInfo.address}

+
+ +
+
+ + {/* Contact Form */} +
+
+
+ + + +
+
+