├── public
├── azimov.webp
├── bgimage.webp
└── mainlogo.webp
├── postcss.config.mjs
├── middlewares
└── Fetcher.tsx
├── types
└── RootTypes.tsx
├── next.config.ts
├── app
├── blogs
│ ├── page.tsx
│ └── [title]
│ │ └── page.tsx
├── trainers
│ └── page.tsx
├── services
│ └── page.tsx
├── page.tsx
├── about
│ └── page.tsx
├── layout.tsx
└── globals.css
├── lib
└── utils.ts
├── components
├── shared
│ ├── Title.tsx
│ ├── breadcrumb.tsx
│ ├── footer.tsx
│ ├── header.tsx
│ └── image-gallery.tsx
└── ui
│ ├── button.tsx
│ ├── card.tsx
│ └── sheet.tsx
├── eslint.config.mjs
├── components.json
├── .gitignore
├── tsconfig.json
├── package.json
├── README.md
└── modules
├── About.tsx
├── Hero.tsx
├── Services.tsx
├── Testimonial.tsx
├── Blogs.tsx
└── Trainers.tsx
/public/azimov.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sarbondev/seven-sport-center-client/HEAD/public/azimov.webp
--------------------------------------------------------------------------------
/public/bgimage.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sarbondev/seven-sport-center-client/HEAD/public/bgimage.webp
--------------------------------------------------------------------------------
/public/mainlogo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sarbondev/seven-sport-center-client/HEAD/public/mainlogo.webp
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | const config = {
2 | plugins: ["@tailwindcss/postcss"],
3 | };
4 |
5 | export default config;
6 |
--------------------------------------------------------------------------------
/middlewares/Fetcher.tsx:
--------------------------------------------------------------------------------
1 | // export const BASE_URL = "http://localhost:3000/api";
2 | export const BASE_URL = "https://server.7sportcenter.uz/api";
3 |
4 | export const fetcher = (url: string) =>
5 | fetch(`${BASE_URL}${url}`).then((res) => res.json());
6 |
--------------------------------------------------------------------------------
/types/RootTypes.tsx:
--------------------------------------------------------------------------------
1 | export interface BlogPost {
2 | _id?: string;
3 | photos: string[];
4 | title: string;
5 | description: string;
6 | createdAt?: string;
7 | }
8 |
9 | export interface TrainerMember {
10 | photo: string;
11 | fullName: string;
12 | level: string;
13 | experience: string;
14 | students: string;
15 | }
16 |
--------------------------------------------------------------------------------
/next.config.ts:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images: {
4 | remotePatterns: [
5 | {
6 | protocol: "https",
7 | hostname: "**",
8 | },
9 | {
10 | protocol: "http",
11 | hostname: "**",
12 | },
13 | ],
14 | },
15 | };
16 |
17 | module.exports = nextConfig;
18 |
--------------------------------------------------------------------------------
/app/blogs/page.tsx:
--------------------------------------------------------------------------------
1 | import BlogsModule from "@/modules/Blogs";
2 | import { Metadata } from "next";
3 | import React from "react";
4 |
5 | export const metadata: Metadata = {
6 | title: "BLOGLAR",
7 | description: "Sport yangiliklar.",
8 | };
9 |
10 | function page() {
11 | return (
12 |
13 |
14 |
15 | );
16 | }
17 |
18 | export default page;
19 |
--------------------------------------------------------------------------------
/app/trainers/page.tsx:
--------------------------------------------------------------------------------
1 | import TrainersModule from "@/modules/Trainers";
2 | import { Metadata } from "next";
3 | import React from "react";
4 |
5 | export const metadata: Metadata = {
6 | title: "MURABBIYLAR",
7 | description: "Seven sport center murabbiylar jamoasi.",
8 | };
9 |
10 | function page() {
11 | return (
12 |
13 |
14 |
15 | );
16 | }
17 |
18 | export default page;
19 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
8 | export function formatDate(dateString: string): string {
9 | const date = new Date(dateString);
10 |
11 | return date.toLocaleDateString("uz-UZ", {
12 | day: "2-digit",
13 | month: "short",
14 | year: "numeric",
15 | });
16 | }
17 |
--------------------------------------------------------------------------------
/components/shared/Title.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function Title({
4 | children,
5 | }: Readonly<{
6 | children: React.ReactNode;
7 | }>) {
8 | return (
9 |
10 |
11 |
12 | {children}
13 |
14 |
15 | );
16 | }
17 |
18 | export default Title;
19 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { dirname } from "path";
2 | import { fileURLToPath } from "url";
3 | import { FlatCompat } from "@eslint/eslintrc";
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = dirname(__filename);
7 |
8 | const compat = new FlatCompat({
9 | baseDirectory: __dirname,
10 | });
11 |
12 | const eslintConfig = [
13 | ...compat.extends("next/core-web-vitals", "next/typescript"),
14 | ];
15 |
16 | export default eslintConfig;
17 |
--------------------------------------------------------------------------------
/app/services/page.tsx:
--------------------------------------------------------------------------------
1 | import Services from "@/modules/Services";
2 | import Testimonial from "@/modules/Testimonial";
3 | import { Metadata } from "next";
4 | import React from "react";
5 |
6 | export const metadata: Metadata = {
7 | title: "XIZMATLAR",
8 | description: "BIZ SIZGA NIMALARNI TAKLIF ETAMIZ.",
9 | };
10 |
11 | function page() {
12 | return (
13 |
14 |
15 |
16 |
17 | );
18 | }
19 |
20 | export default page;
21 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "",
8 | "css": "app/globals.css",
9 | "baseColor": "zinc",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "modules": "@/modules",
15 | "components": "@/components",
16 | "utils": "@/lib/utils",
17 | "ui": "@/components/ui",
18 | "lib": "@/lib",
19 | "hooks": "@/hooks"
20 | },
21 | "iconLibrary": "lucide"
22 | }
23 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | import About from "@/modules/About";
2 | import BlogsModule from "@/modules/Blogs";
3 | import Hero from "@/modules/Hero";
4 | import Services from "@/modules/Services";
5 | import Testimonial from "@/modules/Testimonial";
6 | import TrainersModule from "@/modules/Trainers";
7 | import React from "react";
8 |
9 | function Home() {
10 | return (
11 | <>
12 |
13 |
14 |
15 |
16 |
17 |
18 | >
19 | );
20 | }
21 |
22 | export default Home;
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.*
7 | .yarn/*
8 | !.yarn/patches
9 | !.yarn/plugins
10 | !.yarn/releases
11 | !.yarn/versions
12 |
13 | # testing
14 | /coverage
15 |
16 | # next.js
17 | /.next/
18 | /out/
19 |
20 | # production
21 | /build
22 |
23 | # misc
24 | .DS_Store
25 | *.pem
26 |
27 | # debug
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 | .pnpm-debug.log*
32 |
33 | # env files (can opt-in for committing if needed)
34 | .env*
35 |
36 | # vercel
37 | .vercel
38 |
39 | # typescript
40 | *.tsbuildinfo
41 | next-env.d.ts
42 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/app/about/page.tsx:
--------------------------------------------------------------------------------
1 | import About from "@/modules/About";
2 | import Testimonial from "@/modules/Testimonial";
3 | import TrainersModule from "@/modules/Trainers";
4 | import { Metadata } from "next";
5 | import React from "react";
6 |
7 | export const metadata: Metadata = {
8 | title: "BIZ HAQIMIZDA",
9 | description:
10 | "Namanngan shahridagi dzyudo klubi bolalar uchun sport va tarbiyaning mukammal uyg'unligini taqdim etadi. Bizning malakali murabbiylarimiz har bir yosh sportchiga maxsus yondashuv bilan shug'ullanadi.",
11 | };
12 |
13 | function page() {
14 | return (
15 |
16 |
17 |
18 |
19 |
20 | );
21 | }
22 |
23 | export default page;
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev --turbopack",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@radix-ui/react-dialog": "^1.1.6",
13 | "@radix-ui/react-slot": "^1.1.2",
14 | "axios": "^1.8.3",
15 | "class-variance-authority": "^0.7.1",
16 | "clsx": "^2.1.1",
17 | "embla-carousel-react": "^8.5.2",
18 | "lucide-react": "^0.482.0",
19 | "next": "15.2.2",
20 | "react": "^19.0.0",
21 | "react-dom": "^19.0.0",
22 | "swr": "^2.3.3",
23 | "tailwind-merge": "^3.0.2",
24 | "tailwindcss-animate": "^1.0.7"
25 | },
26 | "devDependencies": {
27 | "@eslint/eslintrc": "^3",
28 | "@tailwindcss/postcss": "^4",
29 | "@types/node": "^20",
30 | "@types/react": "^19",
31 | "@types/react-dom": "^19",
32 | "eslint": "^9",
33 | "eslint-config-next": "15.2.2",
34 | "tailwindcss": "^4",
35 | "typescript": "^5"
36 | },
37 | "rules": {
38 | "@typescript-eslint/no-explicit-any": "off"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Judo Club Website
2 |
3 | Этот репозиторий содержит код веб-сайта для дзюдо-клуба. Сайт разработан с использованием современных технологий и предоставляет удобный интерфейс для пользователей.
4 |
5 | ## Технологии
6 |
7 | - **Next.js** - Фреймворк для React, обеспечивающий серверный рендеринг и статическую генерацию страниц.
8 | - **Express.js** - Минималистичный фреймворк для создания серверного API.
9 | - **ShadCN UI & TailwindCSS** - Набор готовых компонентов для стилизации интерфейса.
10 | - **JSON Web Token (JWT)** - Технология для аутентификации и авторизации пользователей.
11 |
12 | ## Установка и запуск
13 |
14 | ### 1. Клонирование репозитория
15 |
16 | ```sh
17 | git clone https://github.com/rakhsrb/seven-sport-center-client
18 | cd seven-sport-center-client
19 | ```
20 |
21 | ### 2. Установка зависимостей
22 |
23 | ```sh
24 | npm install
25 | ```
26 |
27 | ### 3. Запуск проекта
28 |
29 | #### Запуск фронтенда (Next.js)
30 |
31 | ```sh
32 | npm run dev
33 | ```
34 |
35 | #### Запуск бэкенда (Express.js)
36 |
37 | ```sh
38 | npm run server
39 | ```
40 |
41 | ## Контакты
42 |
43 | Если у вас есть вопросы или предложения, свяжитесь с нами!
44 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import Header from "@/components/shared/header";
3 | import Footer from "@/components/shared/footer";
4 | import "./globals.css";
5 |
6 | export const metadata: Metadata = {
7 | title: "Namangan shahridagi dzyudo klubi | Seven Sport Center",
8 | icons: "./mainlogo.webp",
9 | description:
10 | "Seven Sport Center – Namangan shahridagi yetakchi dzyudo klubi. Bolalar uchun mashgulotlar.",
11 | keywords: "дзюдо, спорт, Namangan, дзюдо клуб, тренировки",
12 | openGraph: {
13 | title: "Namangan shahridagi dzyudo klubi | Seven Sport Center",
14 | description:
15 | "Namangandagi eng yaxshi dzyudo klubida mashq qiling! Professional murabbiylar, zamonaviy sharoitlar, qulay jadval.",
16 | url: "https://seven-sport-center-client.vercel.app/",
17 | images: [
18 | {
19 | url: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSSfAq6xh00HGmB98f1qWb0o4V0ZAZfPEpXgsvbMXSylo_k1HWl1CMzOBsywiYYGDpFUmE&usqp=CAU",
20 | width: 1200,
21 | height: 630,
22 | alt: "Дзюдо клуб в Намангане",
23 | },
24 | ],
25 | },
26 | };
27 |
28 | export default function RootLayout({
29 | children,
30 | }: Readonly<{
31 | children: React.ReactNode;
32 | }>) {
33 | return (
34 |
35 |
36 |
37 | {children}
38 |
39 |
40 |
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/modules/About.tsx:
--------------------------------------------------------------------------------
1 | import Title from "@/components/shared/Title";
2 | import Image from "next/image";
3 | import Link from "next/link";
4 |
5 | export default function About() {
6 | return (
7 |
8 |
9 |
10 |
11 |
20 |
21 |
22 |
23 |
DZYUDO KLUBIMIZ HAQIDA
24 |
25 | XAVFSIZ SPORT MUHITI. BOLALARNING JISMONIY VA MA'NAVIY
26 | RIVOJLANISHI UCHUN ENG YAXSHI TANLOV!
27 |
28 |
29 |
30 |
31 | Namanngan shahridagi dzyudo klubi bolalar uchun sport va
32 | tarbiyaning mukammal uyg'unligini taqdim etadi. Bizning malakali
33 | murabbiylarimiz har bir yosh sportchiga maxsus yondashuv bilan
34 | shug'ullanadi.
35 |
36 |
37 |
38 | Dzyudo nafaqat jismoniy tarbiya, balki intizom, hurmat va
39 | irodani shakllantirishga yordam beradi. Klubimizda farzandingiz
40 | sport bilan shug'ullanishi va sog'lom hayot tarzini
41 | shakllantirishi mumkin.
42 |
43 |
44 |
45 |
46 |
50 | Bog'lanish
51 |
52 |
53 |
54 |
55 |
56 |
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
14 | destructive:
15 | "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
16 | outline:
17 | "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
18 | secondary:
19 | "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
20 | ghost:
21 | "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
22 | link: "text-primary underline-offset-4 hover:underline",
23 | },
24 | size: {
25 | default: "h-9 px-4 py-2 has-[>svg]:px-3",
26 | sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
27 | lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
28 | icon: "size-9",
29 | },
30 | },
31 | defaultVariants: {
32 | variant: "default",
33 | size: "default",
34 | },
35 | }
36 | )
37 |
38 | function Button({
39 | className,
40 | variant,
41 | size,
42 | asChild = false,
43 | ...props
44 | }: React.ComponentProps<"button"> &
45 | VariantProps & {
46 | asChild?: boolean
47 | }) {
48 | const Comp = asChild ? Slot : "button"
49 |
50 | return (
51 |
56 | )
57 | }
58 |
59 | export { Button, buttonVariants }
60 |
--------------------------------------------------------------------------------
/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | function Card({ className, ...props }: React.ComponentProps<"div">) {
6 | return (
7 |
15 | )
16 | }
17 |
18 | function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
19 | return (
20 |
28 | )
29 | }
30 |
31 | function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
32 | return (
33 |
38 | )
39 | }
40 |
41 | function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
42 | return (
43 |
48 | )
49 | }
50 |
51 | function CardAction({ className, ...props }: React.ComponentProps<"div">) {
52 | return (
53 |
61 | )
62 | }
63 |
64 | function CardContent({ className, ...props }: React.ComponentProps<"div">) {
65 | return (
66 |
71 | )
72 | }
73 |
74 | function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
75 | return (
76 |
81 | )
82 | }
83 |
84 | export {
85 | Card,
86 | CardHeader,
87 | CardFooter,
88 | CardTitle,
89 | CardAction,
90 | CardDescription,
91 | CardContent,
92 | }
93 |
--------------------------------------------------------------------------------
/app/blogs/[title]/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useSearchParams } from "next/navigation";
4 | import { Breadcrumb } from "@/components/shared/breadcrumb";
5 | import { CalendarIcon } from "lucide-react";
6 | import { formatDate } from "@/lib/utils";
7 | import { ImageGallery } from "@/components/shared/image-gallery";
8 | import { BlogPost } from "@/types/RootTypes";
9 | import { fetcher } from "@/middlewares/Fetcher";
10 | import useSWR from "swr";
11 |
12 | export default function BlogDetail() {
13 | const searchParams = useSearchParams();
14 | const title = searchParams.get("title") || "";
15 | const { data, error, isLoading } = useSWR(
16 | `/blog?title=${encodeURIComponent(title)}`,
17 | fetcher
18 | );
19 |
20 | if (isLoading) {
21 | return (
22 |
23 |
24 |
25 | );
26 | }
27 |
28 | if (error) {
29 | return (
30 |
31 |
Ошибка
32 |
{error}. Попробуйте позже.
33 |
34 | );
35 | }
36 |
37 | return (
38 |
39 |
40 |
41 | {data && (
42 |
43 |
44 |
45 | {data[0].title}
46 |
47 |
48 | {data[0].createdAt && (
49 |
50 |
51 |
52 | {formatDate(data[0].createdAt)}
53 |
54 |
55 | )}
56 |
57 |
58 | {data[0].photos?.length > 0 && (
59 |
60 | )}
61 |
62 |
63 | {data[0].description}
64 |
65 |
66 | )}
67 |
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/modules/Hero.tsx:
--------------------------------------------------------------------------------
1 | import { Medal, Star, Users } from "lucide-react";
2 | import Link from "next/link";
3 | import React from "react";
4 |
5 | function Hero() {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 | O'zbekiston chempionlari tayyorlaymiz
14 |
15 |
16 |
17 |
18 | Farzandingiz uchun
19 | professional dzudo
20 | mashg'ulotlari
21 |
22 |
23 |
24 | Bizning malakali murabbiylarimiz bilan farzandingiz nafaqat sport,
25 | balki intizom, hurmat va maqsadga erishish yo'llarini o'rganadi.
26 |
27 |
28 |
29 |
33 | Hoziroq qo'shiling
34 |
35 |
39 | Batafsil ma'lumot
40 |
41 |
42 |
43 |
44 |
45 |
46 | 500+ o'quvchilar
47 |
48 |
49 |
50 | 15+ yillik tajriba
51 |
52 |
53 |
54 |
55 |
56 | );
57 | }
58 |
59 | export default Hero;
60 |
--------------------------------------------------------------------------------
/components/shared/breadcrumb.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Link from "next/link";
4 | import { ChevronRight } from "lucide-react";
5 | import { cn } from "@/lib/utils";
6 | import { usePathname } from "next/navigation";
7 | import { useMemo } from "react";
8 |
9 | interface BreadcrumbProps extends React.HTMLAttributes {
10 | segments?: {
11 | name: string;
12 | href: string;
13 | isCurrent?: boolean;
14 | }[];
15 | homeName?: string;
16 | segmentNames?: Record;
17 | }
18 |
19 | export function Breadcrumb({
20 | segments: customSegments,
21 | homeName = "Bosh sahifa",
22 | segmentNames = {
23 | blog: "Bloglar",
24 | blogs: "Bloglar",
25 | trainers: "Murabbiylar",
26 | about: "Biz haqimizda",
27 | contact: "Aloqa",
28 | },
29 | className,
30 | ...props
31 | }: BreadcrumbProps) {
32 | const pathname = usePathname();
33 |
34 | const segments = useMemo(() => {
35 | if (customSegments) return customSegments;
36 |
37 | const generatedSegments = [
38 | { name: homeName, href: "/", isCurrent: pathname === "/" },
39 | ];
40 |
41 | if (pathname === "/") return generatedSegments;
42 |
43 | const pathSegments = pathname.split("/").filter(Boolean);
44 |
45 | let currentPath = "";
46 |
47 | pathSegments.forEach((segment, index) => {
48 | currentPath += `/${segment}`;
49 | const isLast = index === pathSegments.length - 1;
50 |
51 | let name = segmentNames[segment] || segment;
52 |
53 | if (segment.includes("%")) {
54 | try {
55 | name = decodeURIComponent(segment);
56 | } catch (e) {
57 | console.error("Failed to decode URL segment:", segment, e);
58 | }
59 | }
60 |
61 | generatedSegments.push({
62 | name,
63 | href: currentPath,
64 | isCurrent: isLast,
65 | });
66 | });
67 |
68 | return generatedSegments;
69 | }, [pathname, customSegments, homeName, segmentNames]);
70 |
71 | return (
72 |
77 |
78 | {segments.map((segment, index) => (
79 |
80 | {index === 0 ? null : (
81 |
82 | )}
83 | {segment.isCurrent ? (
84 |
89 | {segment.name}
90 |
91 | ) : (
92 |
97 | {segment.name}
98 |
99 | )}
100 |
101 | ))}
102 |
103 |
104 | );
105 | }
106 |
--------------------------------------------------------------------------------
/components/shared/footer.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Link from "next/link";
4 | import { Instagram, Send, ArrowUp, MapPin } from "lucide-react";
5 |
6 | export default function Footer() {
7 | const scrollToTop = () => {
8 | window.scrollTo({
9 | top: 0,
10 | behavior: "smooth",
11 | });
12 | };
13 |
14 | const links = [
15 | { label: "Bosh sahifa", url: "/" },
16 | { label: "Biz haqimizda", url: "/about" },
17 | { label: "Xizmatlar", url: "/services" },
18 | { label: "Bloglar", url: "/blogs" },
19 | ];
20 |
21 | return (
22 |
23 |
24 |
25 |
26 |
27 | {links.map((link, index) => (
28 |
29 |
33 | {link.label}
34 |
35 |
36 | ))}
37 |
38 |
39 |
40 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
56 |
57 |
58 |
62 |
63 |
64 |
65 |
66 | +998 88 680 11 44
67 |
68 |
69 |
70 |
71 |
72 | Namangan, “Pahlavon” sport majmuasi
73 |
74 |
79 | xarita
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | © Seven Sport Center {new Date().getFullYear()} Barcha huquqlar
88 | himoyalangan
89 |
90 |
91 | Sayt Texnokarvon tomonidan ishlab chiqilgan
92 |
93 |
94 |
95 |
96 | );
97 | }
98 |
--------------------------------------------------------------------------------
/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SheetPrimitive from "@radix-ui/react-dialog"
5 | import { XIcon } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | function Sheet({ ...props }: React.ComponentProps) {
10 | return
11 | }
12 |
13 | function SheetTrigger({
14 | ...props
15 | }: React.ComponentProps) {
16 | return
17 | }
18 |
19 | function SheetClose({
20 | ...props
21 | }: React.ComponentProps) {
22 | return
23 | }
24 |
25 | function SheetPortal({
26 | ...props
27 | }: React.ComponentProps) {
28 | return
29 | }
30 |
31 | function SheetOverlay({
32 | className,
33 | ...props
34 | }: React.ComponentProps) {
35 | return (
36 |
44 | )
45 | }
46 |
47 | function SheetContent({
48 | className,
49 | children,
50 | side = "right",
51 | ...props
52 | }: React.ComponentProps & {
53 | side?: "top" | "right" | "bottom" | "left"
54 | }) {
55 | return (
56 |
57 |
58 |
74 | {children}
75 |
76 |
77 | Close
78 |
79 |
80 |
81 | )
82 | }
83 |
84 | function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
85 | return (
86 |
91 | )
92 | }
93 |
94 | function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
95 | return (
96 |
101 | )
102 | }
103 |
104 | function SheetTitle({
105 | className,
106 | ...props
107 | }: React.ComponentProps) {
108 | return (
109 |
114 | )
115 | }
116 |
117 | function SheetDescription({
118 | className,
119 | ...props
120 | }: React.ComponentProps) {
121 | return (
122 |
127 | )
128 | }
129 |
130 | export {
131 | Sheet,
132 | SheetTrigger,
133 | SheetClose,
134 | SheetContent,
135 | SheetHeader,
136 | SheetFooter,
137 | SheetTitle,
138 | SheetDescription,
139 | }
140 |
--------------------------------------------------------------------------------
/modules/Services.tsx:
--------------------------------------------------------------------------------
1 | import Title from "@/components/shared/Title";
2 | import { ArrowRight, Clock, Dumbbell, Heart } from "lucide-react";
3 | import Link from "next/link";
4 |
5 | const SERVICES = [
6 | {
7 | icon: Dumbbell,
8 | title: "Professional Trening Uskunalari",
9 | description:
10 | "Bolalarimiz uchun xavfsiz va zamonaviy sport jihozlari bilan jihozlangan zal. Trening samaradorligini oshirish uchun eng yaxshi sharoitlarni taqdim etamiz.",
11 | },
12 | {
13 | icon: Heart,
14 | title: "Sog'lom Turmush Tarzi",
15 | description:
16 | "Dzyudo nafaqat jismoniy kuch, balki sog'lom turmush tarzining asosi. Bolalarning umumiy rivojlanishiga ko'maklashamiz va ularni mustahkam iroda bilan tarbiyalaymiz.",
17 | },
18 | {
19 | icon: Clock,
20 | title: "Samarali Mashg'ulot Dasturlari",
21 | description:
22 | "Har bir bola uchun individual yondashuv! Mashg'ulot dasturlarimiz dzyudo texnikalarini mukammal egallash va sport natijalarini yaxshilashga qaratilgan.",
23 | },
24 | ];
25 |
26 | export default function Services() {
27 | return (
28 |
29 |
30 |
31 |
32 |
33 |
34 | BIZNING XIZMATLARIMIZ
35 |
36 |
37 |
38 |
39 | BIZ SIZGA NIMALARNI TAKLIF ETAMIZ
40 |
41 |
42 |
43 |
44 | {SERVICES.map((service, index) => (
45 |
52 |
55 |
56 |
62 |
63 |
64 | {service.title}
65 |
66 |
67 |
68 | {service.description}
69 |
70 |
71 |
75 | Batafsil
76 |
77 |
78 |
79 | ))}
80 |
81 |
82 |
83 |
87 | Barcha xizmatlarni ko'rish
88 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | );
107 | }
108 |
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,300..900;1,300..900&display=swap");
2 | @import "tailwindcss";
3 |
4 | @plugin "tailwindcss-animate";
5 |
6 | * {
7 | font-family: "Rubik", sans-serif;
8 | }
9 |
10 | @custom-variant dark (&:is(.dark *));
11 |
12 | @theme inline {
13 | --color-background: var(--background);
14 | --color-foreground: var(--foreground);
15 | --font-sans: var(--font-geist-sans);
16 | --font-mono: var(--font-geist-mono);
17 | --color-sidebar-ring: var(--sidebar-ring);
18 | --color-sidebar-border: var(--sidebar-border);
19 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
20 | --color-sidebar-accent: var(--sidebar-accent);
21 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
22 | --color-sidebar-primary: var(--sidebar-primary);
23 | --color-sidebar-foreground: var(--sidebar-foreground);
24 | --color-sidebar: var(--sidebar);
25 | --color-chart-5: var(--chart-5);
26 | --color-chart-4: var(--chart-4);
27 | --color-chart-3: var(--chart-3);
28 | --color-chart-2: var(--chart-2);
29 | --color-chart-1: var(--chart-1);
30 | --color-ring: var(--ring);
31 | --color-input: var(--input);
32 | --color-border: var(--border);
33 | --color-destructive: var(--destructive);
34 | --color-accent-foreground: var(--accent-foreground);
35 | --color-accent: var(--accent);
36 | --color-muted-foreground: var(--muted-foreground);
37 | --color-muted: var(--muted);
38 | --color-secondary-foreground: var(--secondary-foreground);
39 | --color-secondary: var(--secondary);
40 | --color-primary-foreground: var(--primary-foreground);
41 | --color-primary: var(--primary);
42 | --color-popover-foreground: var(--popover-foreground);
43 | --color-popover: var(--popover);
44 | --color-card-foreground: var(--card-foreground);
45 | --color-card: var(--card);
46 | --radius-sm: calc(var(--radius) - 4px);
47 | --radius-md: calc(var(--radius) - 2px);
48 | --radius-lg: var(--radius);
49 | --radius-xl: calc(var(--radius) + 4px);
50 | }
51 |
52 | :root {
53 | --radius: 0.625rem;
54 | --background: oklch(1 0 0);
55 | --foreground: oklch(0.141 0.005 285.823);
56 | --card: oklch(1 0 0);
57 | --card-foreground: oklch(0.141 0.005 285.823);
58 | --popover: oklch(1 0 0);
59 | --popover-foreground: oklch(0.141 0.005 285.823);
60 | --primary: oklch(0.21 0.006 285.885);
61 | --primary-foreground: oklch(0.985 0 0);
62 | --secondary: oklch(0.967 0.001 286.375);
63 | --secondary-foreground: oklch(0.21 0.006 285.885);
64 | --muted: oklch(0.967 0.001 286.375);
65 | --muted-foreground: oklch(0.552 0.016 285.938);
66 | --accent: oklch(0.967 0.001 286.375);
67 | --accent-foreground: oklch(0.21 0.006 285.885);
68 | --destructive: oklch(0.577 0.245 27.325);
69 | --border: oklch(0.92 0.004 286.32);
70 | --input: oklch(0.92 0.004 286.32);
71 | --ring: oklch(0.705 0.015 286.067);
72 | --chart-1: oklch(0.646 0.222 41.116);
73 | --chart-2: oklch(0.6 0.118 184.704);
74 | --chart-3: oklch(0.398 0.07 227.392);
75 | --chart-4: oklch(0.828 0.189 84.429);
76 | --chart-5: oklch(0.769 0.188 70.08);
77 | --sidebar: oklch(0.985 0 0);
78 | --sidebar-foreground: oklch(0.141 0.005 285.823);
79 | --sidebar-primary: oklch(0.21 0.006 285.885);
80 | --sidebar-primary-foreground: oklch(0.985 0 0);
81 | --sidebar-accent: oklch(0.967 0.001 286.375);
82 | --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
83 | --sidebar-border: oklch(0.92 0.004 286.32);
84 | --sidebar-ring: oklch(0.705 0.015 286.067);
85 | }
86 |
87 | .dark {
88 | --background: oklch(0.141 0.005 285.823);
89 | --foreground: oklch(0.985 0 0);
90 | --card: oklch(0.21 0.006 285.885);
91 | --card-foreground: oklch(0.985 0 0);
92 | --popover: oklch(0.21 0.006 285.885);
93 | --popover-foreground: oklch(0.985 0 0);
94 | --primary: oklch(0.92 0.004 286.32);
95 | --primary-foreground: oklch(0.21 0.006 285.885);
96 | --secondary: oklch(0.274 0.006 286.033);
97 | --secondary-foreground: oklch(0.985 0 0);
98 | --muted: oklch(0.274 0.006 286.033);
99 | --muted-foreground: oklch(0.705 0.015 286.067);
100 | --accent: oklch(0.274 0.006 286.033);
101 | --accent-foreground: oklch(0.985 0 0);
102 | --destructive: oklch(0.704 0.191 22.216);
103 | --border: oklch(1 0 0 / 10%);
104 | --input: oklch(1 0 0 / 15%);
105 | --ring: oklch(0.552 0.016 285.938);
106 | --chart-1: oklch(0.488 0.243 264.376);
107 | --chart-2: oklch(0.696 0.17 162.48);
108 | --chart-3: oklch(0.769 0.188 70.08);
109 | --chart-4: oklch(0.627 0.265 303.9);
110 | --chart-5: oklch(0.645 0.246 16.439);
111 | --sidebar: oklch(0.21 0.006 285.885);
112 | --sidebar-foreground: oklch(0.985 0 0);
113 | --sidebar-primary: oklch(0.488 0.243 264.376);
114 | --sidebar-primary-foreground: oklch(0.985 0 0);
115 | --sidebar-accent: oklch(0.274 0.006 286.033);
116 | --sidebar-accent-foreground: oklch(0.985 0 0);
117 | --sidebar-border: oklch(1 0 0 / 10%);
118 | --sidebar-ring: oklch(0.552 0.016 285.938);
119 | }
120 |
121 | @layer base {
122 | * {
123 | @apply border-border outline-ring/50;
124 | }
125 | body {
126 | @apply bg-background text-foreground;
127 | }
128 | }
129 |
130 | .hero {
131 | background: linear-gradient(30deg, rgba(0, 0, 0, 0.557), rgba(0, 0, 0, 0.677)),
132 | url("../public/bgimage.webp");
133 | background-size: cover;
134 | background-repeat: no-repeat;
135 | background-position: center;
136 | }
--------------------------------------------------------------------------------
/components/shared/header.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Sheet,
5 | SheetContent,
6 | SheetTrigger,
7 | SheetTitle,
8 | } from "@/components/ui/sheet";
9 | import { Menu } from "lucide-react";
10 | import Image from "next/image";
11 | import Link from "next/link";
12 | import { usePathname } from "next/navigation";
13 | import { useEffect, useState } from "react";
14 |
15 | const links = [
16 | { label: "BOSH SAHIFA", url: "/" },
17 | { label: "BIZ HAQIMIZDA", url: "/about" },
18 | { label: "XIZMATLAR", url: "/services" },
19 | { label: "BLOGLAR", url: "/blogs" },
20 | ];
21 |
22 | function Header() {
23 | const pathname = usePathname();
24 | const [isOpen, setIsOpen] = useState(false);
25 | const [prevScrollPos, setPrevScrollPos] = useState(0);
26 | const [visible, setVisible] = useState(true);
27 | const [scrollY, setScrollY] = useState(0);
28 |
29 | useEffect(() => {
30 | setScrollY(window.pageYOffset);
31 |
32 | const handleScroll = () => {
33 | const currentScrollPos = window.pageYOffset;
34 | setVisible(prevScrollPos > currentScrollPos || currentScrollPos < 10);
35 | setPrevScrollPos(currentScrollPos);
36 | setScrollY(currentScrollPos);
37 | };
38 |
39 | window.addEventListener("scroll", handleScroll);
40 | return () => window.removeEventListener("scroll", handleScroll);
41 | }, [prevScrollPos]);
42 |
43 | const headerStyle = {
44 | top: visible ? "0" : "-100px",
45 | };
46 |
47 | return (
48 | = 200 ? "bg-black" : "bg-transparent"
51 | }`}
52 | style={headerStyle}
53 | >
54 |
55 |
59 |
65 |
66 |
67 |
68 |
69 |
70 | {links.map((item, index) => (
71 |
72 |
76 | {item.label}
77 |
78 |
79 | ))}
80 |
81 |
82 |
86 | +998 88 680 11 44
87 |
88 |
89 |
90 |
91 |
92 |
93 |
97 |
98 |
99 |
100 |
104 |
110 |
111 |
112 |
113 |
114 | Mobile Menu
115 |
116 | {links.map((item, index) => (
117 |
118 | setIsOpen(false)}
122 | >
123 | {item.label}
124 |
125 |
126 | ))}
127 |
128 |
129 |
130 |
134 | +998 88 680 11 44
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 | );
144 | }
145 |
146 | export default Header;
147 |
--------------------------------------------------------------------------------
/components/shared/image-gallery.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useState } from "react";
4 | import Image from "next/image";
5 | import { X, ChevronLeft, ChevronRight, ZoomIn } from "lucide-react";
6 | import { cn } from "@/lib/utils";
7 |
8 | interface ImageGalleryProps {
9 | images: string[];
10 | alt: string;
11 | }
12 |
13 | export function ImageGallery({ images, alt }: ImageGalleryProps) {
14 | const [isOpen, setIsOpen] = useState(false);
15 | const [currentIndex, setCurrentIndex] = useState(0);
16 |
17 | const openLightbox = (index: number) => {
18 | setCurrentIndex(index);
19 | setIsOpen(true);
20 | document.body.style.overflow = "hidden"; // Prevent scrolling when lightbox is open
21 | };
22 |
23 | const closeLightbox = () => {
24 | setIsOpen(false);
25 | document.body.style.overflow = ""; // Restore scrolling
26 | };
27 |
28 | const goToPrevious = (e: React.MouseEvent) => {
29 | e.stopPropagation();
30 | setCurrentIndex((prev) => (prev === 0 ? images.length - 1 : prev - 1));
31 | };
32 |
33 | const goToNext = (e: React.MouseEvent) => {
34 | e.stopPropagation();
35 | setCurrentIndex((prev) => (prev === images.length - 1 ? 0 : prev + 1));
36 | };
37 |
38 | // Handle keyboard navigation
39 | const handleKeyDown = (e: React.KeyboardEvent) => {
40 | if (e.key === "Escape") closeLightbox();
41 | if (e.key === "ArrowLeft")
42 | setCurrentIndex((prev) => (prev === 0 ? images.length - 1 : prev - 1));
43 | if (e.key === "ArrowRight")
44 | setCurrentIndex((prev) => (prev === images.length - 1 ? 0 : prev + 1));
45 | };
46 |
47 | if (!images || images.length === 0) {
48 | return null;
49 | }
50 |
51 | return (
52 | <>
53 |
54 |
openLightbox(0)}
57 | >
58 |
66 |
71 |
72 |
73 | {images.length > 1 && (
74 |
75 | {images.map((image, index) => (
76 |
openLightbox(index)}
83 | >
84 |
91 |
92 | ))}
93 |
94 | )}
95 |
96 |
97 | {isOpen && (
98 |
104 |
109 |
110 |
111 |
112 | {images.length > 1 && (
113 | <>
114 |
119 |
120 |
121 |
126 |
127 |
128 | >
129 | )}
130 |
131 |
132 | {currentIndex + 1} / {images.length}
133 |
134 |
135 |
136 |
144 |
145 |
146 | )}
147 | >
148 | );
149 | }
150 |
--------------------------------------------------------------------------------
/modules/Testimonial.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useCallback, useEffect, useState } from "react";
4 | import { Quote, ChevronLeft, ChevronRight } from "lucide-react";
5 | import Image from "next/image";
6 | import useEmblaCarousel from "embla-carousel-react";
7 | import { cn } from "@/lib/utils";
8 |
9 | const testimonials = [
10 | {
11 | id: 1,
12 | name: "Abdulaziz Karimov",
13 | role: "Ota",
14 | quote:
15 | "O'g'lim 2 yildan beri bu klubda shug'ullanadi. Jismoniy rivojlanish bilan birga, intizom va hurmat kabi muhim fazilatlarni ham o'rgandi.",
16 | avatar:
17 | "https://upload.wikimedia.org/wikipedia/commons/8/8b/Valeriy_Konovalyuk_3x4.jpg",
18 | },
19 | {
20 | id: 2,
21 | name: "Dilnoza Ahmedova",
22 | role: "Ona",
23 | quote:
24 | "Murabbiylar juda malakali va bolalarga g'amxo'rlik bilan yondashadi. Qizim bu yerda nafaqat sport, balki hayotiy ko'nikmalarni ham o'rganmoqda.",
25 | avatar:
26 | "https://passport-photo.online/_optimized/prepare2.0498e1e2-opt-1920.WEBP",
27 | },
28 | {
29 | id: 3,
30 | name: "Jahongir Toshmatov",
31 | role: "Sobiq o'quvchi",
32 | quote:
33 | "Bu klubda olgan bilim va tajribam tufayli bugun milliy terma jamoada faoliyat yuritmoqdaman. Eng yaxshi boshlang'ich ta'lim uchun minnatdorman.",
34 | avatar:
35 | "https://s3.eu-west-2.amazonaws.com/static-candidates.democracyclub.org.uk/media/cache/29/9e/299e3c6f9f3b4e0278f7d2dc2b9134ef.jpg",
36 | },
37 | ];
38 |
39 | function Testimonial() {
40 | const [selectedIndex, setSelectedIndex] = useState(0);
41 | const [emblaRef, emblaApi] = useEmblaCarousel({
42 | loop: true,
43 | align: "center",
44 | skipSnaps: false,
45 | });
46 |
47 | const scrollPrev = useCallback(() => {
48 | if (emblaApi) emblaApi.scrollPrev();
49 | }, [emblaApi]);
50 |
51 | const scrollNext = useCallback(() => {
52 | if (emblaApi) emblaApi.scrollNext();
53 | }, [emblaApi]);
54 |
55 | const scrollTo = useCallback(
56 | (index: number) => {
57 | if (emblaApi) emblaApi.scrollTo(index);
58 | },
59 | [emblaApi]
60 | );
61 |
62 | const onSelect = useCallback(() => {
63 | if (!emblaApi) return;
64 | setSelectedIndex(emblaApi.selectedScrollSnap());
65 | }, [emblaApi]);
66 |
67 | useEffect(() => {
68 | if (!emblaApi) return;
69 |
70 | onSelect();
71 | emblaApi.on("select", onSelect);
72 |
73 | const autoplay = setInterval(() => {
74 | if (emblaApi.canScrollNext()) {
75 | emblaApi.scrollNext();
76 | } else {
77 | emblaApi.scrollTo(0);
78 | }
79 | }, 5000);
80 |
81 | return () => {
82 | emblaApi.off("select", onSelect);
83 | clearInterval(autoplay);
84 | };
85 | }, [emblaApi, onSelect]);
86 |
87 | return (
88 |
89 |
90 |
91 |
92 |
93 |
94 | BIZNING MUXLISLAR FIKRI
95 |
96 |
97 |
98 |
99 |
100 | Ota-onalar va o'quvchilar bizning klubimiz haqida nima
101 | deyishadi
102 |
103 |
104 |
105 |
106 |
107 |
108 | {testimonials.map((testimonial) => (
109 |
113 |
114 |
115 |
116 |
123 |
124 |
125 |
126 | {testimonial.name}
127 |
128 |
{testimonial.role}
129 |
130 |
131 |
132 |
133 |
134 |
135 | {testimonial.quote}
136 |
137 |
138 |
139 |
140 | ))}
141 |
142 |
143 |
144 |
149 |
150 |
151 |
152 |
157 |
158 |
159 |
160 |
161 |
162 | {testimonials.map((_, index) => (
163 | scrollTo(index)}
166 | className={cn(
167 | "w-3 h-3 rounded-full transition-all duration-300",
168 | index === selectedIndex ? "bg-red-600 w-6" : "bg-gray-300"
169 | )}
170 | aria-label={`Go to testimonial ${index + 1}`}
171 | />
172 | ))}
173 |
174 |
175 |
176 | );
177 | }
178 |
179 | export default Testimonial;
180 |
--------------------------------------------------------------------------------
/modules/Blogs.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { fetcher } from "@/middlewares/Fetcher";
4 | import { BlogPost } from "@/types/RootTypes";
5 | import Image from "next/image";
6 | import Link from "next/link";
7 | import useSWR from "swr";
8 |
9 | export default function BlogsModule() {
10 | const { data, error, isLoading } = useSWR("/blog", fetcher);
11 |
12 | if (isLoading) return ;
13 |
14 | const renderHeader = () => (
15 |
16 |
17 |
18 |
19 | YANGILIKLAR
20 |
21 |
22 |
23 |
24 | DZYUDO BO'YICHA ENG SO'NGGI YANGILIKLAR VA FOYDALI MASLAHATLAR
25 |
26 |
27 | );
28 |
29 | if (error || !data || data.length === 0) {
30 | return (
31 |
32 |
33 | {renderHeader()}
34 | {error ? (
35 |
36 |
37 | Ma'lumotlarni yuklashda xatolik yuz berdi
38 |
39 |
40 | Iltimos, keyinroq qayta urinib ko'ring yoki administrator bilan
41 | bog'laning.
42 |
43 |
44 | ) : (
45 |
46 |
Hech qanday maqola topilmadi
47 |
48 | )}
49 |
50 |
51 | );
52 | }
53 |
54 | return (
55 |
56 | {renderHeader()}
57 |
58 | {data.map((blog, index) => (
59 |
63 |
64 |
65 |
73 |
74 |
75 |
76 | {blog.createdAt && (
77 |
78 | {new Date(blog.createdAt).toLocaleDateString()}
79 |
80 | )}
81 |
82 |
86 | {blog.title}
87 |
88 |
89 |
90 | {blog.description}
91 |
92 |
96 | Batafsil
97 |
109 |
110 |
111 |
112 |
113 |
114 | ))}
115 |
116 |
117 |
118 |
122 | Barcha maqolalarni ko'rish
123 |
135 |
136 |
137 |
138 |
139 |
140 | );
141 | }
142 |
143 | function BlogsModuleSkeleton() {
144 | return (
145 |
146 |
147 |
148 |
149 |
150 | YANGILIKLAR
151 |
152 |
153 |
154 |
155 | DZYUDO BO'YICHA ENG SO'NGGI YANGILIKLAR VA FOYDALI
156 | MASLAHATLAR
157 |
158 |
159 |
160 |
161 | {Array.from({ length: 4 }).map((_, index) => (
162 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 | ))}
175 |
176 |
177 |
180 |
181 | );
182 | }
183 |
--------------------------------------------------------------------------------
/modules/Trainers.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Title from "@/components/shared/Title";
4 | import { fetcher } from "@/middlewares/Fetcher";
5 | import type { TrainerMember } from "@/types/RootTypes";
6 | import useEmblaCarousel from "embla-carousel-react";
7 | import { Award, ChevronLeft, ChevronRight, Clock, Users } from "lucide-react";
8 | import Image from "next/image";
9 | import Link from "next/link";
10 | import { useCallback, useEffect, useState } from "react";
11 | import useSWR from "swr";
12 |
13 | export default function TrainersModule() {
14 | const { data, error, isLoading } = useSWR(
15 | `/trainer`,
16 | fetcher
17 | );
18 |
19 | const [emblaRef, emblaApi] = useEmblaCarousel({
20 | loop: true,
21 | align: "start",
22 | slidesToScroll: 1,
23 | });
24 |
25 | const [canScrollPrev, setCanScrollPrev] = useState(false);
26 | const [canScrollNext, setCanScrollNext] = useState(true);
27 |
28 | const onSelect = useCallback(() => {
29 | if (!emblaApi) return;
30 | setCanScrollPrev(emblaApi.canScrollPrev());
31 | setCanScrollNext(emblaApi.canScrollNext());
32 | }, [emblaApi]);
33 |
34 | useEffect(() => {
35 | if (!emblaApi) return;
36 |
37 | emblaApi.on("select", onSelect);
38 | onSelect();
39 |
40 | return () => {
41 | emblaApi.off("select", onSelect);
42 | };
43 | }, [emblaApi, onSelect]);
44 |
45 | const scrollPrev = () => emblaApi && emblaApi.scrollPrev();
46 | const scrollNext = () => emblaApi && emblaApi.scrollNext();
47 |
48 | return (
49 |
50 |
51 |
52 |
53 |
BIZNING JAMOA
54 |
55 | ENG TAJRIBALI MURABBIYLARIMIZ
56 |
57 |
58 |
59 | {isLoading ? (
60 |
63 | ) : error ? (
64 |
65 |
66 | Xatolik yuz berdi. Qayta urinib ko'ring.
67 |
68 |
69 | ) : (
70 |
71 |
81 |
82 |
83 |
93 |
94 |
95 |
99 | Batafsil ma'lumotlar
100 |
112 |
113 |
114 |
115 |
116 | )}
117 |
118 |
119 | {isLoading ? (
120 |
121 | {[1, 2, 3].map((i) => (
122 |
130 | ))}
131 |
132 | ) : error ? (
133 |
134 |
135 | Ma'lumotlarni yuklashda xatolik yuz berdi
136 |
137 |
138 | Iltimos, keyinroq qayta urinib ko'ring yoki administrator bilan
139 | bog'laning.
140 |
141 |
142 | ) : data && data.length > 0 ? (
143 |
144 |
145 | {data.map((member, index) => (
146 |
150 |
151 |
152 |
160 |
161 |
162 |
163 |
164 | {member.fullName}
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 | Tajriba
174 |
175 |
176 |
177 | {member.experience} Yil
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 | Darajasi
186 |
187 |
188 |
189 | {member.level} belbog
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 | Shogirtlar
198 |
199 |
200 |
201 | {member.students}
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 | ))}
210 |
211 |
212 | ) : (
213 |
214 |
Hozircha murabbiylar mavjud emas.
215 |
216 | )}
217 |
218 |
219 | );
220 | }
221 |
--------------------------------------------------------------------------------