update: add custom-components and make home page
This commit is contained in:
parent
477d311213
commit
ceef7c7efc
@ -10,12 +10,13 @@
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"componentPath": "src/shared/shad-cn",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
"components": "@/shared",
|
||||
"utils": "@/shared/lib/utils",
|
||||
"ui": "@/shared/shad-cn",
|
||||
"lib": "@/shared/lib",
|
||||
"hooks": "@/shared/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
@ -10,7 +10,7 @@ import globals from 'globals';
|
||||
import TS_ESLint from 'typescript-eslint';
|
||||
|
||||
export default TS_ESLint.config(
|
||||
{ ignores: ['dist'] },
|
||||
{ ignores: ['.next'] },
|
||||
{
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
@ -33,11 +33,7 @@ export default TS_ESLint.config(
|
||||
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
project: [
|
||||
'./tsconfig.app.json',
|
||||
'./tsconfig.json',
|
||||
'./tsconfig.node.json',
|
||||
],
|
||||
project: ['./tsconfig.json'],
|
||||
projectService: true,
|
||||
sourceType: 'module',
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
@ -50,9 +46,7 @@ export default TS_ESLint.config(
|
||||
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
// 'react-refresh': reactRefresh,
|
||||
'unused-imports': unusedImports,
|
||||
// 'react-compiler': reactCompiler,
|
||||
react,
|
||||
},
|
||||
|
||||
@ -69,7 +63,7 @@ export default TS_ESLint.config(
|
||||
|
||||
ignores: [
|
||||
'.prettierrc.cjs',
|
||||
'dist',
|
||||
'.next',
|
||||
'postcss.config.js',
|
||||
'tailwind.config.ts',
|
||||
],
|
||||
|
||||
5461
package-lock.json
generated
5461
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,7 @@
|
||||
"@hookform/resolvers": "^5.0.1",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.11",
|
||||
"@radix-ui/react-slot": "^1.2.0",
|
||||
"@radix-ui/react-tabs": "^1.1.8",
|
||||
"@reduxjs/toolkit": "^2.7.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
@ -22,6 +23,8 @@
|
||||
"react-dom": "^19.0.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tw-animate-css": "^1.2.6",
|
||||
"zod": "^3.24.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
52
pnpm-lock.yaml
generated
52
pnpm-lock.yaml
generated
@ -17,6 +17,9 @@ importers:
|
||||
'@radix-ui/react-slot':
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0(@types/react@19.1.2)(react@19.1.0)
|
||||
'@radix-ui/react-tabs':
|
||||
specifier: ^1.1.8
|
||||
version: 1.1.8(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@reduxjs/toolkit':
|
||||
specifier: ^2.7.0
|
||||
version: 2.7.0(react-redux@9.2.0(@types/react@19.1.2)(react@19.1.0)(redux@5.0.1))(react@19.1.0)
|
||||
@ -47,6 +50,12 @@ importers:
|
||||
tailwind-merge:
|
||||
specifier: ^3.2.0
|
||||
version: 3.2.0
|
||||
tailwindcss-animate:
|
||||
specifier: ^1.0.7
|
||||
version: 1.0.7(tailwindcss@4.1.4)
|
||||
tw-animate-css:
|
||||
specifier: ^1.2.6
|
||||
version: 1.2.6
|
||||
zod:
|
||||
specifier: ^3.24.3
|
||||
version: 3.24.3
|
||||
@ -646,6 +655,19 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-tabs@1.1.8':
|
||||
resolution: {integrity: sha512-4iUaN9SYtG+/E+hJ7jRks/Nv90f+uAsRHbLYA6BcA9EsR6GNWgsvtS4iwU2SP0tOZfDGAyqIT0yz7ckgohEIFA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.1':
|
||||
resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
|
||||
peerDependencies:
|
||||
@ -2271,6 +2293,11 @@ packages:
|
||||
tailwind-merge@3.2.0:
|
||||
resolution: {integrity: sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==}
|
||||
|
||||
tailwindcss-animate@1.0.7:
|
||||
resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0 || insiders'
|
||||
|
||||
tailwindcss@4.1.4:
|
||||
resolution: {integrity: sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==}
|
||||
|
||||
@ -2298,6 +2325,9 @@ packages:
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
tw-animate-css@1.2.6:
|
||||
resolution: {integrity: sha512-/DSl24Y1WNdtEWA187h3M5ixwvucje2DH2/Qi8N1plNn0Mb0O1E6F9trXknwzZbtVJCdnogaWLt45xQZOrKtpw==}
|
||||
|
||||
type-check@0.4.0:
|
||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@ -2882,6 +2912,22 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.2
|
||||
|
||||
'@radix-ui/react-tabs@1.1.8(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.2
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||
'@radix-ui/react-presence': 1.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-roving-focus': 1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.2)(react@19.1.0)
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.2
|
||||
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.2)(react@19.1.0)':
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
@ -4627,6 +4673,10 @@ snapshots:
|
||||
|
||||
tailwind-merge@3.2.0: {}
|
||||
|
||||
tailwindcss-animate@1.0.7(tailwindcss@4.1.4):
|
||||
dependencies:
|
||||
tailwindcss: 4.1.4
|
||||
|
||||
tailwindcss@4.1.4: {}
|
||||
|
||||
tapable@2.2.1: {}
|
||||
@ -4653,6 +4703,8 @@ snapshots:
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
tw-animate-css@1.2.6: {}
|
||||
|
||||
type-check@0.4.0:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
@import 'tailwindcss';
|
||||
/* @import "tw-animate-css"; */
|
||||
@import "tw-animate-css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@ -45,71 +45,71 @@
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.141 0.005 285.823);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.141 0.005 285.823);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.141 0.005 285.823);
|
||||
--primary: oklch(0.21 0.006 285.885);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.967 0.001 286.375);
|
||||
--secondary-foreground: oklch(0.21 0.006 285.885);
|
||||
--muted: oklch(0.967 0.001 286.375);
|
||||
--muted-foreground: oklch(0.552 0.016 285.938);
|
||||
--accent: oklch(0.967 0.001 286.375);
|
||||
--accent-foreground: oklch(0.21 0.006 285.885);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.92 0.004 286.32);
|
||||
--input: oklch(0.92 0.004 286.32);
|
||||
--ring: oklch(0.705 0.015 286.067);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
||||
--sidebar-primary: oklch(0.21 0.006 285.885);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.967 0.001 286.375);
|
||||
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
||||
--sidebar-border: oklch(0.92 0.004 286.32);
|
||||
--sidebar-ring: oklch(0.705 0.015 286.067);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.141 0.005 285.823);
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.21 0.006 285.885);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.21 0.006 285.885);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.92 0.004 286.32);
|
||||
--primary-foreground: oklch(0.21 0.006 285.885);
|
||||
--secondary: oklch(0.274 0.006 286.033);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.274 0.006 286.033);
|
||||
--muted-foreground: oklch(0.705 0.015 286.067);
|
||||
--accent: oklch(0.274 0.006 286.033);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.552 0.016 285.938);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.21 0.006 285.885);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.274 0.006 286.033);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.552 0.016 285.938);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
||||
@ -1,23 +1,16 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { Geist, Geist_Mono } from 'next/font/google';
|
||||
import { Geist, Geist_Mono, Inter } from 'next/font/google';
|
||||
|
||||
import { Providers } from '@/shared/providers/providers';
|
||||
|
||||
import './globals.css';
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: '--font-geist-sans',
|
||||
subsets: ['latin'],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: '--font-geist-mono',
|
||||
subsets: ['latin'],
|
||||
});
|
||||
const inter = Inter({ subsets: ['latin', 'cyrillic'] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Create Next App',
|
||||
description: 'Generated by create next app',
|
||||
title: 'GasNetwork - Сеть заправок в Таджикистане',
|
||||
description:
|
||||
'Качественное топливо, удобное расположение и отличный сервис для наших клиентов',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
@ -27,9 +20,7 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang='en' suppressHydrationWarning>
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<body className={`${inter.className} antialiased`}>
|
||||
<Providers>{children}</Providers>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
740
src/app/page.tsx
740
src/app/page.tsx
@ -1,104 +1,676 @@
|
||||
import { ModeToggle } from "@/shared/theme/theme-toggle";
|
||||
import Image from "next/image";
|
||||
import {
|
||||
ArrowRight,
|
||||
Briefcase,
|
||||
ChevronRight,
|
||||
Fuel,
|
||||
Gift,
|
||||
Handshake,
|
||||
Heart,
|
||||
Mail,
|
||||
MapPin,
|
||||
Phone,
|
||||
Users,
|
||||
} from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
import AboutCounter from '@/shared/components/about-counter';
|
||||
import GasStationMap from '@/shared/components/gas-station-map';
|
||||
import PromotionSlider from '@/shared/components/promotion-slider';
|
||||
import StatsSection from '@/shared/components/stats-section';
|
||||
import { Button } from '@/shared/shadcn-ui/button';
|
||||
import { Card, CardContent } from '@/shared/shadcn-ui/card';
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from '@/shared/shadcn-ui/tabs';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
|
||||
<div className='flex min-h-screen flex-col'>
|
||||
{/* Header */}
|
||||
<header className='sticky top-0 z-40 w-full border-b bg-white'>
|
||||
<div className='container flex h-16 items-center justify-between py-4'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Fuel className='h-6 w-6 text-red-600' />
|
||||
<span className='text-xl font-bold'>GasNetwork</span>
|
||||
</div>
|
||||
<nav className='hidden items-center gap-6 md:flex'>
|
||||
<Link
|
||||
href='#stations'
|
||||
className='text-sm font-medium transition-colors hover:text-red-600'
|
||||
>
|
||||
Наши заправки
|
||||
</Link>
|
||||
<Link
|
||||
href='#about'
|
||||
className='text-sm font-medium transition-colors hover:text-red-600'
|
||||
>
|
||||
О нас
|
||||
</Link>
|
||||
<Link
|
||||
href='#vacancies'
|
||||
className='text-sm font-medium transition-colors hover:text-red-600'
|
||||
>
|
||||
Вакансии
|
||||
</Link>
|
||||
<Link
|
||||
href='#promotions'
|
||||
className='text-sm font-medium transition-colors hover:text-red-600'
|
||||
>
|
||||
Акции
|
||||
</Link>
|
||||
<Link
|
||||
href='#partners'
|
||||
className='text-sm font-medium transition-colors hover:text-red-600'
|
||||
>
|
||||
Партнеры
|
||||
</Link>
|
||||
<Link
|
||||
href='#charity'
|
||||
className='text-sm font-medium transition-colors hover:text-red-600'
|
||||
>
|
||||
Благотворительность
|
||||
</Link>
|
||||
</nav>
|
||||
<div className='flex items-center gap-4'>
|
||||
<Button variant='outline' size='sm' className='hidden md:flex'>
|
||||
TJ
|
||||
</Button>
|
||||
<Button variant='outline' size='sm' className='hidden md:flex'>
|
||||
RU
|
||||
</Button>
|
||||
<Button className='bg-red-600 hover:bg-red-700'>Контакты</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className='flex-1'>
|
||||
{/* Hero Section */}
|
||||
<section className='relative'>
|
||||
<div className='relative h-[500px] w-full overflow-hidden'>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
src='/placeholder.svg?height=500&width=1920'
|
||||
alt='Gas Station Network'
|
||||
width={1920}
|
||||
height={500}
|
||||
className='object-cover'
|
||||
priority
|
||||
/>
|
||||
<ModeToggle/>
|
||||
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||
<li className="mb-2 tracking-[-.01em]">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
|
||||
src/app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li className="tracking-[-.01em]">
|
||||
Save and see your changes instantly.
|
||||
</li>
|
||||
</ol>
|
||||
<div className='absolute inset-0 flex items-center bg-gradient-to-r from-black/70 to-black/30'>
|
||||
<div className='container'>
|
||||
<div className='max-w-lg space-y-4 text-white'>
|
||||
<h1 className='text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl'>
|
||||
Сеть современных заправок в Таджикистане
|
||||
</h1>
|
||||
<p className='text-lg text-gray-200'>
|
||||
Качественное топливо, удобное расположение и отличный сервис
|
||||
для наших клиентов
|
||||
</p>
|
||||
<div className='flex gap-4'>
|
||||
<Button className='bg-red-600 hover:bg-red-700'>
|
||||
Найти заправку <MapPin className='ml-2 h-4 w-4' />
|
||||
</Button>
|
||||
<Button
|
||||
variant='outline'
|
||||
className='border-white text-white hover:bg-white/10'
|
||||
>
|
||||
Узнать больше
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
{/* Stats Section */}
|
||||
<StatsSection />
|
||||
|
||||
{/* Map Section */}
|
||||
<section id='stations' className='bg-gray-50 py-16'>
|
||||
<div className='container'>
|
||||
<div className='mb-12 flex flex-col items-center justify-center text-center'>
|
||||
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
|
||||
<MapPin className='h-6 w-6 text-red-600' />
|
||||
</div>
|
||||
<h2 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl'>
|
||||
Наши заправки
|
||||
</h2>
|
||||
<p className='max-w-2xl text-gray-600'>
|
||||
Найдите ближайшую к вам заправку нашей сети. Мы расположены в
|
||||
удобных местах по всему Таджикистану.
|
||||
</p>
|
||||
</div>
|
||||
<div className='h-[500px] overflow-hidden rounded-xl border shadow-lg'>
|
||||
<GasStationMap />
|
||||
</div>
|
||||
<div className='mt-8 flex justify-center'>
|
||||
<Button className='bg-red-600 hover:bg-red-700'>
|
||||
Показать все заправки <ChevronRight className='ml-2 h-4 w-4' />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* About Section */}
|
||||
<section id='about' className='py-16'>
|
||||
<div className='container'>
|
||||
<div className='grid items-center gap-12 md:grid-cols-2'>
|
||||
<div>
|
||||
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
|
||||
<Users className='h-6 w-6 text-red-600' />
|
||||
</div>
|
||||
<h2 className='mb-6 text-3xl font-bold tracking-tight sm:text-4xl'>
|
||||
О нашей компании
|
||||
</h2>
|
||||
<p className='mb-6 text-gray-600'>
|
||||
Наша сеть заправок является одной из ведущих в Таджикистане.
|
||||
Мы предоставляем качественное топливо и высокий уровень
|
||||
обслуживания для наших клиентов уже более 15 лет.
|
||||
</p>
|
||||
<p className='mb-6 text-gray-600'>
|
||||
Мы постоянно развиваемся, открывая новые станции и улучшая
|
||||
сервис на существующих. Наша цель - сделать заправку
|
||||
автомобиля максимально удобной и быстрой для каждого клиента.
|
||||
</p>
|
||||
|
||||
<AboutCounter />
|
||||
<div className='space-y-4'>
|
||||
<div className='flex items-start'>
|
||||
<div className='mt-1 flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-red-600'>
|
||||
<span className='text-xs text-white'>✓</span>
|
||||
</div>
|
||||
<div className='ml-3'>
|
||||
<h3 className='text-lg font-medium'>
|
||||
Качественное топливо
|
||||
</h3>
|
||||
<p className='text-gray-600'>
|
||||
Мы гарантируем высокое качество нашего топлива
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-start'>
|
||||
<div className='mt-1 flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-red-600'>
|
||||
<span className='text-xs text-white'>✓</span>
|
||||
</div>
|
||||
<div className='ml-3'>
|
||||
<h3 className='text-lg font-medium'>
|
||||
Современное оборудование
|
||||
</h3>
|
||||
<p className='text-gray-600'>
|
||||
Все наши станции оснащены современным оборудованием
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-start'>
|
||||
<div className='mt-1 flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-red-600'>
|
||||
<span className='text-xs text-white'>✓</span>
|
||||
</div>
|
||||
<div className='ml-3'>
|
||||
<h3 className='text-lg font-medium'>
|
||||
Профессиональный персонал
|
||||
</h3>
|
||||
<p className='text-gray-600'>
|
||||
Наши сотрудники - профессионалы своего дела
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='relative h-[400px] overflow-hidden rounded-xl shadow-xl'>
|
||||
<Image
|
||||
src='/placeholder.svg?height=400&width=600'
|
||||
alt='About our company'
|
||||
fill
|
||||
className='object-cover'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Promotions Section */}
|
||||
<section id='promotions' className='bg-gray-50 py-16'>
|
||||
<div className='container'>
|
||||
<div className='mb-12 flex flex-col items-center justify-center text-center'>
|
||||
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
|
||||
<Gift className='h-6 w-6 text-red-600' />
|
||||
</div>
|
||||
<h2 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl'>
|
||||
Актуальные акции
|
||||
</h2>
|
||||
<p className='max-w-2xl text-gray-600'>
|
||||
Специальные предложения и акции для наших клиентов.
|
||||
Заправляйтесь выгодно!
|
||||
</p>
|
||||
</div>
|
||||
<PromotionSlider />
|
||||
<div className='mt-8 flex justify-center'>
|
||||
<Button className='bg-red-600 hover:bg-red-700'>
|
||||
Все акции <ArrowRight className='ml-2 h-4 w-4' />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Vacancies Section */}
|
||||
<section id='vacancies' className='py-16'>
|
||||
<div className='container'>
|
||||
<div className='mb-12 flex flex-col items-center justify-center text-center'>
|
||||
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
|
||||
<Briefcase className='h-6 w-6 text-red-600' />
|
||||
</div>
|
||||
<h2 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl'>
|
||||
Вакансии
|
||||
</h2>
|
||||
<p className='max-w-2xl text-gray-600'>
|
||||
Присоединяйтесь к нашей команде профессионалов. Мы предлагаем
|
||||
стабильную работу и возможности для роста.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue='all' className='mx-auto w-full max-w-3xl'>
|
||||
<TabsList className='mb-8 grid grid-cols-3'>
|
||||
<TabsTrigger value='all'>Все вакансии</TabsTrigger>
|
||||
<TabsTrigger value='office'>Офис</TabsTrigger>
|
||||
<TabsTrigger value='stations'>Заправки</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value='all' className='space-y-4'>
|
||||
{[
|
||||
'Оператор АЗС',
|
||||
'Менеджер по продажам',
|
||||
'Бухгалтер',
|
||||
'Специалист по логистике',
|
||||
].map((job, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
className='overflow-hidden transition-all hover:shadow-md'
|
||||
>
|
||||
<CardContent className='p-0'>
|
||||
<div className='p-6'>
|
||||
<div className='flex items-start justify-between'>
|
||||
<div>
|
||||
<h3 className='mb-2 text-lg font-bold'>{job}</h3>
|
||||
<p className='mb-4 text-sm text-gray-500'>
|
||||
Душанбе, Таджикистан
|
||||
</p>
|
||||
<div className='mb-4 flex flex-wrap gap-2'>
|
||||
<span className='inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-800'>
|
||||
Полный день
|
||||
</span>
|
||||
<span className='inline-flex items-center rounded-full bg-red-100 px-2.5 py-0.5 text-xs font-medium text-red-800'>
|
||||
Опыт от 1 года
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant='outline' size='sm'>
|
||||
Подробнее
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</TabsContent>
|
||||
<TabsContent value='office' className='space-y-4'>
|
||||
{[
|
||||
'Менеджер по продажам',
|
||||
'Бухгалтер',
|
||||
'Специалист по логистике',
|
||||
].map((job, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
className='overflow-hidden transition-all hover:shadow-md'
|
||||
>
|
||||
<CardContent className='p-0'>
|
||||
<div className='p-6'>
|
||||
<div className='flex items-start justify-between'>
|
||||
<div>
|
||||
<h3 className='mb-2 text-lg font-bold'>{job}</h3>
|
||||
<p className='mb-4 text-sm text-gray-500'>
|
||||
Душанбе, Таджикистан
|
||||
</p>
|
||||
<div className='mb-4 flex flex-wrap gap-2'>
|
||||
<span className='inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-800'>
|
||||
Полный день
|
||||
</span>
|
||||
<span className='inline-flex items-center rounded-full bg-red-100 px-2.5 py-0.5 text-xs font-medium text-red-800'>
|
||||
Опыт от 1 года
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant='outline' size='sm'>
|
||||
Подробнее
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</TabsContent>
|
||||
<TabsContent value='stations' className='space-y-4'>
|
||||
{['Оператор АЗС', 'Заправщик', 'Менеджер станции'].map(
|
||||
(job, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
className='overflow-hidden transition-all hover:shadow-md'
|
||||
>
|
||||
<CardContent className='p-0'>
|
||||
<div className='p-6'>
|
||||
<div className='flex items-start justify-between'>
|
||||
<div>
|
||||
<h3 className='mb-2 text-lg font-bold'>{job}</h3>
|
||||
<p className='mb-4 text-sm text-gray-500'>
|
||||
Душанбе, Таджикистан
|
||||
</p>
|
||||
<div className='mb-4 flex flex-wrap gap-2'>
|
||||
<span className='inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-800'>
|
||||
Сменный график
|
||||
</span>
|
||||
<span className='inline-flex items-center rounded-full bg-red-100 px-2.5 py-0.5 text-xs font-medium text-red-800'>
|
||||
Обучение
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant='outline' size='sm'>
|
||||
Подробнее
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
),
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<div className='mt-8 flex justify-center'>
|
||||
<Button className='bg-red-600 hover:bg-red-700'>
|
||||
Отправить резюме <ArrowRight className='ml-2 h-4 w-4' />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Partners Section */}
|
||||
<section id='partners' className='bg-gray-50 py-16'>
|
||||
<div className='container'>
|
||||
<div className='mb-12 flex flex-col items-center justify-center text-center'>
|
||||
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
|
||||
<Handshake className='h-6 w-6 text-red-600' />
|
||||
</div>
|
||||
<h2 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl'>
|
||||
Наши партнеры
|
||||
</h2>
|
||||
<p className='max-w-2xl text-gray-600'>
|
||||
Мы сотрудничаем с ведущими компаниями для предоставления лучших
|
||||
услуг нашим клиентам.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-2 gap-8 md:grid-cols-4'>
|
||||
{[1, 2, 3, 4, 5, 6, 7, 8].map((partner) => (
|
||||
<div
|
||||
key={partner}
|
||||
className='flex h-32 items-center justify-center rounded-lg bg-white p-6 shadow-md transition-transform hover:scale-105'
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
src={`/placeholder.svg?height=80&width=160&text=Partner ${partner}`}
|
||||
alt={`Partner ${partner}`}
|
||||
width={160}
|
||||
height={80}
|
||||
className='max-h-16 w-auto'
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className='mt-12 text-center'>
|
||||
<h3 className='mb-4 text-xl font-bold'>
|
||||
Станьте нашим партнером
|
||||
</h3>
|
||||
<p className='mx-auto mb-6 max-w-2xl text-gray-600'>
|
||||
Мы открыты для сотрудничества и новых партнерских отношений.
|
||||
Свяжитесь с нами для обсуждения возможностей.
|
||||
</p>
|
||||
<Button className='bg-red-600 hover:bg-red-700'>
|
||||
Связаться с нами
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Charity Section */}
|
||||
<section id='charity' className='py-16'>
|
||||
<div className='container'>
|
||||
<div className='grid items-center gap-12 md:grid-cols-2'>
|
||||
<div className='relative order-2 h-[400px] overflow-hidden rounded-xl shadow-xl md:order-1'>
|
||||
<Image
|
||||
src='/placeholder.svg?height=400&width=600'
|
||||
alt='Charity Foundation'
|
||||
fill
|
||||
className='object-cover'
|
||||
/>
|
||||
</div>
|
||||
<div className='order-1 md:order-2'>
|
||||
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
|
||||
<Heart className='h-6 w-6 text-red-600' />
|
||||
</div>
|
||||
<h2 className='mb-6 text-3xl font-bold tracking-tight sm:text-4xl'>
|
||||
Благотворительный фонд
|
||||
</h2>
|
||||
<p className='mb-6 text-gray-600'>
|
||||
Наш благотворительный фонд был создан для поддержки социально
|
||||
значимых проектов в Таджикистане. Мы стремимся внести свой
|
||||
вклад в развитие общества и помочь тем, кто в этом нуждается.
|
||||
</p>
|
||||
<p className='mb-6 text-gray-600'>
|
||||
Основные направления деятельности нашего фонда:
|
||||
</p>
|
||||
<ul className='mb-6 space-y-2'>
|
||||
<li className='flex items-center'>
|
||||
<ChevronRight className='mr-2 h-5 w-5 text-red-600' />
|
||||
<span>Поддержка образовательных программ</span>
|
||||
</li>
|
||||
<li className='flex items-center'>
|
||||
<ChevronRight className='mr-2 h-5 w-5 text-red-600' />
|
||||
<span>Помощь детям из малообеспеченных семей</span>
|
||||
</li>
|
||||
<li className='flex items-center'>
|
||||
<ChevronRight className='mr-2 h-5 w-5 text-red-600' />
|
||||
<span>Экологические инициативы</span>
|
||||
</li>
|
||||
<li className='flex items-center'>
|
||||
<ChevronRight className='mr-2 h-5 w-5 text-red-600' />
|
||||
<span>Поддержка спортивных мероприятий</span>
|
||||
</li>
|
||||
</ul>
|
||||
<Button className='bg-red-600 hover:bg-red-700'>
|
||||
Подробнее о фонде
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className='bg-red-600 py-16 text-white'>
|
||||
<div className='container'>
|
||||
<div className='flex flex-col items-center text-center'>
|
||||
<h2 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl'>
|
||||
Присоединяйтесь к нам
|
||||
</h2>
|
||||
<p className='mb-8 max-w-2xl'>
|
||||
Станьте частью нашей сети. Получайте специальные предложения,
|
||||
бонусы и скидки.
|
||||
</p>
|
||||
<div className='flex flex-col gap-4 sm:flex-row'>
|
||||
<Button
|
||||
variant='outline'
|
||||
className='border-white text-white hover:bg-white/10'
|
||||
>
|
||||
Read our docs
|
||||
Скачать приложение
|
||||
</Button>
|
||||
<Button className='bg-white text-red-600 hover:bg-gray-100'>
|
||||
Получить карту лояльности
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className='bg-gray-900 py-12 text-white'>
|
||||
<div className='container'>
|
||||
<div className='grid grid-cols-1 gap-8 md:grid-cols-4'>
|
||||
<div>
|
||||
<div className='mb-4 flex items-center gap-2'>
|
||||
<Fuel className='h-6 w-6 text-red-500' />
|
||||
<span className='text-xl font-bold'>GasNetwork</span>
|
||||
</div>
|
||||
<p className='mb-4 text-gray-400'>
|
||||
Сеть современных заправок в Таджикистане. Качественное топливо и
|
||||
отличный сервис.
|
||||
</p>
|
||||
<div className='flex space-x-4'>
|
||||
<a href='#' className='text-gray-400 hover:text-white'>
|
||||
<svg
|
||||
className='h-6 w-6'
|
||||
fill='currentColor'
|
||||
viewBox='0 0 24 24'
|
||||
aria-hidden='true'
|
||||
>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
d='M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z'
|
||||
clipRule='evenodd'
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href='#' className='text-gray-400 hover:text-white'>
|
||||
<svg
|
||||
className='h-6 w-6'
|
||||
fill='currentColor'
|
||||
viewBox='0 0 24 24'
|
||||
aria-hidden='true'
|
||||
>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
d='M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z'
|
||||
clipRule='evenodd'
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href='#' className='text-gray-400 hover:text-white'>
|
||||
<svg
|
||||
className='h-6 w-6'
|
||||
fill='currentColor'
|
||||
viewBox='0 0 24 24'
|
||||
aria-hidden='true'
|
||||
>
|
||||
<path d='M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84' />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
</div>
|
||||
<div>
|
||||
<h3 className='mb-4 text-lg font-semibold'>Контакты</h3>
|
||||
<div className='space-y-3'>
|
||||
<div className='flex items-start'>
|
||||
<MapPin className='mt-0.5 mr-3 h-5 w-5 text-red-500' />
|
||||
<p>ул. Рудаки 137, Душанбе, Таджикистан</p>
|
||||
</div>
|
||||
<div className='flex items-start'>
|
||||
<Phone className='mt-0.5 mr-3 h-5 w-5 text-red-500' />
|
||||
<p>+992 (37) 223-45-67</p>
|
||||
</div>
|
||||
<div className='flex items-start'>
|
||||
<Mail className='mt-0.5 mr-3 h-5 w-5 text-red-500' />
|
||||
<p>info@gasnetwork.tj</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className='mb-4 text-lg font-semibold'>Навигация</h3>
|
||||
<ul className='space-y-2'>
|
||||
<li>
|
||||
<Link
|
||||
href='#stations'
|
||||
className='text-gray-400 hover:text-white'
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
Наши заправки
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href='#about'
|
||||
className='text-gray-400 hover:text-white'
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
О нас
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href='#vacancies'
|
||||
className='text-gray-400 hover:text-white'
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
Вакансии
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href='#promotions'
|
||||
className='text-gray-400 hover:text-white'
|
||||
>
|
||||
Акции
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href='#partners'
|
||||
className='text-gray-400 hover:text-white'
|
||||
>
|
||||
Партнеры
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href='#charity'
|
||||
className='text-gray-400 hover:text-white'
|
||||
>
|
||||
Благотворительность
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className='mb-4 text-lg font-semibold'>Подписка</h3>
|
||||
<p className='mb-4 text-gray-400'>
|
||||
Подпишитесь на нашу рассылку, чтобы получать новости и
|
||||
специальные предложения.
|
||||
</p>
|
||||
<form className='space-y-2'>
|
||||
<input
|
||||
type='email'
|
||||
placeholder='Ваш email'
|
||||
className='w-full rounded-md border border-gray-700 bg-gray-800 px-4 py-2 text-white'
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
<Button className='w-full bg-red-600 hover:bg-red-700'>
|
||||
Подписаться
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-8 border-t border-gray-800 pt-8 text-center text-gray-400'>
|
||||
<p>
|
||||
© {new Date().getFullYear()} GasNetwork. Все права защищены.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
|
||||
70
src/shared/components/about-counter.tsx
Normal file
70
src/shared/components/about-counter.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
'use client';
|
||||
|
||||
import { Users } from 'lucide-react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import AnimatedCounter from './animated-counter';
|
||||
|
||||
export default function AboutCounter() {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const sectionRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const [entry] = entries;
|
||||
if (entry.isIntersecting) {
|
||||
setIsVisible(true);
|
||||
observer.disconnect();
|
||||
}
|
||||
},
|
||||
{
|
||||
threshold: 0.1,
|
||||
},
|
||||
);
|
||||
|
||||
if (sectionRef.current) {
|
||||
observer.observe(sectionRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={sectionRef} className='my-8 grid grid-cols-3 gap-6 text-center'>
|
||||
<div className='transform rounded-lg bg-white p-6 shadow-md transition-transform hover:scale-105'>
|
||||
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
|
||||
<Users className='h-6 w-6 text-red-600' />
|
||||
</div>
|
||||
<h3 className='text-2xl font-bold text-gray-900'>
|
||||
{isVisible ? <AnimatedCounter end={150} suffix='+' /> : '0+'}
|
||||
</h3>
|
||||
<p className='text-gray-600'>Сотрудников</p>
|
||||
</div>
|
||||
<div className='transform rounded-lg bg-white p-6 shadow-md transition-transform hover:scale-105'>
|
||||
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
|
||||
<Users className='h-6 w-6 text-red-600' />
|
||||
</div>
|
||||
<h3 className='text-2xl font-bold text-gray-900'>
|
||||
{isVisible ? <AnimatedCounter end={5} suffix='M+' /> : '0M+'}
|
||||
</h3>
|
||||
<p className='text-gray-600'>Литров топлива в месяц</p>
|
||||
</div>
|
||||
<div className='transform rounded-lg bg-white p-6 shadow-md transition-transform hover:scale-105'>
|
||||
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
|
||||
<Users className='h-6 w-6 text-red-600' />
|
||||
</div>
|
||||
<h3 className='text-2xl font-bold text-gray-900'>
|
||||
{isVisible ? (
|
||||
<AnimatedCounter end={98} suffix='%' decimals={1} />
|
||||
) : (
|
||||
'0%'
|
||||
)}
|
||||
</h3>
|
||||
<p className='text-gray-600'>Довольных клиентов</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
73
src/shared/components/animated-counter.tsx
Normal file
73
src/shared/components/animated-counter.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
interface AnimatedCounterProps {
|
||||
end: number;
|
||||
duration?: number;
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
decimals?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function AnimatedCounter({
|
||||
end,
|
||||
duration = 2000,
|
||||
prefix = '',
|
||||
suffix = '',
|
||||
decimals = 0,
|
||||
className = '',
|
||||
}: AnimatedCounterProps) {
|
||||
const [count, setCount] = useState(0);
|
||||
const countRef = useRef(0);
|
||||
const startTimeRef = useRef<number | null>(null);
|
||||
const frameRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Reset when end value changes
|
||||
countRef.current = 0;
|
||||
startTimeRef.current = null;
|
||||
setCount(0);
|
||||
|
||||
const animate = (timestamp: number) => {
|
||||
if (startTimeRef.current === null) {
|
||||
startTimeRef.current = timestamp;
|
||||
}
|
||||
|
||||
const progress = timestamp - startTimeRef.current;
|
||||
const percentage = Math.min(progress / duration, 1);
|
||||
|
||||
// Easing function for smoother animation
|
||||
const easeOutQuart = 1 - Math.pow(1 - percentage, 4);
|
||||
|
||||
const currentCount = Math.min(easeOutQuart * end, end);
|
||||
countRef.current = currentCount;
|
||||
setCount(currentCount);
|
||||
|
||||
if (percentage < 1) {
|
||||
frameRef.current = requestAnimationFrame(animate);
|
||||
}
|
||||
};
|
||||
|
||||
frameRef.current = requestAnimationFrame(animate);
|
||||
|
||||
return () => {
|
||||
if (frameRef.current !== null) {
|
||||
cancelAnimationFrame(frameRef.current);
|
||||
}
|
||||
};
|
||||
}, [end, duration]);
|
||||
|
||||
const formatNumber = (num: number) => {
|
||||
return num.toFixed(decimals);
|
||||
};
|
||||
|
||||
return (
|
||||
<span className={className}>
|
||||
{prefix}
|
||||
{formatNumber(count)}
|
||||
{suffix}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
116
src/shared/components/gas-station-map.tsx
Normal file
116
src/shared/components/gas-station-map.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
'use client';
|
||||
|
||||
import { MapPin } from 'lucide-react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
export default function GasStationMap() {
|
||||
const mapRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// This is a placeholder for a real map implementation
|
||||
// In a real application, you would use a mapping library like Mapbox, Google Maps, or Leaflet
|
||||
if (mapRef.current) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = mapRef.current.clientWidth;
|
||||
canvas.height = mapRef.current.clientHeight;
|
||||
mapRef.current.appendChild(canvas);
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (ctx) {
|
||||
// Draw a simple map placeholder
|
||||
ctx.fillStyle = '#f3f4f6';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Draw some roads
|
||||
ctx.strokeStyle = '#d1d5db';
|
||||
ctx.lineWidth = 5;
|
||||
|
||||
// Horizontal roads
|
||||
for (let i = 1; i < 5; i++) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, canvas.height * (i / 5));
|
||||
ctx.lineTo(canvas.width, canvas.height * (i / 5));
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Vertical roads
|
||||
for (let i = 1; i < 8; i++) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(canvas.width * (i / 8), 0);
|
||||
ctx.lineTo(canvas.width * (i / 8), canvas.height);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Draw gas station markers
|
||||
const stations = [
|
||||
{ x: 0.2, y: 0.3 },
|
||||
{ x: 0.5, y: 0.2 },
|
||||
{ x: 0.7, y: 0.4 },
|
||||
{ x: 0.3, y: 0.6 },
|
||||
{ x: 0.6, y: 0.7 },
|
||||
{ x: 0.8, y: 0.8 },
|
||||
{ x: 0.1, y: 0.9 },
|
||||
];
|
||||
|
||||
stations.forEach((station) => {
|
||||
// Draw marker
|
||||
ctx.fillStyle = '#ef4444';
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
station.x * canvas.width,
|
||||
station.y * canvas.height,
|
||||
10,
|
||||
0,
|
||||
2 * Math.PI,
|
||||
);
|
||||
ctx.fill();
|
||||
|
||||
// Draw white border
|
||||
ctx.strokeStyle = 'white';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
station.x * canvas.width,
|
||||
station.y * canvas.height,
|
||||
10,
|
||||
0,
|
||||
2 * Math.PI,
|
||||
);
|
||||
ctx.stroke();
|
||||
});
|
||||
|
||||
// Add city names
|
||||
ctx.fillStyle = '#1f2937';
|
||||
ctx.font = 'bold 16px Arial';
|
||||
ctx.fillText('Душанбе', canvas.width * 0.45, canvas.height * 0.15);
|
||||
ctx.fillText('Худжанд', canvas.width * 0.2, canvas.height * 0.25);
|
||||
ctx.fillText('Куляб', canvas.width * 0.7, canvas.height * 0.35);
|
||||
ctx.fillText('Бохтар', canvas.width * 0.3, canvas.height * 0.55);
|
||||
ctx.fillText('Хорог', canvas.width * 0.6, canvas.height * 0.65);
|
||||
ctx.fillText('Истаравшан', canvas.width * 0.8, canvas.height * 0.75);
|
||||
ctx.fillText('Пенджикент', canvas.width * 0.1, canvas.height * 0.85);
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (mapRef.current) {
|
||||
while (mapRef.current.firstChild) {
|
||||
mapRef.current.removeChild(mapRef.current.firstChild);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className='relative h-full w-full'>
|
||||
<div ref={mapRef} className='h-full w-full'></div>
|
||||
<div className='absolute right-4 bottom-4 rounded-lg bg-white p-3 shadow-lg'>
|
||||
<div className='flex items-center gap-2 text-sm font-medium'>
|
||||
<MapPin className='h-5 w-5 text-red-600' />
|
||||
<span>Наши заправки</span>
|
||||
</div>
|
||||
<p className='mt-1 text-xs text-gray-500'>Всего станций: 25</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
144
src/shared/components/promotion-slider.tsx
Normal file
144
src/shared/components/promotion-slider.tsx
Normal file
@ -0,0 +1,144 @@
|
||||
'use client';
|
||||
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Button } from '@/shared/shadcn-ui/button';
|
||||
import { Card, CardContent } from '@/shared/shadcn-ui/card';
|
||||
|
||||
const promotions = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Скидка 10% на премиум топливо',
|
||||
description:
|
||||
'Получите скидку 10% на премиум топливо при заправке от 30 литров',
|
||||
image: '/placeholder.svg?height=200&width=400&text=Promotion 1',
|
||||
validUntil: '31.12.2023',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Кофе в подарок',
|
||||
description: 'Получите бесплатный кофе при заправке от 20 литров',
|
||||
image: '/placeholder.svg?height=200&width=400&text=Promotion 2',
|
||||
validUntil: '15.11.2023',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Двойные баллы по карте лояльности',
|
||||
description: 'Получайте в 2 раза больше баллов при заправке в выходные дни',
|
||||
image: '/placeholder.svg?height=200&width=400&text=Promotion 3',
|
||||
validUntil: '30.11.2023',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: 'Скидка на автомойку',
|
||||
description: 'Скидка 20% на автомойку при заправке от 40 литров',
|
||||
image: '/placeholder.svg?height=200&width=400&text=Promotion 4',
|
||||
validUntil: '31.12.2023',
|
||||
},
|
||||
];
|
||||
|
||||
export default function PromotionSlider() {
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const [visibleItems, setVisibleItems] = useState(3);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (window.innerWidth < 640) {
|
||||
setVisibleItems(1);
|
||||
} else if (window.innerWidth < 1024) {
|
||||
setVisibleItems(2);
|
||||
} else {
|
||||
setVisibleItems(3);
|
||||
}
|
||||
};
|
||||
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
const nextSlide = () => {
|
||||
setCurrentIndex((prevIndex) =>
|
||||
prevIndex + visibleItems >= promotions.length ? 0 : prevIndex + 1,
|
||||
);
|
||||
};
|
||||
|
||||
const prevSlide = () => {
|
||||
setCurrentIndex((prevIndex) =>
|
||||
prevIndex === 0
|
||||
? Math.max(0, promotions.length - visibleItems)
|
||||
: prevIndex - 1,
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<div className='overflow-hidden'>
|
||||
<div
|
||||
className='flex transition-transform duration-300 ease-in-out'
|
||||
style={{
|
||||
transform: `translateX(-${currentIndex * (100 / visibleItems)}%)`,
|
||||
}}
|
||||
>
|
||||
{promotions.map((promo) => (
|
||||
<div
|
||||
key={promo.id}
|
||||
className='w-full flex-none p-2 sm:w-1/2 lg:w-1/3'
|
||||
>
|
||||
<Card className='h-full overflow-hidden transition-shadow hover:shadow-lg'>
|
||||
<div className='relative h-48'>
|
||||
<Image
|
||||
src={promo.image || '/placeholder.svg'}
|
||||
alt={promo.title}
|
||||
fill
|
||||
className='object-cover'
|
||||
/>
|
||||
</div>
|
||||
<CardContent className='p-4'>
|
||||
<h3 className='mb-2 text-lg font-bold'>{promo.title}</h3>
|
||||
<p className='mb-3 text-sm text-gray-600'>
|
||||
{promo.description}
|
||||
</p>
|
||||
<div className='flex items-center justify-between'>
|
||||
<span className='text-xs text-gray-500'>
|
||||
Действует до: {promo.validUntil}
|
||||
</span>
|
||||
<Button
|
||||
variant='outline'
|
||||
size='sm'
|
||||
className='border-red-600 text-red-600 hover:bg-red-50'
|
||||
>
|
||||
Подробнее
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant='outline'
|
||||
size='icon'
|
||||
className='absolute top-1/2 left-0 z-10 -translate-x-1/2 -translate-y-1/2 border-gray-200 bg-white shadow-lg'
|
||||
onClick={prevSlide}
|
||||
>
|
||||
<ChevronLeft className='h-4 w-4' />
|
||||
<span className='sr-only'>Предыдущий</span>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant='outline'
|
||||
size='icon'
|
||||
className='absolute top-1/2 right-0 z-10 translate-x-1/2 -translate-y-1/2 border-gray-200 bg-white shadow-lg'
|
||||
onClick={nextSlide}
|
||||
>
|
||||
<ChevronRight className='h-4 w-4' />
|
||||
<span className='sr-only'>Следующий</span>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
66
src/shared/components/stats-section.tsx
Normal file
66
src/shared/components/stats-section.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import AnimatedCounter from './animated-counter';
|
||||
|
||||
export default function StatsSection() {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const sectionRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const [entry] = entries;
|
||||
if (entry.isIntersecting) {
|
||||
setIsVisible(true);
|
||||
observer.disconnect();
|
||||
}
|
||||
},
|
||||
{
|
||||
threshold: 0.1,
|
||||
},
|
||||
);
|
||||
|
||||
if (sectionRef.current) {
|
||||
observer.observe(sectionRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section ref={sectionRef} className='bg-red-600 py-12 text-white'>
|
||||
<div className='container'>
|
||||
<div className='grid grid-cols-2 gap-8 text-center md:grid-cols-4'>
|
||||
<div className='space-y-2'>
|
||||
<h3 className='text-3xl font-bold'>
|
||||
{isVisible ? <AnimatedCounter end={25} suffix='+' /> : '0+'}
|
||||
</h3>
|
||||
<p className='text-sm text-white/80'>Заправок по стране</p>
|
||||
</div>
|
||||
<div className='space-y-2'>
|
||||
<h3 className='text-3xl font-bold'>
|
||||
{isVisible ? <AnimatedCounter end={10000} suffix='+' /> : '0+'}
|
||||
</h3>
|
||||
<p className='text-sm text-white/80'>Клиентов ежедневно</p>
|
||||
</div>
|
||||
<div className='space-y-2'>
|
||||
<h3 className='text-3xl font-bold'>
|
||||
{isVisible ? <AnimatedCounter end={15} /> : '0'}
|
||||
</h3>
|
||||
<p className='text-sm text-white/80'>Лет на рынке</p>
|
||||
</div>
|
||||
<div className='space-y-2'>
|
||||
<h3 className='text-3xl font-bold'>
|
||||
{isVisible ? <AnimatedCounter end={24} suffix='/7' /> : '0/7'}
|
||||
</h3>
|
||||
<p className='text-sm text-white/80'>Работаем круглосуточно</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { store } from '../store';
|
||||
|
||||
86
src/shared/shadcn-ui/card.tsx
Normal file
86
src/shared/shadcn-ui/card.tsx
Normal file
@ -0,0 +1,86 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '@/shared/lib/utils';
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'bg-card text-card-foreground rounded-lg border shadow-sm',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Card.displayName = 'Card';
|
||||
|
||||
const CardHeader = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('flex flex-col space-y-1.5 p-6', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardHeader.displayName = 'CardHeader';
|
||||
|
||||
const CardTitle = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'text-2xl leading-none font-semibold tracking-tight',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardTitle.displayName = 'CardTitle';
|
||||
|
||||
const CardDescription = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('text-muted-foreground text-sm', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardDescription.displayName = 'CardDescription';
|
||||
|
||||
const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
|
||||
));
|
||||
CardContent.displayName = 'CardContent';
|
||||
|
||||
const CardFooter = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('flex items-center p-6 pt-0', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardFooter.displayName = 'CardFooter';
|
||||
|
||||
export {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardFooter,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
};
|
||||
@ -19,7 +19,7 @@ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
|
||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
||||
|
||||
const DropdownMenuSubTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||
React.ComponentRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean;
|
||||
}
|
||||
@ -41,7 +41,7 @@ DropdownMenuSubTrigger.displayName =
|
||||
DropdownMenuPrimitive.SubTrigger.displayName;
|
||||
|
||||
const DropdownMenuSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||
React.ComponentRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
@ -57,7 +57,7 @@ DropdownMenuSubContent.displayName =
|
||||
DropdownMenuPrimitive.SubContent.displayName;
|
||||
|
||||
const DropdownMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||
React.ComponentRef<typeof DropdownMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
@ -75,7 +75,7 @@ const DropdownMenuContent = React.forwardRef<
|
||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
||||
|
||||
const DropdownMenuItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||
React.ComponentRef<typeof DropdownMenuPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean;
|
||||
}
|
||||
@ -93,7 +93,7 @@ const DropdownMenuItem = React.forwardRef<
|
||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
||||
|
||||
const DropdownMenuCheckboxItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||
React.ComponentRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
||||
>(({ className, children, checked, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
@ -117,7 +117,7 @@ DropdownMenuCheckboxItem.displayName =
|
||||
DropdownMenuPrimitive.CheckboxItem.displayName;
|
||||
|
||||
const DropdownMenuRadioItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||
React.ComponentRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
@ -139,7 +139,7 @@ const DropdownMenuRadioItem = React.forwardRef<
|
||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
|
||||
|
||||
const DropdownMenuLabel = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||
React.ComponentRef<typeof DropdownMenuPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean;
|
||||
}
|
||||
@ -157,7 +157,7 @@ const DropdownMenuLabel = React.forwardRef<
|
||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
|
||||
|
||||
const DropdownMenuSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||
React.ComponentRef<typeof DropdownMenuPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
55
src/shared/shadcn-ui/tabs.tsx
Normal file
55
src/shared/shadcn-ui/tabs.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
'use client';
|
||||
|
||||
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '@/shared/lib/utils';
|
||||
|
||||
const Tabs = TabsPrimitive.Root;
|
||||
|
||||
const TabsList = React.forwardRef<
|
||||
React.ComponentRef<typeof TabsPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'bg-muted text-muted-foreground inline-flex h-10 items-center justify-center rounded-md p-1',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsList.displayName = TabsPrimitive.List.displayName;
|
||||
|
||||
const TabsTrigger = React.forwardRef<
|
||||
React.ComponentRef<typeof TabsPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'ring-offset-background focus-visible:ring-ring data-[state=active]:bg-background data-[state=active]:text-foreground inline-flex items-center justify-center rounded-sm px-3 py-1.5 text-sm font-medium whitespace-nowrap transition-all focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
||||
|
||||
const TabsContent = React.forwardRef<
|
||||
React.ComponentRef<typeof TabsPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'ring-offset-background focus-visible:ring-ring mt-2 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
||||
@ -4,13 +4,13 @@ import { Moon, Sun } from 'lucide-react';
|
||||
import { useTheme } from 'next-themes';
|
||||
import * as React from 'react';
|
||||
|
||||
import { Button } from '@/shared/shad-cn/button';
|
||||
import { Button } from '@/shared/shadcn-ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/shared/shad-cn/dropdown-menu';
|
||||
} from '@/shared/shadcn-ui/dropdown-menu';
|
||||
|
||||
import { capitalize } from '../lib/capitalize';
|
||||
|
||||
|
||||
181
tailwind.config.js
Normal file
181
tailwind.config.js
Normal file
@ -0,0 +1,181 @@
|
||||
// /** @type {import('tailwindcss').Config} */
|
||||
// import tailwindcssAnimate from 'tailwindcss-animate';
|
||||
|
||||
// export default {
|
||||
// darkMode: ['class'],
|
||||
// content: ['./src/**/*.{js,ts,jsx,tsx}'],
|
||||
// theme: {
|
||||
// extend: {
|
||||
// borderRadius: {
|
||||
// lg: 'var(--radius)',
|
||||
// md: 'calc(var(--radius) - 2px)',
|
||||
// sm: 'calc(var(--radius) - 4px)',
|
||||
// },
|
||||
// colors: {
|
||||
// background: 'hsl(var(--background))',
|
||||
// foreground: 'hsl(var(--foreground))',
|
||||
// card: {
|
||||
// DEFAULT: 'hsl(var(--card))',
|
||||
// foreground: 'hsl(var(--card-foreground))',
|
||||
// },
|
||||
// popover: {
|
||||
// DEFAULT: 'hsl(var(--popover))',
|
||||
// foreground: 'hsl(var(--popover-foreground))',
|
||||
// },
|
||||
// primary: {
|
||||
// DEFAULT: 'hsl(var(--primary))',
|
||||
// foreground: 'hsl(var(--primary-foreground))',
|
||||
// },
|
||||
// secondary: {
|
||||
// DEFAULT: 'hsl(var(--secondary))',
|
||||
// foreground: 'hsl(var(--secondary-foreground))',
|
||||
// },
|
||||
// muted: {
|
||||
// DEFAULT: 'hsl(var(--muted))',
|
||||
// foreground: 'hsl(var(--muted-foreground))',
|
||||
// },
|
||||
// accent: {
|
||||
// DEFAULT: 'hsl(var(--accent))',
|
||||
// foreground: 'hsl(var(--accent-foreground))',
|
||||
// },
|
||||
// destructive: {
|
||||
// DEFAULT: 'hsl(var(--destructive))',
|
||||
// foreground: 'hsl(var(--destructive-foreground))',
|
||||
// },
|
||||
// border: 'hsl(var(--border))',
|
||||
// 'soft-gray': 'hsl(var(--soft-gray))',
|
||||
// 'forest-green': 'hsl(var(--forest-green))',
|
||||
// 'berry-red': 'hsl(var(--berry-red))',
|
||||
// input: 'hsl(var(--input))',
|
||||
// ring: 'hsl(var(--ring))',
|
||||
// radio: 'hsl(var(--radio))',
|
||||
// chart: {
|
||||
// 1: 'hsl(var(--chart-1))',
|
||||
// 2: 'hsl(var(--chart-2))',
|
||||
// 3: 'hsl(var(--chart-3))',
|
||||
// 4: 'hsl(var(--chart-4))',
|
||||
// 5: 'hsl(var(--chart-5))',
|
||||
// },
|
||||
// },
|
||||
// fontFamily: {
|
||||
// inter: ['Inter', 'sans-serif'],
|
||||
// },
|
||||
// keyframes: {
|
||||
// 'accordion-down': {
|
||||
// from: {
|
||||
// height: '0',
|
||||
// },
|
||||
// to: {
|
||||
// height: 'var(--radix-accordion-content-height)',
|
||||
// },
|
||||
// },
|
||||
// 'accordion-up': {
|
||||
// from: {
|
||||
// height: 'var(--radix-accordion-content-height)',
|
||||
// },
|
||||
// to: {
|
||||
// height: '0',
|
||||
// },
|
||||
// },
|
||||
// 'caret-blink': {
|
||||
// '0%,70%,100%': { opacity: '1' },
|
||||
// '20%,50%': { opacity: '0' },
|
||||
// },
|
||||
// 'collapsible-down': {
|
||||
// from: { height: '0' },
|
||||
// to: { height: 'var(--radix-collapsible-content-height)' },
|
||||
// },
|
||||
// 'collapsible-up': {
|
||||
// from: { height: 'var(--radix-collapsible-content-height)' },
|
||||
// to: { height: '0' },
|
||||
// },
|
||||
// },
|
||||
// animation: {
|
||||
// 'accordion-down': 'accordion-down 0.2s ease-out',
|
||||
// 'accordion-up': 'accordion-up 0.2s ease-out',
|
||||
// 'caret-blink': 'caret-blink 1.25s ease-out infinite',
|
||||
// 'collapsible-down': 'collapsible-down 0.2s ease-out',
|
||||
// 'collapsible-up': 'collapsible-up 0.2s ease-out',
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// plugins: [tailwindcssAnimate],
|
||||
// };
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: ['class'],
|
||||
content: [
|
||||
'./pages/**/*.{ts,tsx}',
|
||||
'./components/**/*.{ts,tsx}',
|
||||
'./app/**/*.{ts,tsx}',
|
||||
'./src/**/*.{ts,tsx}',
|
||||
'*.{js,ts,jsx,tsx,mdx}',
|
||||
],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: '2rem',
|
||||
screens: {
|
||||
'2xl': '1400px',
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
primary: {
|
||||
DEFAULT: '#e11d48', // Red-600 for primary
|
||||
foreground: 'hsl(var(--primary-foreground))',
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))',
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))',
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))',
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))',
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsl(var(--popover))',
|
||||
foreground: 'hsl(var(--popover-foreground))',
|
||||
},
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))',
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)',
|
||||
},
|
||||
keyframes: {
|
||||
'accordion-down': {
|
||||
from: { height: 0 },
|
||||
to: { height: 'var(--radix-accordion-content-height)' },
|
||||
},
|
||||
'accordion-up': {
|
||||
from: { height: 'var(--radix-accordion-content-height)' },
|
||||
to: { height: 0 },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||
'accordion-up': 'accordion-up 0.2s ease-out',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require('tailwindcss-animate')],
|
||||
};
|
||||
@ -19,7 +19,11 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
"@/*": ["./src/*"],
|
||||
"@/entities/*": ["./src/entities/*"],
|
||||
"@/features/*": ["./src/features/*"],
|
||||
"@/shared/*": ["./src/shared/*"],
|
||||
"@/widgets/*": ["./src/widgets/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user