commit c5e710b94b9864ac1c47bceb96691f8d30004c5a Author: “dongming” <“lidongming@aituringflow.com”> Date: Fri Dec 19 13:01:41 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..28b7580 --- /dev/null +++ b/.gitignore @@ -0,0 +1,157 @@ +# 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/ +.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..e8e51c2 --- /dev/null +++ b/README.md @@ -0,0 +1,406 @@ +# si-educational + +在线教育平台着陆页模板,适用于教育机构、在线课程平台、培训中心展示课程和服务。 + +## 技术栈 + +- Next.js 15.5.9 (已锁定版本,App Router) +- React 19.2.3 (已锁定版本,修复 CVE-2025-55182 漏洞) +- TypeScript 5 +- Tailwind CSS 4 +- next-auth (身份认证) +- next-themes (深色/浅色主题切换) +- Framer Motion (高级动画) +- AOS (滚动动画) +- react-slick (轮播组件) +- @headlessui/react (无头 UI 组件) +- @iconify/react (图标库) + +## 快速开始 + +```bash +npm install +npm run dev +``` + +## 目录结构 + +```text +src/ +├── app/ # Next.js App Router 入口 +│ ├── layout.tsx # 根布局 (Header + Footer + 主题Provider) +│ ├── page.tsx # 首页 (Hero + 课程 + 导师 + 评价) +│ ├── not-found.tsx # 404 页面 +│ ├── globals.css # 全局样式 + Tailwind 主题变量 +│ ├── (site)/ # 路由分组 (共享布局) +│ │ ├── (auth)/ # 认证路由组 +│ │ │ ├── signin/page.tsx # 登录页面 +│ │ │ └── signup/page.tsx # 注册页面 +│ │ └── documentation/page.tsx # 文档页面 +│ ├── api/ # API 路由 +│ │ ├── auth/[...nextauth]/route.ts # NextAuth 认证 API +│ │ └── data/route.ts # 静态数据 API (菜单、课程、导师) +│ ├── components/ # 可复用组件 +│ │ ├── Layout/ # 布局组件 +│ │ │ ├── Header/index.tsx # 导航栏 (Logo + 菜单 + 主题切换) +│ │ │ │ ├── Logo/index.tsx # Logo 组件 +│ │ │ │ ├── ThemeToggler.tsx # 主题切换按钮 +│ │ │ │ └── Navigation/ # 导航链接 +│ │ │ │ ├── HeaderLink.tsx # 桌面导航 +│ │ │ │ └── MobileHeaderLink.tsx # 移动导航 +│ │ │ └── Footer/index.tsx # 页脚 +│ │ ├── Home/ # 首页专用组件 +│ │ │ ├── Hero/index.tsx # Hero 区域 (主标题 + 下拉选择器) +│ │ │ │ ├── Dropdownone.tsx # 课程类型选择 +│ │ │ │ └── Dropdowntwo.tsx # 学习时长选择 +│ │ │ ├── Companies/index.tsx # 公司 Logo 轮播 +│ │ │ ├── Courses/index.tsx # 课程列表 (分类过滤) +│ │ │ ├── Mentor/index.tsx # 导师卡片网格 +│ │ │ ├── Testimonial/index.tsx # 学生评价轮播 +│ │ │ └── Newsletter/index.tsx # 邮件订阅区 +│ │ ├── Auth/ # 认证相关组件 +│ │ │ ├── SignIn/index.tsx # 登录表单 +│ │ │ ├── SignUp/index.tsx # 注册表单 +│ │ │ ├── ForgotPassword/index.tsx # 忘记密码 +│ │ │ ├── MagicLink/index.tsx # 魔法链接登录 +│ │ │ ├── ResetPassword/index.tsx # 重置密码 +│ │ │ ├── SocialSignIn.tsx # 社交登录按钮 +│ │ │ └── SocialSignUp.tsx # 社交注册按钮 +│ │ ├── Contact/ # 联系表单 +│ │ │ └── Form/index.tsx +│ │ ├── Documentation/ # 文档相关组件 +│ │ │ ├── Documentation.tsx # 主文档组件 +│ │ │ ├── Introduction.tsx # 介绍 +│ │ │ ├── QuickStart.tsx # 快速开始 +│ │ │ ├── Configuration.tsx # 配置说明 +│ │ │ ├── ColorConfiguraion.tsx # 颜色配置 +│ │ │ ├── TypographyConfiguration.tsx # 字体配置 +│ │ │ ├── LogoConfiguration.tsx # Logo 配置 +│ │ │ ├── PackageStructure.tsx # 包结构说明 +│ │ │ └── DocNavigation.tsx # 文档侧边导航 +│ │ ├── Common/ # 通用组件 +│ │ │ ├── Breadcrumb.tsx # 面包屑 +│ │ │ ├── Loader.tsx # 加载动画 +│ │ │ ├── PreLoader.tsx # 页面预加载 +│ │ │ └── ScrollUp.tsx # 回到顶部按钮 +│ │ ├── Skeleton/ # 骨架屏组件 +│ │ │ ├── CourseDetail/index.tsx # 课程加载骨架 +│ │ │ ├── Mentor/index.tsx # 导师加载骨架 +│ │ │ └── Testimonial/index.tsx # 评价加载骨架 +│ │ ├── SharedComponent/ # 共享组件 +│ │ │ ├── HeroSub/index.tsx # 子页面 Hero +│ │ │ └── Volunteer/index.tsx # 志愿者组件 +│ │ ├── Breadcrumb/index.tsx # 面包屑导航 +│ │ ├── NotFound/index.tsx # 404 组件 +│ │ └── ScrollToTop/index.tsx # 滚动到顶部 +│ └── types/ # TypeScript 类型定义 +│ ├── blog.ts # 博客类型 +│ ├── breadcrumb.ts # 面包屑类型 +│ ├── course.ts # 课程分类类型 +│ ├── coursedetail.ts # 课程详情类型 +│ ├── footerlinks.ts # 页脚链接类型 +│ ├── hour.ts # 学习时长类型 +│ ├── menu.ts # 菜单类型 +│ ├── mentor.ts # 导师类型 +│ └── testimonial.ts # 评价类型 +├── utils/ # 工具函数 +│ ├── aos.tsx # AOS 动画初始化 +│ └── validateEmail.ts # 邮箱验证 + +public/ # 静态资源 +└── images/ + ├── logo/ # Logo 图片 + ├── banner/ # Hero 区域背景 + ├── courses/ # 课程卡片图片 + ├── mentor/ # 导师头像 + ├── testimonial/ # 评价用户头像 + ├── slickCompany/ # 公司 Logo 轮播 + ├── newsletter/ # 新闻订阅图片 + ├── documentation/ # 文档图片 + └── 404-*.svg # 404 页面图片 +``` + +## 路由配置 + +| 路径 | 页面 | 说明 | +|------|------|------| +| `/` | Home | 首页 (Hero + 公司轮播 + 课程 + 导师 + 评价 + 订阅) | +| `/signin` | SignIn | 登录页面 | +| `/signup` | SignUp | 注册页面 | +| `/documentation` | Documentation | 模板文档 | + +## API 路由 + +| 端点 | 方法 | 说明 | +|------|------|------| +| `/api/data` | GET | 获取所有前端数据 (导航、课程、导师、评价) | +| `/api/auth/*` | - | NextAuth 认证端点 | +| `/api/register` | POST | 用户注册 | + +## 数据结构 + +### 导航菜单数据 (src/app/api/data/route.ts) + +```typescript +const HeaderData = [ + { + label: 'Home', + href: '/', + }, + { + label: 'Pages', + href: '#', + submenu: [ + { label: 'Sign In', href: '/signin' }, + { label: 'Sign Up', href: '/signup' }, + ] + }, + // ... +]; +``` + +### 课程数据 (src/app/api/data/route.ts) + +```typescript +const CourseDetailData = [ + { + course: 'HTML, CSS & Javascript Course for Web Developers', + imageSrc: '/images/courses/courses1.svg', + profession: 'Web Development', + price: '$70.00', + category: 'webdevelopment' // 用于分类过滤 + }, + // ... +]; + +// 课程分类 +const CourseData = [ + { name: 'Web Development' }, + { name: 'Mobile Development' }, + { name: 'Data Science' }, + { name: 'Cloud Computing' }, +]; +``` + +### 导师数据 (src/app/api/data/route.ts) + +```typescript +const MentorData = [ + { + name: 'Brooklyn Simmons', + href: '#', + imageSrc: '/images/mentor/mentor1.svg', + imageAlt: 'Brooklyn Simmons', + color: 'bg-success' // 颜色标签 + }, + // ... +]; +``` + +### 评价数据 (src/app/api/data/route.ts) + +```typescript +const TestimonialData = [ + { + profession: 'UI/UX Designer', + name: 'Robert Fox', + imgSrc: '/images/testimonial/testimonial1.svg', + starimg: '/images/testimonial/stars.svg', + detail: '评价内容...' + }, + // ... +]; +``` + +### 类型定义 + +```typescript +// src/app/types/coursedetail.ts +type CourseDetailType = { + course: string; + imageSrc: string; + profession: string; + price: string; + category: 'mobiledevelopment' | 'webdevelopment' | 'datascience' | 'cloudcomputing'; +}; + +// src/app/types/mentor.ts +type MentorType = { + name: string; + href: string; + imageSrc: string; + imageAlt: string; + color: string; +}; + +// src/app/types/testimonial.ts +type TestimonialType = { + profession: string; + name: string; + imgSrc: string; + starimg: string; + detail: string; +}; + +// src/app/types/menu.ts +type HeaderItem = { + label: string; + href: string; + submenu?: SubmenuItem[]; +}; +``` + +## 核心组件说明 + +### 布局系统 + +- `RootLayout` (src/app/layout.tsx): 根布局,包含主题Provider、认证Provider、Header和Footer +- 使用 `next-themes` 实现深色/浅色模式切换 +- 使用 `next-auth` 实现用户认证 + +### 主题切换 + +主题通过 `next-themes` 的 `ThemeProvider` 管理,支持: + +- `light` - 浅色主题 +- `dark` - 深色主题 +- `system` - 跟随系统 + +### 主题颜色 (src/app/globals.css) + +```css +--color-primary: #611f69; /* 紫色 - 主色 */ +--color-cream: #fcf5ef; /* 奶油色 - 背景 */ +--color-success: #6b9f36; /* 绿色 - 成功状态 */ +--color-orange: #f9cd92; /* 橙色 - 强调 */ +``` + +### 动画效果 + +- **AOS (Animate On Scroll)**: 在 `src/utils/aos.tsx` 初始化,页面滚动时的进入动画 +- **Framer Motion**: 高级动画库,支持复杂过渡效果 +- **react-slick**: 轮播组件动画 + +### 认证系统 + +使用 `next-auth` v4,配置文件位于 `src/app/api/auth/[...nextauth]/route.ts`。 + +启用社交登录需要: + +1. 在 `.env.local` 中配置环境变量: + +```bash +NEXTAUTH_SECRET=your-secret-key +NEXTAUTH_URL=http://localhost:3000 + +# Google 登录 +GOOGLE_CLIENT_ID=your-google-client-id +GOOGLE_CLIENT_SECRET=your-google-client-secret + +# GitHub 登录 +GITHUB_ID=your-github-id +GITHUB_SECRET=your-github-secret +``` + +2. 在 `route.ts` 中取消对应 provider 的注释 + +### 数据获取模式 + +所有页面数据从 `/api/data` 集中获取: + +```typescript +// 组件中的数据获取示例 +useEffect(() => { + const fetchData = async () => { + const res = await fetch('/api/data'); + const data = await res.json(); + setCourses(data.CourseDetailData); + }; + fetchData(); +}, []); +``` + +## 常见修改任务 + +### 修改网站信息 + +1. 修改 `src/app/components/Layout/Header/Logo/index.tsx` 中的 Logo +2. 修改 `src/app/components/Layout/Footer/index.tsx` 中的版权信息 +3. 修改 `src/app/layout.tsx` 中的 metadata + +### 修改导航菜单 + +编辑 `src/app/api/data/route.ts` 中的 `HeaderData` 数组 + +### 添加新页面 + +1. 在 `src/app/(site)/` 目录下创建新文件夹和 `page.tsx` +2. 在 `HeaderData` 中添加导航项 + +### 修改样式主题 + +1. 编辑 `src/app/globals.css` 中的 CSS 变量 +2. 主要颜色: `--color-primary`, `--color-cream`, `--color-success`, `--color-orange` + +### 修改课程/导师/评价数据 + +编辑 `src/app/api/data/route.ts` 中对应的数据数组: +- `CourseDetailData` - 课程列表 +- `MentorData` - 导师列表 +- `TestimonialData` - 评价列表 +- `CourseData` - 课程分类 + +### 添加新的课程分类 + +1. 在 `CourseData` 添加新分类 +2. 在 `CourseDetailType` 类型中添加新的 category 值 +3. 在课程数据中使用新分类 + +## 组件使用示例 + +### 课程列表组件 (Courses) + +```tsx +// 支持分类过滤 +const categories = ['webdevelopment', 'mobiledevelopment', 'datascience', 'cloudcomputing']; + +// 过滤逻辑 +const filteredCourses = courses.filter( + course => selectedCategory === 'all' || course.category === selectedCategory +); +``` + +### 轮播组件 (Companies/Testimonial) + +```tsx +// react-slick 配置 +const settings = { + dots: false, + infinite: true, + slidesToShow: 4, + slidesToScroll: 1, + autoplay: true, + autoplaySpeed: 2000, + responsive: [ + { breakpoint: 1024, settings: { slidesToShow: 3 } }, + { breakpoint: 768, settings: { slidesToShow: 2 } }, + ] +}; +``` + +## 图片资源 + +- 存放在 `public/images/` 目录 +- 引用路径:`/images/xxx.svg` +- 课程图片:`/images/courses/` +- 导师头像:`/images/mentor/` +- 评价头像:`/images/testimonial/` + +## 安全说明 + +以下依赖版本已锁定以修复安全漏洞: + +- **Next.js 15.5.9** - 锁定版本,修复漏洞编号为:CVE-2025-66478 +- **React 19.2.3** - 修复 React Server Components 远程代码执行漏洞 (CVE-2025-55182) + +请勿使用 `^` 或 `~` 前缀以避免自动升级到有漏洞的版本。 diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..c85fb67 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,16 @@ +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript"), +]; + +export default eslintConfig; diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..2e22efd --- /dev/null +++ b/next.config.ts @@ -0,0 +1,14 @@ +import type { NextConfig } from 'next' + +const nextConfig: NextConfig = { + eslint: { + ignoreDuringBuilds: true, + }, + images: { + unoptimized: true, + }, + // 允许所有来源访问开发服务器 + allowedDevOrigins: ['*'], +} + +export default nextConfig diff --git a/package.json b/package.json new file mode 100644 index 0000000..12ae276 --- /dev/null +++ b/package.json @@ -0,0 +1,44 @@ +{ + "name": "sieducational_project", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev --turbopack -H 0.0.0.0 -p 3000", + "build": "next build", + "start": "next start -H 0.0.0.0 -p 3000", + "lint": "next lint" + }, + "dependencies": { + "@headlessui/react": "^2.2.0", + "@iconify/icons-ion": "^1.2.10", + "@iconify/react": "^5.2.1", + "aos": "^2.3.4", + "axios": "^1.8.4", + "date-fns": "^4.1.0", + "framer-motion": "^12.6.3", + "gray-matter": "^4.0.3", + "next": "15.5.9", + "next-auth": "^4.24.11", + "next-themes": "^0.4.6", + "react": "19.2.3", + "react-dom": "19.2.3", + "react-hot-toast": "^2.5.2", + "react-slick": "^0.30.3", + "remark": "^15.0.1", + "remark-html": "^16.0.1", + "slick-carousel": "^1.8.1" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@tailwindcss/postcss": "^4", + "@types/aos": "^3.0.7", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "@types/react-slick": "^0.23.13", + "eslint": "^9", + "eslint-config-next": "15.5.9", + "tailwindcss": "^4", + "typescript": "^5" + } +} diff --git a/postcss.config.mjs b/postcss.config.mjs new file mode 100644 index 0000000..c7bcb4b --- /dev/null +++ b/postcss.config.mjs @@ -0,0 +1,5 @@ +const config = { + plugins: ["@tailwindcss/postcss"], +}; + +export default config; diff --git a/public/images/404-dark.svg b/public/images/404-dark.svg new file mode 100644 index 0000000..7982a4f --- /dev/null +++ b/public/images/404-dark.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/images/404.svg b/public/images/404.svg new file mode 100644 index 0000000..0897b07 --- /dev/null +++ b/public/images/404.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/banner/Stars.svg b/public/images/banner/Stars.svg new file mode 100644 index 0000000..a5b4107 --- /dev/null +++ b/public/images/banner/Stars.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/images/banner/background.png b/public/images/banner/background.png new file mode 100644 index 0000000..6b0f866 Binary files /dev/null and b/public/images/banner/background.png differ diff --git a/public/images/closed.svg b/public/images/closed.svg new file mode 100644 index 0000000..28aacb0 --- /dev/null +++ b/public/images/closed.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/public/images/courses/Star.svg b/public/images/courses/Star.svg new file mode 100644 index 0000000..a7c884b --- /dev/null +++ b/public/images/courses/Star.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/courses/account.svg b/public/images/courses/account.svg new file mode 100644 index 0000000..b73be11 --- /dev/null +++ b/public/images/courses/account.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/images/courses/coursesFour.svg b/public/images/courses/coursesFour.svg new file mode 100644 index 0000000..5032cb1 --- /dev/null +++ b/public/images/courses/coursesFour.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/courses/coursesOne.svg b/public/images/courses/coursesOne.svg new file mode 100644 index 0000000..9fb8df8 --- /dev/null +++ b/public/images/courses/coursesOne.svg @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/courses/coursesThree.svg b/public/images/courses/coursesThree.svg new file mode 100644 index 0000000..4931b8d --- /dev/null +++ b/public/images/courses/coursesThree.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/courses/coursesTwo.svg b/public/images/courses/coursesTwo.svg new file mode 100644 index 0000000..92da87c --- /dev/null +++ b/public/images/courses/coursesTwo.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/documentation/Categories=Nextjs.svg b/public/images/documentation/Categories=Nextjs.svg new file mode 100644 index 0000000..f4663d0 --- /dev/null +++ b/public/images/documentation/Categories=Nextjs.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/documentation/Categories=React.svg b/public/images/documentation/Categories=React.svg new file mode 100644 index 0000000..aa9430c --- /dev/null +++ b/public/images/documentation/Categories=React.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/images/documentation/Categories=Tailwind.svg b/public/images/documentation/Categories=Tailwind.svg new file mode 100644 index 0000000..6e3334f --- /dev/null +++ b/public/images/documentation/Categories=Tailwind.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/documentation/Categories=Typescript.svg b/public/images/documentation/Categories=Typescript.svg new file mode 100644 index 0000000..3b31b02 --- /dev/null +++ b/public/images/documentation/Categories=Typescript.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/images/documentation/axios.svg b/public/images/documentation/axios.svg new file mode 100644 index 0000000..67369ea --- /dev/null +++ b/public/images/documentation/axios.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/public/images/documentation/nextauth.png b/public/images/documentation/nextauth.png new file mode 100644 index 0000000..67c21ae Binary files /dev/null and b/public/images/documentation/nextauth.png differ diff --git a/public/images/logo/logo.svg b/public/images/logo/logo.svg new file mode 100644 index 0000000..ee86ebb --- /dev/null +++ b/public/images/logo/logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/images/logo/logo2.svg b/public/images/logo/logo2.svg new file mode 100644 index 0000000..c0ad17e --- /dev/null +++ b/public/images/logo/logo2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/images/mentor/boy1.svg b/public/images/mentor/boy1.svg new file mode 100644 index 0000000..617d913 --- /dev/null +++ b/public/images/mentor/boy1.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/images/mentor/boy2.svg b/public/images/mentor/boy2.svg new file mode 100644 index 0000000..f566244 --- /dev/null +++ b/public/images/mentor/boy2.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/images/mentor/boy3.svg b/public/images/mentor/boy3.svg new file mode 100644 index 0000000..384c6f9 --- /dev/null +++ b/public/images/mentor/boy3.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/images/mentor/boy4.svg b/public/images/mentor/boy4.svg new file mode 100644 index 0000000..e75fef3 --- /dev/null +++ b/public/images/mentor/boy4.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/images/mentor/boy5.svg b/public/images/mentor/boy5.svg new file mode 100644 index 0000000..14d8166 --- /dev/null +++ b/public/images/mentor/boy5.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/images/mentor/girl1.svg b/public/images/mentor/girl1.svg new file mode 100644 index 0000000..65984b0 --- /dev/null +++ b/public/images/mentor/girl1.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/images/newsletter/Free.svg b/public/images/newsletter/Free.svg new file mode 100644 index 0000000..a3a3df8 --- /dev/null +++ b/public/images/newsletter/Free.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/images/newsletter/hands.svg b/public/images/newsletter/hands.svg new file mode 100644 index 0000000..5b0dc88 --- /dev/null +++ b/public/images/newsletter/hands.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/images/newsletter/pinkBackground.svg b/public/images/newsletter/pinkBackground.svg new file mode 100644 index 0000000..0f96063 --- /dev/null +++ b/public/images/newsletter/pinkBackground.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/slickCompany/airbnb.svg b/public/images/slickCompany/airbnb.svg new file mode 100644 index 0000000..c84af19 --- /dev/null +++ b/public/images/slickCompany/airbnb.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/slickCompany/fedex.svg b/public/images/slickCompany/fedex.svg new file mode 100644 index 0000000..9eaecfc --- /dev/null +++ b/public/images/slickCompany/fedex.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/images/slickCompany/google.svg b/public/images/slickCompany/google.svg new file mode 100644 index 0000000..5e38a64 --- /dev/null +++ b/public/images/slickCompany/google.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/public/images/slickCompany/hubspot.svg b/public/images/slickCompany/hubspot.svg new file mode 100644 index 0000000..f04ef2a --- /dev/null +++ b/public/images/slickCompany/hubspot.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/images/slickCompany/microsoft.svg b/public/images/slickCompany/microsoft.svg new file mode 100644 index 0000000..38ce48e --- /dev/null +++ b/public/images/slickCompany/microsoft.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/public/images/slickCompany/walmart.svg b/public/images/slickCompany/walmart.svg new file mode 100644 index 0000000..d9a56ad --- /dev/null +++ b/public/images/slickCompany/walmart.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/images/testimonial/austin.svg b/public/images/testimonial/austin.svg new file mode 100644 index 0000000..1291702 --- /dev/null +++ b/public/images/testimonial/austin.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/testimonial/greenpic.svg b/public/images/testimonial/greenpic.svg new file mode 100644 index 0000000..b0457a1 --- /dev/null +++ b/public/images/testimonial/greenpic.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/images/testimonial/smallAvatar.svg b/public/images/testimonial/smallAvatar.svg new file mode 100644 index 0000000..b0457a1 --- /dev/null +++ b/public/images/testimonial/smallAvatar.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/images/testimonial/stars.png b/public/images/testimonial/stars.png new file mode 100644 index 0000000..80d7161 Binary files /dev/null and b/public/images/testimonial/stars.png differ diff --git a/public/images/testimonial/user-1.jpg b/public/images/testimonial/user-1.jpg new file mode 100644 index 0000000..df8e3fb Binary files /dev/null and b/public/images/testimonial/user-1.jpg differ diff --git a/public/images/testimonial/user-2.jpg b/public/images/testimonial/user-2.jpg new file mode 100644 index 0000000..9de110f Binary files /dev/null and b/public/images/testimonial/user-2.jpg differ diff --git a/public/images/testimonial/user-3.jpg b/public/images/testimonial/user-3.jpg new file mode 100644 index 0000000..7d4531f Binary files /dev/null and b/public/images/testimonial/user-3.jpg differ diff --git a/src/app/(site)/(auth)/signin/page.tsx b/src/app/(site)/(auth)/signin/page.tsx new file mode 100644 index 0000000..5497adc --- /dev/null +++ b/src/app/(site)/(auth)/signin/page.tsx @@ -0,0 +1,20 @@ +import Signin from "@/app/components/Auth/SignIn"; +import Breadcrumb from "@/app/components/Common/Breadcrumb"; +import { Metadata } from "next"; + +export const metadata: Metadata = { + title: + "Sign In | Property", +}; + +const SigninPage = () => { + return ( + <> + + + + + ); +}; + +export default SigninPage; diff --git a/src/app/(site)/(auth)/signup/page.tsx b/src/app/(site)/(auth)/signup/page.tsx new file mode 100644 index 0000000..695a1d3 --- /dev/null +++ b/src/app/(site)/(auth)/signup/page.tsx @@ -0,0 +1,20 @@ +import SignUp from "@/app/components/Auth/SignUp"; +import Breadcrumb from "@/app/components/Common/Breadcrumb"; +import { Metadata } from "next"; + +export const metadata: Metadata = { + title: + "Sign Up | Property", +}; + +const SignupPage = () => { + return ( + <> + + + + + ); +}; + +export default SignupPage; diff --git a/src/app/(site)/documentation/page.tsx b/src/app/(site)/documentation/page.tsx new file mode 100644 index 0000000..490688e --- /dev/null +++ b/src/app/(site)/documentation/page.tsx @@ -0,0 +1,13 @@ +import { Documentation } from '@/app/components/Documentation/Documentation' +import { Metadata } from 'next' +export const metadata: Metadata = { + title: 'Featurs | SiEducational', +} + +export default function Page() { + return ( + <> + + + ) +} diff --git a/src/app/api/auth/[...nextauth]/route.js b/src/app/api/auth/[...nextauth]/route.js new file mode 100644 index 0000000..f0c8884 --- /dev/null +++ b/src/app/api/auth/[...nextauth]/route.js @@ -0,0 +1,36 @@ +import NextAuth from 'next-auth'; +import CredentialsProvider from 'next-auth/providers/credentials'; +import GoogleProvider from 'next-auth/providers/google'; +import GitHubProvider from 'next-auth/providers/github'; + +const handler = NextAuth({ + site: process.env.NEXTAUTH_URL || 'http://localhost:3000', + providers: [ + GoogleProvider({ + clientId: process.env.GOOGLE_CLIENT_ID, + clientSecret: process.env.GOOGLE_CLIENT_SECRET, + }), + GitHubProvider({ + clientId: process.env.GITHUB_ID, + clientSecret: process.env.GITHUB_SECRET, + }), + CredentialsProvider({ + name: 'credentials', + credentials: { + username: { label: 'Username', type: 'text' }, + password: { label: 'Password', type: 'password' }, + }, + authorize: async (credentials) => { + // Add your own authentication logic here + if (credentials.username === 'admin' && credentials.password === 'admin123') { + // Return user object if credentials are valid + return Promise.resolve({ id: 1, name: 'Admin', email: 'admin@example.com' }); + } else { + // Return null if credentials are invalid + return Promise.resolve(null); + } + }, + }), + ], +}); +export { handler as GET, handler as POST }; diff --git a/src/app/api/auth/[...nextauth]/users.json b/src/app/api/auth/[...nextauth]/users.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/src/app/api/auth/[...nextauth]/users.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/app/api/data/route.ts b/src/app/api/data/route.ts new file mode 100644 index 0000000..f61e312 --- /dev/null +++ b/src/app/api/data/route.ts @@ -0,0 +1,286 @@ +import { NextResponse } from 'next/server' + +import { HeaderItem } from '@/app/types/menu' +import { CourseType } from '@/app/types/course' +import { Hourtype } from '@/app/types/hour' +import { CourseDetailType } from '@/app/types/coursedetail' +import { MentorType } from '@/app/types/mentor' +import { TestimonialType } from '@/app/types/testimonial' +import { FooterLinkType } from '@/app/types/footerlinks' + +const HeaderData: HeaderItem[] = [ + { label: 'Home', href: '/#Home' }, + { label: 'Courses', href: '/#Courses' }, + { label: 'Mentors', href: '/#mentors-section' }, + { label: 'Testimonial', href: '/#testimonial-section' }, + { label: 'Join', href: '/#join-section' }, + { label: 'Contact Us', href: '/#contact' }, + { label: 'Docs', href: '/documentation' }, +] + +const CourseData: CourseType[] = [ + { name: 'Mobile Development' }, + { name: 'Web Development' }, + { name: 'Data Science' }, + { name: 'Cloud Computing' }, +] + +const HourData: Hourtype[] = [ + { name: '20hrs in a Month' }, + { name: '30hrs in a Month' }, + { name: '40hrs in a Month' }, + { name: '50hrs in a Month' }, +] + +const Companiesdata: { imgSrc: string }[] = [ + { + imgSrc: '/images/slickCompany/airbnb.svg', + }, + { + imgSrc: '/images/slickCompany/hubspot.svg', + }, + { + imgSrc: '/images/slickCompany/microsoft.svg', + }, + { + imgSrc: '/images/slickCompany/google.svg', + }, + { + imgSrc: '/images/slickCompany/walmart.svg', + }, + { + imgSrc: '/images/slickCompany/fedex.svg', + }, +] + +const CourseDetailData: CourseDetailType[] = [ + { + course: 'HTML, CSS, JS', + imageSrc: '/images/courses/coursesOne.svg', + profession: 'HTML, CSS, Javascript Development', + price: '40', + category: 'webdevelopment', + }, + { + course: 'Node.js', + imageSrc: '/images/courses/coursesTwo.svg', + profession: 'Backend with Node.js and Express.js', + price: '21', + category: 'webdevelopment', + }, + { + course: 'Database', + imageSrc: '/images/courses/coursesThree.svg', + profession: 'Learn Mongodb with Mongoose', + price: '21', + category: 'webdevelopment', + }, + { + course: 'React.js', + imageSrc: '/images/courses/coursesFour.svg', + profession: 'Learn React with Redux toolkit', + price: '99', + category: 'webdevelopment', + }, + { + course: 'React Native', + imageSrc: '/images/courses/coursesOne.svg', + profession: 'Learn React Native with Node.js', + price: '89', + category: 'mobiledevelopment', + }, + { + course: 'Swift', + imageSrc: '/images/courses/coursesThree.svg', + profession: 'Learn Swift from Scratch', + price: '89', + category: 'mobiledevelopment', + }, + { + course: 'Flutter', + imageSrc: '/images/courses/coursesFour.svg', + profession: 'Flutter App Development', + price: '69', + category: 'mobiledevelopment', + }, + { + course: 'Onsen UI', + imageSrc: '/images/courses/coursesTwo.svg', + profession: 'Learn Onsen Ui with HTML, CSS', + price: '69', + category: 'mobiledevelopment', + }, + { + course: 'TensorFlow', + imageSrc: '/images/courses/coursesTwo.svg', + profession: 'Learn TensorFlow with SQL', + price: '99', + category: 'datascience', + }, + { + course: 'AWS', + imageSrc: '/images/courses/coursesFour.svg', + profession: 'AWS Deep Learning AMI', + price: '99', + category: 'datascience', + }, + { + course: 'Bokeh', + imageSrc: '/images/courses/coursesOne.svg', + profession: 'Learn Bokeh with Python', + price: '99', + category: 'datascience', + }, + { + course: 'Scikit', + imageSrc: '/images/courses/coursesThree.svg', + profession: 'Scikit with Python Development', + price: '89', + category: 'datascience', + }, + { + course: 'Laas', + imageSrc: '/images/courses/coursesThree.svg', + profession: 'Infra-as-a-Service', + price: '21', + category: 'cloudcomputing', + }, + { + course: 'Iaas', + imageSrc: '/images/courses/coursesFour.svg', + profession: 'Info-as-a-Service', + price: '29', + category: 'cloudcomputing', + }, + { + course: 'Paas', + imageSrc: '/images/courses/coursesOne.svg', + profession: 'Platform-as-a-Service', + price: '99', + category: 'cloudcomputing', + }, + { + course: 'Saas', + imageSrc: '/images/courses/coursesTwo.svg', + profession: 'Software-as-a-Service', + price: '58', + category: 'cloudcomputing', + }, +] + +const MentorData: MentorType[] = [ + { + name: 'Senior UX Designer', + href: '#', + imageSrc: '/images/mentor/boy1.svg', + imageAlt: "Front of men's Basic Tee in black.", + color: 'Shoo Thar Mein', + }, + { + name: 'Photoshop Instructor', + href: '#', + imageSrc: '/images/mentor/boy2.svg', + imageAlt: "Front of men's Basic Tee in black.", + color: 'Cristian Doru Barin', + }, + { + name: 'SEO Expert', + href: '#', + imageSrc: '/images/mentor/boy3.svg', + imageAlt: "Front of men's Basic Tee in black.", + color: 'Tanzeel Ur Rehman', + }, + { + name: 'UI/UX Designer', + href: '#', + imageSrc: '/images/mentor/boy4.svg', + imageAlt: "Front of men's Basic Tee in black.", + color: 'Andrew Williams', + }, + { + name: 'Web Development / Web Design', + href: '#', + imageSrc: '/images/mentor/boy5.svg', + imageAlt: "Front of men's Basic Tee in black.", + color: 'Brad Schiff', + }, + { + name: 'Adobe Certified Instructor', + href: '#', + imageSrc: '/images/mentor/girl1.svg', + imageAlt: "Front of men's Basic Tee in black.", + color: 'Daniel Walter Scott', + }, +] + +const TestimonialData: TestimonialType[] = [ + { + profession: 'UX/UI Designer', + name: 'Andrew Williams', + imgSrc: '/images/testimonial/user-1.jpg', + starimg: '/images/testimonial/stars.png', + detail: + "I have been a Junior Graphic Designer for more then 10 years. Some things are problem that I had and teach how to solve them. That's why this course is so great!", + }, + { + profession: 'UX/UI Designer', + name: 'Cristian Doru Barin', + imgSrc: '/images/testimonial/user-2.jpg', + starimg: '/images/testimonial/stars.png', + detail: + "I have been a Junior Graphic Designer for more then 10 years. Some things are problem that I had and teach how to solve them. That's why this course is so great!", + }, + { + profession: 'UX/UI Designer', + name: 'Tanzeel Ur Rehman', + imgSrc: '/images/testimonial/user-3.jpg', + starimg: '/images/testimonial/stars.png', + detail: + "I have been a Junior Graphic Designer for more then 10 years. Some things are problem that I had and teach how to solve them. That's why this course is so great!", + }, + { + profession: 'UX/UI Designer', + name: 'Andrew Williams', + imgSrc: '/images/testimonial/user-1.jpg', + starimg: '/images/testimonial/stars.png', + detail: + "I have been a Junior Graphic Designer for more then 10 years. Some things are problem that I had and teach how to solve them. That's why this course is so great!", + }, +] + +const FooterLinkData: FooterLinkType[] = [ + { + section: 'Company', + links: [ + { label: 'Home', href: '/#Home' }, + { label: 'Courses', href: '/#Courses' }, + { label: 'Mentors', href: '/#mentors-section' }, + { label: 'Testimonial', href: '/#testimonial-section' }, + { label: 'Join', href: '/#join-section' }, + { label: 'Contact Us', href: '/#contact' }, + ], + }, + { + section: 'Support', + links: [ + { label: 'Help center', href: '/' }, + { label: 'Terms of service', href: '/' }, + { label: 'Legal', href: '/' }, + { label: 'Privacy Policy', href: '/' }, + { label: 'Status', href: '/' }, + ], + }, +] + +export const GET = () => { + return NextResponse.json({ + HeaderData, + CourseData, + HourData, + Companiesdata, + CourseDetailData, + MentorData, + TestimonialData, + FooterLinkData, + }) +} diff --git a/src/app/components/Auth/ForgotPassword/index.tsx b/src/app/components/Auth/ForgotPassword/index.tsx new file mode 100644 index 0000000..14aa28b --- /dev/null +++ b/src/app/components/Auth/ForgotPassword/index.tsx @@ -0,0 +1,324 @@ +"use client"; +import React from "react"; +import { useState } from "react"; +import toast from "react-hot-toast"; +import axios from "axios"; +import Loader from "@/app/components/Common/Loader"; +import Link from "next/link"; +import Image from "next/image"; + +const ForgotPassword = () => { + const [email, setEmail] = useState(""); + const [loader, setLoader] = useState(false); + + const handleSubmit = async (e: any) => { + e.preventDefault(); + + if (!email) { + toast.error("Please enter your email address."); + + return; + } + + setLoader(true); + + try { + const res = await axios.post("/api/forgot-password/reset", { + email: email.toLowerCase(), + }); + + if (res.status === 404) { + toast.error("User not found."); + return; + } + + if (res.status === 200) { + toast.success(res.data); + setEmail(""); + } + + setEmail(""); + setLoader(false); + } catch (error: any) { + toast.error(error?.response.data); + setLoader(false); + } + }; + + return ( +
+
+
+
+
+
+ + logo + logo + +
+ +
+
+ setEmail(e.target.value)} + required + className="w-full rounded-md border border-stroke bg-transparent px-5 py-3 text-base text-dark outline-hidden transition placeholder:text-dark-6 focus:border-primary focus-visible:shadow-none dark:border-dark-3 dark:text-white dark:focus:border-primary" + /> +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+ ); +}; + +export default ForgotPassword; diff --git a/src/app/components/Auth/MagicLink/index.tsx b/src/app/components/Auth/MagicLink/index.tsx new file mode 100644 index 0000000..871e595 --- /dev/null +++ b/src/app/components/Auth/MagicLink/index.tsx @@ -0,0 +1,67 @@ +"use client"; +import { useState } from "react"; +import { signIn } from "next-auth/react"; +import toast from "react-hot-toast"; +import { validateEmail } from "@/utils/validateEmail"; + +const MagicLink = () => { + const [email, setEmail] = useState(""); + const [loader, setLoader] = useState(false); + + const handleSubmit = (e: any) => { + e.preventDefault(); + + if (!email) { + return toast.error("Please enter your email address."); + } + + setLoader(true); + if (!validateEmail(email)) { + setLoader(false); + return toast.error("Please enter a valid email address."); + } else { + signIn("email", { + redirect: false, + email: email, + }) + .then((callback) => { + if (callback?.ok) { + toast.success("Email sent"); + setEmail(""); + setLoader(false); + } + }) + .catch((error) => { + console.log(error); + toast.error("Unable to send email!"); + setLoader(false); + }); + } + }; + + return ( +
+
+ setEmail(e.target.value.toLowerCase())} + className="w-full rounded-md border border-stroke bg-transparent px-5 py-3 text-base text-dark outline-hidden transition placeholder:text-dark-6 focus:border-primary focus-visible:shadow-none dark:border-dark-3 dark:text-white dark:focus:border-primary" + /> +
+
+ +
+
+ ); +}; + +export default MagicLink; diff --git a/src/app/components/Auth/ResetPassword/index.tsx b/src/app/components/Auth/ResetPassword/index.tsx new file mode 100644 index 0000000..4841c86 --- /dev/null +++ b/src/app/components/Auth/ResetPassword/index.tsx @@ -0,0 +1,368 @@ +"use client"; +import React, { useState, useEffect } from "react"; +import axios from "axios"; +import { useRouter } from "next/navigation"; +import toast from "react-hot-toast"; +import Loader from "@/app/components/Common/Loader"; +import Link from "next/link"; +import Image from "next/image"; + +const ResetPassword = ({ token }: { token: string }) => { + const [data, setData] = useState({ + newPassword: "", + ReNewPassword: "", + }); + const [loader, setLoader] = useState(false); + + const [user, setUser] = useState({ + email: "", + }); + + const router = useRouter(); + + useEffect(() => { + const verifyToken = async () => { + try { + const res = await axios.post(`/api/forgot-password/verify-token`, { + token, + }); + + if (res.status === 200) { + setUser({ + email: res.data.email, + }); + } + } catch (error: any) { + toast.error(error?.response?.data); + router.push("/forgot-password"); + } + }; + + verifyToken(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleChange = (e: React.ChangeEvent) => { + setData({ + ...data, + [e.target.name]: e.target.value, + }); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoader(true); + + if (data.newPassword === "") { + toast.error("Please enter your password."); + return; + } + + try { + const res = await axios.post(`/api/forgot-password/update`, { + email: user?.email, + password: data.newPassword, + }); + + if (res.status === 200) { + toast.success(res.data); + setData({ newPassword: "", ReNewPassword: "" }); + router.push("/signin"); + } + + setLoader(false); + } catch (error: any) { + toast.error(error.response.data); + setLoader(false); + } + }; + + return ( +
+
+
+
+
+
+ + logo + logo + +
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+ ); +}; + +export default ResetPassword; diff --git a/src/app/components/Auth/SignIn/index.tsx b/src/app/components/Auth/SignIn/index.tsx new file mode 100644 index 0000000..c57804d --- /dev/null +++ b/src/app/components/Auth/SignIn/index.tsx @@ -0,0 +1,107 @@ +'use client' +import { signIn } from 'next-auth/react' +import Link from 'next/link' +import { useRouter } from 'next/navigation' +import { useState } from 'react' +import toast from 'react-hot-toast' +import SocialSignIn from '../SocialSignIn' +import Logo from '@/app/components/Layout/Header/Logo' +import Loader from '@/app/components/Common/Loader' + +const Signin = () => { + const router = useRouter() + + const [loginData, setLoginData] = useState({ + email: '', + password: '', + checkboxToggle: false, + }) + const [loading, setLoading] = useState(false) + + const loginUser = (e: any) => { + e.preventDefault() + + setLoading(true) + signIn('credentials', { ...loginData, redirect: false }) + .then((callback) => { + if (callback?.error) { + toast.error(callback?.error) + console.log(callback?.error) + setLoading(false) + return + } + + if (callback?.ok && !callback?.error) { + toast.success('Login successful') + setLoading(false) + router.push('/') + } + }) + .catch((err) => { + setLoading(false) + console.log(err.message) + toast.error(err.message) + }) + } + + return ( + <> +
+ +
+ + + + + + OR + + + +
e.preventDefault()}> +
+ + setLoginData({ ...loginData, email: e.target.value }) + } + className='w-full rounded-md border border-solid bg-transparent px-5 py-3 text-base text-dark outline-hidden transition border-gray-200 placeholder:text-black/30 focus:border-primary focus-visible:shadow-none text-black' + /> +
+
+ + setLoginData({ ...loginData, password: e.target.value }) + } + className='w-full rounded-md border border-solid bg-transparent px-5 py-3 text-base text-dark outline-hidden transition border-gray-200 placeholder:text-black/30 focus:border-primary focus-visible:shadow-none text-black' + /> +
+
+ +
+
+ + + Forgot Password? + +

+ Not a member yet?{' '} + + Sign Up + +

+ + ) +} + +export default Signin diff --git a/src/app/components/Auth/SignUp/index.tsx b/src/app/components/Auth/SignUp/index.tsx new file mode 100644 index 0000000..5abc867 --- /dev/null +++ b/src/app/components/Auth/SignUp/index.tsx @@ -0,0 +1,112 @@ +'use client' +import Link from 'next/link' +import { useRouter } from 'next/navigation' +import toast from 'react-hot-toast' +import SocialSignUp from '../SocialSignUp' +import Logo from '@/app/components/Layout/Header/Logo' +import { useState } from 'react' +import Loader from '@/app/components/Common/Loader' +const SignUp = () => { + const router = useRouter() + const [loading, setLoading] = useState(false) + + const handleSubmit = (e: any) => { + e.preventDefault() + + setLoading(true) + const data = new FormData(e.currentTarget) + const value = Object.fromEntries(data.entries()) + const finalData = { ...value } + + fetch('/api/register', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(finalData), + }) + .then((res) => res.json()) + .then((data) => { + toast.success('Successfully registered') + setLoading(false) + router.push('/signin') + }) + .catch((err) => { + toast.error(err.message) + setLoading(false) + }) + } + + return ( + <> +
+ +
+ + + + + + OR + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +

+ By creating an account you are agree with our{' '} + + Privacy + {' '} + and{' '} + + Policy + +

+ +

+ Already have an account? + + Sign In + +

+ + ) +} + +export default SignUp diff --git a/src/app/components/Auth/SocialSignIn.tsx b/src/app/components/Auth/SocialSignIn.tsx new file mode 100644 index 0000000..3ef5452 --- /dev/null +++ b/src/app/components/Auth/SocialSignIn.tsx @@ -0,0 +1,70 @@ +import React from 'react' +import { signIn } from 'next-auth/react' + +const SocialSignIn = () => { + return ( + <> +
+ + + +
+ + ) +} + +export default SocialSignIn diff --git a/src/app/components/Auth/SocialSignUp.tsx b/src/app/components/Auth/SocialSignUp.tsx new file mode 100644 index 0000000..ba07178 --- /dev/null +++ b/src/app/components/Auth/SocialSignUp.tsx @@ -0,0 +1,70 @@ +import React from 'react' +import { signIn } from 'next-auth/react' + +const SocialSignUp = () => { + return ( + <> +
+ + + +
+ + ) +} + +export default SocialSignUp diff --git a/src/app/components/Breadcrumb/index.tsx b/src/app/components/Breadcrumb/index.tsx new file mode 100644 index 0000000..7a76a32 --- /dev/null +++ b/src/app/components/Breadcrumb/index.tsx @@ -0,0 +1,27 @@ +import React, { FC } from 'react'; +import Link from 'next/link'; + +interface BreadcrumbProps { + links: { href: string; text: string }[]; +} + +const Breadcrumb: FC = ({ links }) => { + const lastIndex = links.length - 1; + return ( +
+ {links.map((link, index) => ( + + {index !== lastIndex ? ( + + {link.text} + + ) : ( + {link.text} + )} + + ))} +
+ ); +}; + +export default Breadcrumb; \ No newline at end of file diff --git a/src/app/components/Common/Breadcrumb.tsx b/src/app/components/Common/Breadcrumb.tsx new file mode 100644 index 0000000..6feff67 --- /dev/null +++ b/src/app/components/Common/Breadcrumb.tsx @@ -0,0 +1,46 @@ +import Link from "next/link"; +import { BreadcrumbProps } from "../../types/breadcrumb"; // Adjust the import path based on your project structure + +const Breadcrumb: React.FC = ({ + pageName, + pageDescription, +}) => { + return ( +
+
+
+
+
+
+

+ {pageName} +

+

+ {pageDescription} +

+ +
    +
  • + + Home + +
  • +
  • +

    + / + {pageName} +

    +
  • +
+
+
+
+
+
+ ); +}; + +export default Breadcrumb; diff --git a/src/app/components/Common/Loader.tsx b/src/app/components/Common/Loader.tsx new file mode 100644 index 0000000..6eeed6d --- /dev/null +++ b/src/app/components/Common/Loader.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +const Loader = () => { + return ( + + ); +}; + +export default Loader; diff --git a/src/app/components/Common/PreLoader.tsx b/src/app/components/Common/PreLoader.tsx new file mode 100644 index 0000000..c9ba27f --- /dev/null +++ b/src/app/components/Common/PreLoader.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +const PreLoader = () => { + return ( +
+
+
+ ); +}; + +export default PreLoader; \ No newline at end of file diff --git a/src/app/components/Common/ScrollUp.tsx b/src/app/components/Common/ScrollUp.tsx new file mode 100644 index 0000000..101d294 --- /dev/null +++ b/src/app/components/Common/ScrollUp.tsx @@ -0,0 +1,9 @@ +"use client"; + +import { useEffect } from "react"; + +export default function ScrollUp() { + useEffect(() => window.document.scrollingElement?.scrollTo(0, 0), []); + + return null; +} diff --git a/src/app/components/Contact/Form/index.tsx b/src/app/components/Contact/Form/index.tsx new file mode 100644 index 0000000..9314c7a --- /dev/null +++ b/src/app/components/Contact/Form/index.tsx @@ -0,0 +1,181 @@ +'use client' +import React from 'react' +import { useState, useEffect } from 'react' + +const ContactForm = () => { + const [formData, setFormData] = useState({ + firstname: '', + lastname: '', + email: '', + phnumber: '', + Message: '', + }) + const [submitted, setSubmitted] = useState(false) + const [showThanks, setShowThanks] = useState(false) + const [loader, setLoader] = useState(false) + const [isFormValid, setIsFormValid] = useState(false) + + useEffect(() => { + const isValid = Object.values(formData).every( + (value) => value.trim() !== '' + ) + setIsFormValid(isValid) + }, [formData]) + const handleChange = (e: any) => { + const { name, value } = e.target + setFormData((prevData) => ({ + ...prevData, + [name]: value, + })) + } + const reset = () => { + formData.firstname = '' + formData.lastname = '' + formData.email = '' + formData.phnumber = '' + formData.Message = '' + } + const handleSubmit = async (e: any) => { + e.preventDefault() + setLoader(true) + + // TODO: 替换为你自己的邮箱地址 + fetch('https://formsubmit.co/ajax/your-email@example.com', { + method: 'POST', + headers: { 'Content-type': 'application/json' }, + body: JSON.stringify({ + Name: formData.firstname, + LastName: formData.lastname, + Email: formData.email, + PhoneNo: formData.phnumber, + Message: formData.Message, + }), + }) + .then((response) => response.json()) + .then((data) => { + if (data.success) { + setSubmitted(true) + setShowThanks(true) + reset() + + setTimeout(() => { + setShowThanks(false) + }, 5000) + } + + reset() + }) + .catch((error) => { + setLoader(false) + console.log(error.message) + }) + } + return ( +
+
+
+

Get in Touch

+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ +
+
+ {showThanks && ( +
+ Thank you for contacting us! We will get back to you soon. +
+
+ )} +
+
+
+ ) +} + +export default ContactForm diff --git a/src/app/components/Documentation/ColorConfiguraion.tsx b/src/app/components/Documentation/ColorConfiguraion.tsx new file mode 100644 index 0000000..21ad8ec --- /dev/null +++ b/src/app/components/Documentation/ColorConfiguraion.tsx @@ -0,0 +1,38 @@ +export const ColorConfiguration = () => { + return ( + <> +

Colors

+
+

+ + 1. Override Colors + {" "} +
+ For any change in colors : tailwind.config.ts +

+
+

+ --color-primary: #611f69; + --color-cream: #fcf5ef; + --color-success: #6b9f36; + --color-orange: #f9cd92; +

+
+
+
+

+ + 2. Override Theme Colors + {" "} +
+ For change , go to : tailwind.config.ts +

+
+

+ --color-primary: #611f69; +

+
+
+ + ); +}; diff --git a/src/app/components/Documentation/Configuration.tsx b/src/app/components/Documentation/Configuration.tsx new file mode 100644 index 0000000..4b09d4d --- /dev/null +++ b/src/app/components/Documentation/Configuration.tsx @@ -0,0 +1,16 @@ +import { ColorConfiguration } from "./ColorConfiguraion" +import { LogoConfiguration } from "./LogoConfiguration" +import { TypographyConfiguration } from "./TypographyConfiguration" + +export const Configuration = () => { + return ( + <> +
+

Project Configuration

+ + + +
+ + ) +} \ No newline at end of file diff --git a/src/app/components/Documentation/DocNavigation.tsx b/src/app/components/Documentation/DocNavigation.tsx new file mode 100644 index 0000000..ad70a75 --- /dev/null +++ b/src/app/components/Documentation/DocNavigation.tsx @@ -0,0 +1,58 @@ +"use client"; +import Link from "next/link"; +import { useEffect, useState } from "react"; + +export const DocNavigation = () => { + const [navItem, setNavItem] = useState("version"); + + function getNavItem(item: string) { + setNavItem(item); + } + + useEffect(() => { + console.log(navItem); + }, [navItem]); + + const DocsNav = [ + { + id: 1, + navItem: "Package Versions", + hash: "version", + }, + { + id: 2, + navItem: "Pacakge Structure", + hash: "structure", + }, + { + id: 3, + navItem: "Quick Start", + hash: "start", + }, + { + id: 4, + navItem: "Project Configuration", + hash: "configuration", + }, + ]; + + return ( +
+ {DocsNav.map((item) => { + return ( + getNavItem(item.hash)} + className={`py-2.5 hover:bg-primary/20 hover:text-primary dark:hover:text-primary xl:min-w-60 lg:min-w-52 min-w-full px-4 rounded-md text-base font-medium ${item.hash === navItem + ? "bg-primary text-white" + : "text-black/60" + }`} + > + {item.navItem} + + ); + })} +
+ ); +}; diff --git a/src/app/components/Documentation/Documentation.tsx b/src/app/components/Documentation/Documentation.tsx new file mode 100644 index 0000000..8d6d191 --- /dev/null +++ b/src/app/components/Documentation/Documentation.tsx @@ -0,0 +1,25 @@ +import { Configuration } from './Configuration' +import { DocNavigation } from './DocNavigation' +import { Introduction } from './Introduction' +import { PackageStructure } from './PackageStructure' +import { QuickStart } from './QuickStart' + +export const Documentation = () => { + return ( +
+
+
+
+ +
+
+ + + + +
+
+
+
+ ) +} diff --git a/src/app/components/Documentation/Introduction.tsx b/src/app/components/Documentation/Introduction.tsx new file mode 100644 index 0000000..8025711 --- /dev/null +++ b/src/app/components/Documentation/Introduction.tsx @@ -0,0 +1,139 @@ +'use client' +import Image from 'next/image' +// import nextImg from '/public/images/documentation/Categories=Nextjs.svg' +// import reactImg from '/public/images/documentation/Categories=React.svg' +// import tailwindImg from '/public/images/documentation/Categories=Tailwind.svg' +// import nextauthImg from '/public/images/documentation/nextauth.png' +// import typescriptImg from '/public/images/documentation/Categories=Typescript.svg' +// import axiosImg from '/public/images/documentation/axios.svg' +import { Icon } from '@iconify/react/dist/iconify.js' +import { useState } from 'react' +import { DocNavigation } from './DocNavigation' + +export const Introduction = () => { + const [docNavbarOpen, setDocNavbarOpen] = useState(false) + const PackageVersions = [ + { + id: '1', + packageName: 'NextJs', + img: '/images/documentation/Categories=Nextjs.svg', + version: '15.5.9', + }, + { + id: '2', + packageName: 'React', + img: '/images/documentation/Categories=React.svg', + version: '19.2.3', + }, + { + id: '3', + packageName: 'Tailwindcss', + img: '/images/documentation/Categories=Tailwind.svg', + version: '4', + }, + { + id: '4', + packageName: 'NextAuth', + img: '/images/documentation/nextauth.png', + version: '4.24.11', + }, + { + id: '5', + packageName: 'Typescript', + img: '/images/documentation/Categories=Typescript.svg', + version: '5', + }, + ] + return ( + <> +
+ {docNavbarOpen && ( +
setDocNavbarOpen(false)} + /> + )} + +
+

+ Package Versions +

+ +
+ +
+ {PackageVersions && + PackageVersions.map((item) => { + return ( +
+ npm-package +
{`v${item.version}`}
+

+ {item.packageName} +

+
+ ) + })} +
+
+

+ SiEducational Tailwind NextJs Template is built with Tailwindcss and + Nextjs. +

+

+ These theme is ready to use and you can totally customize as per + your requirement. +

+

+ For Customize, You should have knowledge of NextJs, ReactJs, + Tailwind and JSX to be able to modify these template. +

+
+
+ +
+
+

+ Docs Menu +

+ +
+ +
+ + ) +} diff --git a/src/app/components/Documentation/LogoConfiguration.tsx b/src/app/components/Documentation/LogoConfiguration.tsx new file mode 100644 index 0000000..0222e03 --- /dev/null +++ b/src/app/components/Documentation/LogoConfiguration.tsx @@ -0,0 +1,32 @@ +export const LogoConfiguration = () => { + return ( + <> +

Logo

+
+

+ 1. Change Logo over here :{" "} + + {" "} + src/components/Layout/Header/Logo/index.tsx + {" "} +

+
+
+

<Link href="/">

+

<Image

+

src="/images/logo/logo.svg"

+

alt="logo"

+

width={160}

+

height={50}

+

quality={100}

+

+ style={width: "auto", height: + "auto"} +

+

/>

+
+
+
+ + ); +}; diff --git a/src/app/components/Documentation/PackageStructure.tsx b/src/app/components/Documentation/PackageStructure.tsx new file mode 100644 index 0000000..4da3db5 --- /dev/null +++ b/src/app/components/Documentation/PackageStructure.tsx @@ -0,0 +1,469 @@ +import { Icon } from "@iconify/react/dist/iconify.js"; +import Image from "next/image"; +import tline from "/public/images/svgs/T-Line.svg"; +import t_half_line from "/public/images/svgs/T-half_line.svg"; +import straight_line from "/public/images/svgs/straight_group.svg"; +import small_straight_line from "/public/images/svgs/smal_straight_line.svg"; + +export const PackageStructure = () => { + const Counts = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + ]; + return ( +
+

+ Pacakge Structure +

+
+
+
+ SiEducational Tailwind NextJs Template +
+
+
    +
  • +
    +

    |—

    + + + packages + +
    +
    +
    + {Counts.slice(0, 22).map((item) => { + return

    |

    ; + })} +
    +
      +
    • +
        +
      • +
          +
        • +
          +

          |—

          + + + markdown + +
          +
        • +
        • +
          +

          |—

          + + + public + +
          +
        • +
        • +
          +

          |—

          + + + src + +
          +
          +
          + {Counts.slice(0, 22).map((item) => { + return

          |

          ; + })} +
          +
            +
          • +
            +

            |—

            + + + app + +
            +
            +
            + {Counts.slice(0, 16).map((item) => { + return ( +

            |

            + ); + })} +
            +
              +
            • +
              +

              |—

              + + + (site) + {" "} + + (Contains all the pages) + +
              +
              +
              + {Counts.slice(0, 5).map( + (item, index) => { + return ( +

              + | +

              + ); + } + )} +
              + +
                +
              • +
                +

                + |— +

                + + + (auth) + +
                +
                +
                + {Counts.slice(0, 2).map( + (item) => { + return ( +

                + | +

                + ); + } + )} +
                +
                  +
                • +
                  +

                  + | +

                  +
                  +

                  + |— +

                  + + + signin + +
                  +
                  +
                • +
                • +
                  +

                  + | +

                  +
                  +

                  + |— +

                  + + + signup + +
                  +
                  +
                • +
                +
                +
              • +
              • +
                +

                + |— +

                + + + documentation + +
                +
              • +
              +
              +
            • +
            • +
              +

              |—

              + + + api + +
              +
              +
              + {Counts.slice(0, 2).map((item) => { + return ( +

              + | +

              + ); + })} +
              +
                +
              • +
                +

                + | +

                +
                +

                + |— +

                + + + auth + +
                +
                +
              • +
              • +
                +

                + | +

                +
                +

                + |— +

                + + + contex + +
                +
                +
              • +
              +
              +
            • +
            • +
              +

              |—

              + + + Context + +
              +
              +
              + {Counts.slice(0, 1).map((item) => { + return ( +

              + | +

              + ); + })} +
              +
                +
              • +
                +

                + | +

                +
                +

                + |— +

                + + +

                + authDialogContext.tsx +

                +
                +
                +
                +
              • +
              +
              +
            • +
            • +
              +

              |—

              + + global.css + +
              +
            • +
            • +
              +

              |—

              + + layout.tsx + +
              +
            • +
            • +
              +

              |—

              + + not-found.tsx + +
              +
            • +
            • +
              +

              |—

              + + page.tsx + +
              +
            • +
            +
            +
          • +
          • +
            +

            |—

            + + + components + {" "} + + (All the Components of this template.) + +
            +
          • +
          • +
            +

            |—

            + + + styles + +
            +
          • +
          • +
            +

            |—

            + + + types + +
            +
          • +
          • +
            +

            |—

            + + + utils + +
            +
          • +
          +
          +
        • +
        • +
          +

          |—

          + + + next.config.mjs + +
          +
        • +
        • +
          +

          |—

          + + + postcss.config.mjs + +
          +
        • +
        • +
          +

          |—

          + + + package.json + +
          +
        • +
        • +
          +

          |—

          + + + tsconfig.json + +
          +
        • +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+ ); +}; diff --git a/src/app/components/Documentation/QuickStart.tsx b/src/app/components/Documentation/QuickStart.tsx new file mode 100644 index 0000000..d0ccec9 --- /dev/null +++ b/src/app/components/Documentation/QuickStart.tsx @@ -0,0 +1,87 @@ +export const QuickStart = () => { + return ( +
+

Quick Start

+
+
1. Requirements
+

+ Before proceeding, you need to have the latest stable{" "} + + node.js + {" "} +

+
+ Recommended environment: +
+
    +
  • node js 20+
  • +
  • npm js 10+
  • +
+
+
+
2. Install
+

+ Open package folder and install its dependencies. We recommanded yarn + or npm.{" "} +

+
+ 1) Install with npm: +
+
+

+ cd project-folder +

+

npm install

+
+
+ 1) Install with yarn: +
+
+

+ cd project-folder +

+

yarn install

+
+
+
+
3. Start
+

+ Once npm install is done now you an run the app. +

+ +
+

npm run dev or yarn run dev

+
+

+ This command will start a local webserver{" "} + http://localhost:3000: +

+
+

+ {"> sieducational_project@2.0.0 dev"} +

+

{"> next dev"}

+

{"-Next.js 14.2.4"}

+

+ {"-Local: http://localhost:3000"} +

+
+
+
+
+ 4. Build / Deployment +
+

+ After adding url run below command for build a app. +

+ +
+

npm run build or yarn build

+
+

+ Finally, Your webiste is ready to be deployed.🥳 +

+
+
+ ); +}; diff --git a/src/app/components/Documentation/TypographyConfiguration.tsx b/src/app/components/Documentation/TypographyConfiguration.tsx new file mode 100644 index 0000000..cde682a --- /dev/null +++ b/src/app/components/Documentation/TypographyConfiguration.tsx @@ -0,0 +1,21 @@ +export const TypographyConfiguration = () => { + return ( + <> +

Typography

+
+

+ 1. Change Font family over here :{" "} + src/app/layout.tsx{" "} +

+
+

+ {`import { Inter } from "next/font/google";`} +

+

+ {`const font = Inter({ subsets: ["latin"] });`} +

+
+
+ + ); +}; \ No newline at end of file diff --git a/src/app/components/Home/Companies/index.tsx b/src/app/components/Home/Companies/index.tsx new file mode 100644 index 0000000..ae68d64 --- /dev/null +++ b/src/app/components/Home/Companies/index.tsx @@ -0,0 +1,92 @@ +'use client' +import React, { useEffect, useState } from 'react' +import Slider from 'react-slick' +import 'slick-carousel/slick/slick.css' +import 'slick-carousel/slick/slick-theme.css' +import Image from 'next/image' + +const Companies = () => { + const settings = { + dots: false, + infinite: true, + slidesToShow: 4, + slidesToScroll: 1, + arrows: false, + autoplay: true, + speed: 2000, + autoplaySpeed: 2000, + cssEase: 'linear', + responsive: [ + { + breakpoint: 1024, + settings: { + slidesToShow: 4, + slidesToScroll: 1, + infinite: true, + dots: false, + }, + }, + { + breakpoint: 700, + settings: { + slidesToShow: 2, + slidesToScroll: 1, + infinite: true, + dots: false, + }, + }, + { + breakpoint: 500, + settings: { + slidesToShow: 1, + slidesToScroll: 1, + infinite: true, + dots: false, + }, + }, + ], + } + + const [companies, setCompianes] = useState<{ imgSrc: string }[]>([]) + + useEffect(() => { + const fetchData = async () => { + try { + const res = await fetch('/api/data') + if (!res.ok) throw new Error('Failed to fetch') + const data = await res.json() + setCompianes(data.Companiesdata) + } catch (error) { + console.error('Error fetching services:', error) + } + } + fetchData() + }, []) + + return ( +
+
+

+ Trusted by companies of all sizes +

+
+ + {companies.map((item, i) => ( +
+ {item.imgSrc} +
+ ))} +
+
+
+
+ ) +} + +export default Companies diff --git a/src/app/components/Home/Courses/index.tsx b/src/app/components/Home/Courses/index.tsx new file mode 100644 index 0000000..74beb59 --- /dev/null +++ b/src/app/components/Home/Courses/index.tsx @@ -0,0 +1,252 @@ +'use client' +import { useEffect, useState } from 'react' +import { Icon } from '@iconify/react/dist/iconify.js' +import Image from 'next/image' +import { CourseDetailType } from '@/app/types/coursedetail' +import CourseDetailSkeleton from '../../Skeleton/CourseDetail' +import Link from 'next/link' + +interface Name { + imageSrc: string + course: string + price: string + profession: string + category: + | 'webdevelopment' + | 'mobiledevelopment' + | 'datascience' + | 'cloudcomputing' +} + +const NamesList = () => { + // ------------------------------------------------------------- + const [courseDetail, setCourseDetail] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const fetchData = async () => { + try { + const res = await fetch('/api/data') + if (!res.ok) throw new Error('Failed to fetch.') + const data = await res.json() + setCourseDetail(data.CourseDetailData) + } catch (error) { + console.error('Error fetching services:', error) + } finally { + setLoading(false) + } + } + fetchData() + }, []) + // ------------------------------------------------------------- + + const [selectedButton, setSelectedButton] = useState< + | 'mobiledevelopment' + | 'webdevelopment' + | 'datascience' + | 'cloudcomputing' + | 'all' + | null + >('webdevelopment') + const mobileDevelopment = courseDetail.filter( + (name) => name.category === 'mobiledevelopment' + ) + const webDevelopment = courseDetail.filter( + (name) => name.category === 'webdevelopment' + ) + const dataScience = courseDetail.filter( + (name) => name.category === 'datascience' + ) + const cloudComputing = courseDetail.filter( + (name) => name.category === 'cloudcomputing' + ) + + let selectedNames: Name[] = [] + if (selectedButton === 'mobiledevelopment') { + selectedNames = mobileDevelopment + } else if (selectedButton === 'webdevelopment') { + selectedNames = webDevelopment + } else if (selectedButton === 'datascience') { + selectedNames = dataScience + } else if (selectedButton === 'cloudcomputing') { + selectedNames = cloudComputing + } + + const nameElements = selectedNames.map((name, index) => ( +
+
+
+ {name.course} +
+
+
+
+

{name.course}

+
+

${name.price}

+
+
+ + + +
+
+

12 Classes

+
+
+ circle +

120

+
+
+ star +

4.5

+
+
+
+
+
+
+ )) + + return ( +
+
+
+

Popular Courses

+
+ +
+
+
+ {/* FOR DESKTOP VIEW */} + + + + + + {/* FOR MOBILE VIEW */} + setSelectedButton('webdevelopment')} + className={ + 'text-5xl sm:hidden block ' + + (selectedButton === 'webdevelopment' + ? 'border-b-2 border-yellow-200' + : 'text-gray-400') + } + /> + + setSelectedButton('mobiledevelopment')} + className={ + 'text-5xl sm:hidden block ' + + (selectedButton === 'mobiledevelopment' + ? 'border-b-2 border-yellow-200' + : 'text-gray-400') + } + /> + + setSelectedButton('datascience')} + className={ + 'text-5xl sm:hidden block ' + + (selectedButton === 'datascience' + ? 'border-b-2 border-yellow-200' + : 'text-gray-400') + } + /> + + setSelectedButton('cloudcomputing')} + className={ + 'text-5xl sm:hidden block ' + + (selectedButton === 'cloudcomputing' + ? 'border-b-2 border-yellow-200' + : 'text-gray-400') + } + /> +
+
+
+ {loading ? ( + Array.from({ length: 4 }).map((_, i) => ( + + )) + ) : nameElements.length > 0 ? ( + nameElements + ) : ( +

No data to show

+ )} +
+
+
+
+ ) +} + +export default NamesList diff --git a/src/app/components/Home/Hero/Dropdownone.tsx b/src/app/components/Home/Hero/Dropdownone.tsx new file mode 100644 index 0000000..13306a6 --- /dev/null +++ b/src/app/components/Home/Hero/Dropdownone.tsx @@ -0,0 +1,91 @@ +'use client' +import { Fragment, useEffect, useState } from 'react' +import { + Listbox, + ListboxButton, + ListboxOptions, + ListboxOption, + Transition, +} from '@headlessui/react' +import { Icon } from '@iconify/react/dist/iconify.js' +import { CourseType } from '@/app/types/course' + +const Dropdown = () => { + const [course, setCourse] = useState([]) + const [selected, setSelected] = useState(null) + + useEffect(() => { + const fetchData = async () => { + try { + const res = await fetch('/api/data') + if (!res.ok) throw new Error('Failed to fetch') + const data = await res.json() + setCourse(data.CourseData) + setSelected(data.CourseData[0]) + } catch (error) { + console.error('Error fetching services:', error) + } + } + fetchData() + }, []) + + return ( +
+

What do you want to learn?

+ +
+ + + {selected?.name} + + + + + + + + {course.map((person, personIdx) => ( + + `relative cursor-default select-none py-2 pl-10 pr-4 ${ + active ? 'bg-amber-100 text-amber-900' : 'text-gray-900' + }` + } + value={person}> + {({ selected }) => ( + <> + + {person.name} + + {selected ? ( + + + + ) : null} + + )} + + ))} + + +
+
+
+ ) +} + +export default Dropdown diff --git a/src/app/components/Home/Hero/Dropdowntwo.tsx b/src/app/components/Home/Hero/Dropdowntwo.tsx new file mode 100644 index 0000000..d941e2e --- /dev/null +++ b/src/app/components/Home/Hero/Dropdowntwo.tsx @@ -0,0 +1,91 @@ +'use client' +import { Fragment, useEffect, useState } from 'react' +import { + Listbox, + ListboxButton, + ListboxOptions, + ListboxOption, + Transition, +} from '@headlessui/react' +import { Icon } from '@iconify/react/dist/iconify.js' +import { Hourtype } from '@/app/types/hour' + +const Dropdown = () => { + const [hour, setHour] = useState([]) + const [selected, setSelected] = useState(null) + + useEffect(() => { + const fetchData = async () => { + try { + const res = await fetch('/api/data') + if (!res.ok) throw new Error('Failed to fetch') + const data = await res.json() + setHour(data.HourData) + setSelected(data.HourData[0]) + } catch (error) { + console.error('Error fetching services:', error) + } + } + fetchData() + }, []) + + return ( +
+

Hours you going to invest?

+ +
+ + + {selected?.name} + + + + + + + + {hour.map((person, personIdx) => ( + + `relative cursor-default select-none py-2 pl-10 pr-4 ${ + active ? 'bg-amber-100 text-amber-900' : 'text-gray-900' + }` + } + value={person}> + {({ selected }) => ( + <> + + {person.name} + + {selected ? ( + + + + ) : null} + + )} + + ))} + + +
+
+
+ ) +} + +export default Dropdown diff --git a/src/app/components/Home/Hero/index.tsx b/src/app/components/Home/Hero/index.tsx new file mode 100644 index 0000000..d8daa1c --- /dev/null +++ b/src/app/components/Home/Hero/index.tsx @@ -0,0 +1,103 @@ +import Link from 'next/link' +import Dropdownone from './Dropdownone' +import Dropdowntwo from './Dropdowntwo' +import Image from 'next/image' + +const Banner = () => { + return ( +
+
+
+
+

+ Advance your engineering skills with our courses +

+

+ Build skills with our courses and mentor from world-class + companies. +

+
+
+
+ img1 + + img2 + img3 + img4 + img5 +
+
+
+

4.6

+ stars-icon +
+
+

Rated by 25k on google.

+
+
+
+
+
+ + {/* DROPDOWN BUTTONS */} + +
+
+
+ +
+
+ +
+
+ + + +
+
+
+
+
+
+ ) +} + +export default Banner diff --git a/src/app/components/Home/Mentor/index.tsx b/src/app/components/Home/Mentor/index.tsx new file mode 100644 index 0000000..6388130 --- /dev/null +++ b/src/app/components/Home/Mentor/index.tsx @@ -0,0 +1,79 @@ +'use client' + +import Image from 'next/image' +import Link from 'next/link' +import { useEffect, useState } from 'react' +import { MentorType } from '@/app/types/mentor' +import MentorSkeleton from '../../Skeleton/Mentor' + +const Mentor = () => { + const [mentor, setMentor] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const fetchData = async () => { + try { + const res = await fetch('/api/data') + if (!res.ok) throw new Error('Failed to fetch') + const data = await res.json() + setMentor(data.MentorData) + } catch (error) { + console.error('Error fetching services:', error) + } finally { + setLoading(false) + } + } + fetchData() + }, []) + + return ( +
+
+
+

Meet with our Mentors

+
+ +
+
+ +
+ {loading + ? Array.from({ length: 6 }).map((_, i) => ( + + )) + : mentor.map((item, index) => ( +
+
+ {item.imageAlt} +
+
+
+
+ + {item.name} + +
+

+ {item.color} +

+
+
+
+ ))} +
+
+
+ ) +} + +export default Mentor diff --git a/src/app/components/Home/Newsletter/index.tsx b/src/app/components/Home/Newsletter/index.tsx new file mode 100644 index 0000000..2441865 --- /dev/null +++ b/src/app/components/Home/Newsletter/index.tsx @@ -0,0 +1,45 @@ +import Image from 'next/image' + +const Newsletter = () => { + return ( +
+
+
+
+
+

Join Our Newsletter

+

+ Subscribe our newsletter for discounts, promo and many more. +

+
+ + +
+
+
+
+ bgimg +
+
+
+
+
+
+ ) +} + +export default Newsletter diff --git a/src/app/components/Home/Testimonial/index.tsx b/src/app/components/Home/Testimonial/index.tsx new file mode 100644 index 0000000..3274bbd --- /dev/null +++ b/src/app/components/Home/Testimonial/index.tsx @@ -0,0 +1,110 @@ +'use client' +import { useEffect, useState } from 'react' +import Image from 'next/image' +import Slider from 'react-slick' +import 'slick-carousel/slick/slick.css' +import 'slick-carousel/slick/slick-theme.css' +import { TestimonialType } from '@/app/types/testimonial' +import TestimonialSkeleton from '../../Skeleton/Testimonial' + +// CAROUSEL SETTINGS + +const Testimonial = () => { + const [testimonial, setTestimonial] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const fetchData = async () => { + try { + const res = await fetch('/api/data') + if (!res.ok) throw new Error('Failed to fetch') + const data = await res.json() + setTestimonial(data.TestimonialData) + } catch (error) { + console.error('Error fetching services:', error) + } finally { + setLoading(false) + } + } + fetchData() + }, []) + + const settings = { + dots: true, + infinite: true, + slidesToShow: 3, + slidesToScroll: 1, + arrows: false, + autoplay: false, + cssEase: 'linear', + responsive: [ + { + breakpoint: 1200, + settings: { + slidesToShow: 2, + }, + }, + { + breakpoint: 800, + settings: { + slidesToShow: 1, + }, + }, + ], + } + return ( +
+
+
+

+ What Our Happy
Students Says +

+
+ +
+
+

+ Build skills with our courses and mentor
from world-class + companies. +

+ + {loading + ? Array.from({ length: 3 }).map((_, i) => ( + + )) + : testimonial.map((items, i) => ( +
+
+
+ gaby +
+

{items.profession}

+

{items.name}

+ stars-img +

+ {items.detail} +

+
+
+ ))} +
+
+
+ ) +} + +export default Testimonial diff --git a/src/app/components/Layout/Footer/index.tsx b/src/app/components/Layout/Footer/index.tsx new file mode 100644 index 0000000..dee0e55 --- /dev/null +++ b/src/app/components/Layout/Footer/index.tsx @@ -0,0 +1,139 @@ +'use client' + +import Link from 'next/link' +import Image from 'next/image' +import { Icon } from '@iconify/react/dist/iconify.js' +import { useEffect, useState } from 'react' +import { FooterLinkType } from '@/app/types/footerlinks' + +const Footer = () => { + const [footerlink, SetFooterlink] = useState([]) + + useEffect(() => { + const fetchData = async () => { + try { + const res = await fetch('/api/data') + if (!res.ok) throw new Error('Failed to fetch') + const data = await res.json() + SetFooterlink(data.FooterLinkData) + } catch (error) { + console.error('Error fetching services:', error) + } + } + fetchData() + }, []) + + return ( +
+
+
+
+
+ Logo +
+

+ {' '} + Level up your skills, and get dream
job with passion.{' '} +

+
+ + + + + + + + + + + + +
+
+ + {/* CLOUMN-2/3 */} +
+
+ {footerlink.map((product, i) => ( +
+

+ {product.section} +

+
    + {product.links.map((item, i) => ( +
  • + + {item.label} + +
  • + ))} +
+
+ ))} +
+
+ {/* CLOUMN-4 */} + +
+

+ Stay up to date +

+
+ +
+ +
+
+
+
+
+
+

+ @2025 - All Rights Reserved +

+
+
+ ) +} + +export default Footer diff --git a/src/app/components/Layout/Header/Logo/index.tsx b/src/app/components/Layout/Header/Logo/index.tsx new file mode 100644 index 0000000..030655c --- /dev/null +++ b/src/app/components/Layout/Header/Logo/index.tsx @@ -0,0 +1,19 @@ +import Image from "next/image"; +import Link from "next/link"; + +const Logo: React.FC = () => { + return ( + + logo + + ); +}; + +export default Logo; diff --git a/src/app/components/Layout/Header/Navigation/HeaderLink.tsx b/src/app/components/Layout/Header/Navigation/HeaderLink.tsx new file mode 100644 index 0000000..77d7ff1 --- /dev/null +++ b/src/app/components/Layout/Header/Navigation/HeaderLink.tsx @@ -0,0 +1,70 @@ +'use client' +import { useState } from 'react' +import Link from 'next/link' +import { HeaderItem } from '../../../../types/menu' +import { usePathname } from 'next/navigation' + +const HeaderLink: React.FC<{ item: HeaderItem }> = ({ item }) => { + const [submenuOpen, setSubmenuOpen] = useState(false) + const path = usePathname() + const handleMouseEnter = () => { + if (item.submenu) { + setSubmenuOpen(true) + } + } + const handleMouseLeave = () => { + setSubmenuOpen(false) + } + + return ( +
+ + {item.label} + {item.submenu && ( + + + + )} + + {submenuOpen && ( +
+ {item.submenu?.map((subItem, index) => ( + + {subItem.label} + + ))} +
+ )} +
+ ) +} + +export default HeaderLink diff --git a/src/app/components/Layout/Header/Navigation/MobileHeaderLink.tsx b/src/app/components/Layout/Header/Navigation/MobileHeaderLink.tsx new file mode 100644 index 0000000..4221b13 --- /dev/null +++ b/src/app/components/Layout/Header/Navigation/MobileHeaderLink.tsx @@ -0,0 +1,52 @@ +import { useState } from 'react' +import Link from 'next/link' +import { HeaderItem } from '../../../../types/menu' + +const MobileHeaderLink: React.FC<{ item: HeaderItem }> = ({ item }) => { + const [submenuOpen, setSubmenuOpen] = useState(false) + + const handleToggle = () => { + setSubmenuOpen(!submenuOpen) + } + + return ( +
+ + {item.label} + {item.submenu && ( + + + + )} + + {submenuOpen && item.submenu && ( +
+ {item.submenu.map((subItem, index) => ( + + {subItem.label} + + ))} +
+ )} +
+ ) +} + +export default MobileHeaderLink diff --git a/src/app/components/Layout/Header/ThemeToggler.tsx b/src/app/components/Layout/Header/ThemeToggler.tsx new file mode 100644 index 0000000..2958572 --- /dev/null +++ b/src/app/components/Layout/Header/ThemeToggler.tsx @@ -0,0 +1,33 @@ +'use client' +import { useTheme } from "next-themes"; + +const ThemeToggler = () => { + const { theme, setTheme } = useTheme(); + return ( + + ); +}; + +export default ThemeToggler; diff --git a/src/app/components/Layout/Header/index.tsx b/src/app/components/Layout/Header/index.tsx new file mode 100644 index 0000000..f34e864 --- /dev/null +++ b/src/app/components/Layout/Header/index.tsx @@ -0,0 +1,213 @@ +'use client' + +import { useEffect, useRef, useState } from 'react' +import Logo from './Logo' +import HeaderLink from '../Header/Navigation/HeaderLink' +import MobileHeaderLink from '../Header/Navigation/MobileHeaderLink' +import Signin from '@/app/components/Auth/SignIn' +import SignUp from '@/app/components/Auth/SignUp' +import { Icon } from '@iconify/react/dist/iconify.js' +import { HeaderItem } from '@/app/types/menu' + +const Header: React.FC = () => { + const [headerData, setHeaderData] = useState([]) + + const [navbarOpen, setNavbarOpen] = useState(false) + const [sticky, setSticky] = useState(false) + const [isSignInOpen, setIsSignInOpen] = useState(false) + const [isSignUpOpen, setIsSignUpOpen] = useState(false) + + const navbarRef = useRef(null) + const signInRef = useRef(null) + const signUpRef = useRef(null) + const mobileMenuRef = useRef(null) + + useEffect(() => { + const fetchData = async () => { + try { + const res = await fetch('/api/data') + if (!res.ok) throw new Error('Failed to fetch') + const data = await res.json() + setHeaderData(data.HeaderData) + } catch (error) { + console.error('Error fetching services:', error) + } + } + fetchData() + }, []) + + const handleScroll = () => { + setSticky(window.scrollY >= 10) + } + + const handleClickOutside = (event: MouseEvent) => { + if ( + signInRef.current && + !signInRef.current.contains(event.target as Node) + ) { + setIsSignInOpen(false) + } + if ( + signUpRef.current && + !signUpRef.current.contains(event.target as Node) + ) { + setIsSignUpOpen(false) + } + if ( + mobileMenuRef.current && + !mobileMenuRef.current.contains(event.target as Node) && + navbarOpen + ) { + setNavbarOpen(false) + } + } + + useEffect(() => { + window.addEventListener('scroll', handleScroll) + document.addEventListener('mousedown', handleClickOutside) + return () => { + window.removeEventListener('scroll', handleScroll) + document.removeEventListener('mousedown', handleClickOutside) + } + }, [navbarOpen, isSignInOpen, isSignUpOpen]) + + useEffect(() => { + if (isSignInOpen || isSignUpOpen || navbarOpen) { + document.body.style.overflow = 'hidden' + } else { + document.body.style.overflow = '' + } + }, [isSignInOpen, isSignUpOpen, navbarOpen]) + + return ( +
+
+
+ + +
+ + {isSignInOpen && ( +
+
+ + +
+
+ )} + + {isSignUpOpen && ( +
+
+ + +
+
+ )} + +
+
+ {navbarOpen && ( +
+ )} +
+
+

+ +

+ {/* */} + +
+ +
+
+
+ ) +} + +export default Header diff --git a/src/app/components/NotFound/index.tsx b/src/app/components/NotFound/index.tsx new file mode 100644 index 0000000..ed51a63 --- /dev/null +++ b/src/app/components/NotFound/index.tsx @@ -0,0 +1,77 @@ +import React from "react"; +import Image from "next/image"; +import Link from "next/link"; + +const NotFound = () => { + return ( +
+
+
+
+
+ image +
+
+
+
+
+ + + + + + + + +
+

+ We Can't Seem to Find The Page You're Looking For. +

+

+ Oops! The page you are looking for does not exist. It might have + been moved or deleted. +

+ + Go To Home + +
+
+
+
+
+ ); +}; + +export default NotFound; \ No newline at end of file diff --git a/src/app/components/ScrollToTop/index.tsx b/src/app/components/ScrollToTop/index.tsx new file mode 100644 index 0000000..4af4908 --- /dev/null +++ b/src/app/components/ScrollToTop/index.tsx @@ -0,0 +1,40 @@ +'use client' +import { useEffect, useState } from 'react' + +export default function ScrollToTop() { + const [isVisible, setIsVisible] = useState(false) + + const scrollToTop = () => { + window.scrollTo({ + top: 0, + behavior: 'smooth', + }) + } + + useEffect(() => { + const toggleVisibility = () => { + if (window.pageYOffset > 300) { + setIsVisible(true) + } else { + setIsVisible(false) + } + } + + window.addEventListener('scroll', toggleVisibility) + + return () => window.removeEventListener('scroll', toggleVisibility) + }, []) + + return ( + <> + {isVisible && ( +
+ +
+ )} + + ) +} diff --git a/src/app/components/SharedComponent/HeroSub/index.tsx b/src/app/components/SharedComponent/HeroSub/index.tsx new file mode 100644 index 0000000..c467c75 --- /dev/null +++ b/src/app/components/SharedComponent/HeroSub/index.tsx @@ -0,0 +1,20 @@ +import React, { FC } from "react"; + +interface HeroSubProps { + title: string; +} + +const HeroSub: FC = ({ title }) => { + + return ( + <> +
+
+

{title}

+
+
+ + ); +}; + +export default HeroSub; \ No newline at end of file diff --git a/src/app/components/SharedComponent/Volunteer/index.tsx b/src/app/components/SharedComponent/Volunteer/index.tsx new file mode 100644 index 0000000..b7e6978 --- /dev/null +++ b/src/app/components/SharedComponent/Volunteer/index.tsx @@ -0,0 +1,25 @@ +import Link from "next/link"; + +const Volunteer = () => { + return ( +
+
+
+

+ Become a Volunteer +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscelit. Nam malesu dolor sit amet, consectetur adipiscelit. consectetur adipiscelit. Nam malesu dolor. +

+
+ + Donate now + +
+
+
+
+ ) +} + +export default Volunteer; \ No newline at end of file diff --git a/src/app/components/Skeleton/CourseDetail/index.tsx b/src/app/components/Skeleton/CourseDetail/index.tsx new file mode 100644 index 0000000..324329f --- /dev/null +++ b/src/app/components/Skeleton/CourseDetail/index.tsx @@ -0,0 +1,29 @@ +const CourseDetailSkeleton = () => { + return ( + <> +
+
+ +
+
+ +
+
+
+ Loading... +
+
+ + ) +} + +export default CourseDetailSkeleton diff --git a/src/app/components/Skeleton/Mentor/index.tsx b/src/app/components/Skeleton/Mentor/index.tsx new file mode 100644 index 0000000..f9d433b --- /dev/null +++ b/src/app/components/Skeleton/Mentor/index.tsx @@ -0,0 +1,25 @@ +const MentorSkeleton = () => { +return ( + <> +
+
+ +
+
+ Loading... +
+
+ + ) +} + +export default MentorSkeleton \ No newline at end of file diff --git a/src/app/components/Skeleton/Testimonial/index.tsx b/src/app/components/Skeleton/Testimonial/index.tsx new file mode 100644 index 0000000..c50b4a6 --- /dev/null +++ b/src/app/components/Skeleton/Testimonial/index.tsx @@ -0,0 +1,33 @@ +const TestimonialSkeleton = () => { + return ( + <> +
+
+ +
+
+ +
+
+
+
+
+
+
+ Loading... +
+
+ + ) +} + +export default TestimonialSkeleton diff --git a/src/app/favicon.ico b/src/app/favicon.ico new file mode 100644 index 0000000..c53e988 Binary files /dev/null and b/src/app/favicon.ico differ diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..6cfd86f --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,57 @@ +@import 'tailwindcss'; + +@custom-variant dark (&:is(.dark *)); + +@theme { + --shadow-mentor-shadow: 0px 4px 20px rgba(110, 127, 185, 0.1); + + --inset-54\%: 54%; + + --color-primary: #611f69; + --color-cream: #fcf5ef; + --color-success: #6b9f36; + --color-orange: #f9cd92; + + --background-image-banner-image: url('/images/banner/background.png'); + --background-image-newsletter: url('/images/newsletter/hands.svg'); +} + +/* + The default border color has changed to `currentColor` in Tailwind CSS v4, + so we've added these compatibility styles to make sure everything still + looks the same as it did with Tailwind CSS v3. + + If we ever want to remove these styles, we need to add an explicit border + color utility to any element that depends on these defaults. +*/ +@layer base { + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentColor); + } + + section { + @apply py-14 + } + + h1 { + @apply text-black md:text-7xl sm:text-6xl text-5xl + } + + h2 { + @apply text-black sm:text-5xl text-4xl + } +} + +@layer utilities { + .container { + @apply max-w-7xl mx-auto w-full px-4; + } +} + +html { + scroll-behavior: smooth; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..fd7811a --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,26 @@ +import { Inter } from "next/font/google"; +import "./globals.css"; +import Header from "@/app/components/Layout/Header"; +import Footer from "@/app/components/Layout/Footer"; +import ScrollToTop from "@/app/components/ScrollToTop"; +import Aoscompo from "@/utils/aos"; +const font = Inter({ subsets: ["latin"] }); + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + +
+ {children} +