update: add custom-components and make home page
This commit is contained in:
parent
477d311213
commit
ceef7c7efc
@ -10,12 +10,13 @@
|
|||||||
"cssVariables": true,
|
"cssVariables": true,
|
||||||
"prefix": ""
|
"prefix": ""
|
||||||
},
|
},
|
||||||
|
"componentPath": "src/shared/shad-cn",
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"components": "@/components",
|
"components": "@/shared",
|
||||||
"utils": "@/lib/utils",
|
"utils": "@/shared/lib/utils",
|
||||||
"ui": "@/components/ui",
|
"ui": "@/shared/shad-cn",
|
||||||
"lib": "@/lib",
|
"lib": "@/shared/lib",
|
||||||
"hooks": "@/hooks"
|
"hooks": "@/shared/hooks"
|
||||||
},
|
},
|
||||||
"iconLibrary": "lucide"
|
"iconLibrary": "lucide"
|
||||||
}
|
}
|
||||||
@ -10,7 +10,7 @@ import globals from 'globals';
|
|||||||
import TS_ESLint from 'typescript-eslint';
|
import TS_ESLint from 'typescript-eslint';
|
||||||
|
|
||||||
export default TS_ESLint.config(
|
export default TS_ESLint.config(
|
||||||
{ ignores: ['dist'] },
|
{ ignores: ['.next'] },
|
||||||
{
|
{
|
||||||
extends: [
|
extends: [
|
||||||
js.configs.recommended,
|
js.configs.recommended,
|
||||||
@ -33,11 +33,7 @@ export default TS_ESLint.config(
|
|||||||
|
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 'latest',
|
ecmaVersion: 'latest',
|
||||||
project: [
|
project: ['./tsconfig.json'],
|
||||||
'./tsconfig.app.json',
|
|
||||||
'./tsconfig.json',
|
|
||||||
'./tsconfig.node.json',
|
|
||||||
],
|
|
||||||
projectService: true,
|
projectService: true,
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
tsconfigRootDir: import.meta.dirname,
|
tsconfigRootDir: import.meta.dirname,
|
||||||
@ -50,9 +46,7 @@ export default TS_ESLint.config(
|
|||||||
|
|
||||||
plugins: {
|
plugins: {
|
||||||
'react-hooks': reactHooks,
|
'react-hooks': reactHooks,
|
||||||
// 'react-refresh': reactRefresh,
|
|
||||||
'unused-imports': unusedImports,
|
'unused-imports': unusedImports,
|
||||||
// 'react-compiler': reactCompiler,
|
|
||||||
react,
|
react,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -69,7 +63,7 @@ export default TS_ESLint.config(
|
|||||||
|
|
||||||
ignores: [
|
ignores: [
|
||||||
'.prettierrc.cjs',
|
'.prettierrc.cjs',
|
||||||
'dist',
|
'.next',
|
||||||
'postcss.config.js',
|
'postcss.config.js',
|
||||||
'tailwind.config.ts',
|
'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",
|
"@hookform/resolvers": "^5.0.1",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.11",
|
"@radix-ui/react-dropdown-menu": "^2.1.11",
|
||||||
"@radix-ui/react-slot": "^1.2.0",
|
"@radix-ui/react-slot": "^1.2.0",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.8",
|
||||||
"@reduxjs/toolkit": "^2.7.0",
|
"@reduxjs/toolkit": "^2.7.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@ -22,6 +23,8 @@
|
|||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"tw-animate-css": "^1.2.6",
|
||||||
"zod": "^3.24.3"
|
"zod": "^3.24.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
52
pnpm-lock.yaml
generated
52
pnpm-lock.yaml
generated
@ -17,6 +17,9 @@ importers:
|
|||||||
'@radix-ui/react-slot':
|
'@radix-ui/react-slot':
|
||||||
specifier: ^1.2.0
|
specifier: ^1.2.0
|
||||||
version: 1.2.0(@types/react@19.1.2)(react@19.1.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':
|
'@reduxjs/toolkit':
|
||||||
specifier: ^2.7.0
|
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)
|
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:
|
tailwind-merge:
|
||||||
specifier: ^3.2.0
|
specifier: ^3.2.0
|
||||||
version: 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:
|
zod:
|
||||||
specifier: ^3.24.3
|
specifier: ^3.24.3
|
||||||
version: 3.24.3
|
version: 3.24.3
|
||||||
@ -646,6 +655,19 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
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':
|
'@radix-ui/react-use-callback-ref@1.1.1':
|
||||||
resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
|
resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -2271,6 +2293,11 @@ packages:
|
|||||||
tailwind-merge@3.2.0:
|
tailwind-merge@3.2.0:
|
||||||
resolution: {integrity: sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==}
|
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:
|
tailwindcss@4.1.4:
|
||||||
resolution: {integrity: sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==}
|
resolution: {integrity: sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==}
|
||||||
|
|
||||||
@ -2298,6 +2325,9 @@ packages:
|
|||||||
tslib@2.8.1:
|
tslib@2.8.1:
|
||||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||||
|
|
||||||
|
tw-animate-css@1.2.6:
|
||||||
|
resolution: {integrity: sha512-/DSl24Y1WNdtEWA187h3M5ixwvucje2DH2/Qi8N1plNn0Mb0O1E6F9trXknwzZbtVJCdnogaWLt45xQZOrKtpw==}
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
@ -2882,6 +2912,22 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.2
|
'@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)':
|
'@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.2)(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
@ -4627,6 +4673,10 @@ snapshots:
|
|||||||
|
|
||||||
tailwind-merge@3.2.0: {}
|
tailwind-merge@3.2.0: {}
|
||||||
|
|
||||||
|
tailwindcss-animate@1.0.7(tailwindcss@4.1.4):
|
||||||
|
dependencies:
|
||||||
|
tailwindcss: 4.1.4
|
||||||
|
|
||||||
tailwindcss@4.1.4: {}
|
tailwindcss@4.1.4: {}
|
||||||
|
|
||||||
tapable@2.2.1: {}
|
tapable@2.2.1: {}
|
||||||
@ -4653,6 +4703,8 @@ snapshots:
|
|||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
|
tw-animate-css@1.2.6: {}
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
prelude-ls: 1.2.1
|
prelude-ls: 1.2.1
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
/* @import "tw-animate-css"; */
|
@import "tw-animate-css";
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
@ -45,71 +45,71 @@
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
--radius: 0.625rem;
|
--radius: 0.625rem;
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.141 0.005 285.823);
|
|
||||||
--card: oklch(1 0 0);
|
--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: oklch(1 0 0);
|
||||||
--popover-foreground: oklch(0.141 0.005 285.823);
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
--primary: oklch(0.21 0.006 285.885);
|
--primary: oklch(0.205 0 0);
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
--secondary: oklch(0.967 0.001 286.375);
|
--secondary: oklch(0.97 0 0);
|
||||||
--secondary-foreground: oklch(0.21 0.006 285.885);
|
--secondary-foreground: oklch(0.205 0 0);
|
||||||
--muted: oklch(0.967 0.001 286.375);
|
--muted: oklch(0.97 0 0);
|
||||||
--muted-foreground: oklch(0.552 0.016 285.938);
|
--muted-foreground: oklch(0.556 0 0);
|
||||||
--accent: oklch(0.967 0.001 286.375);
|
--accent: oklch(0.97 0 0);
|
||||||
--accent-foreground: oklch(0.21 0.006 285.885);
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
--border: oklch(0.92 0.004 286.32);
|
--border: oklch(0.922 0 0);
|
||||||
--input: oklch(0.92 0.004 286.32);
|
--input: oklch(0.922 0 0);
|
||||||
--ring: oklch(0.705 0.015 286.067);
|
--ring: oklch(0.708 0 0);
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
--chart-3: oklch(0.398 0.07 227.392);
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
--chart-4: oklch(0.828 0.189 84.429);
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
--chart-5: oklch(0.769 0.188 70.08);
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
--sidebar: oklch(0.985 0 0);
|
--sidebar: oklch(0.985 0 0);
|
||||||
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
--sidebar-primary: oklch(0.21 0.006 285.885);
|
--sidebar-primary: oklch(0.205 0 0);
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-accent: oklch(0.967 0.001 286.375);
|
--sidebar-accent: oklch(0.97 0 0);
|
||||||
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
--sidebar-border: oklch(0.92 0.004 286.32);
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
--sidebar-ring: oklch(0.705 0.015 286.067);
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
|
--background: oklch(1 0 0);
|
||||||
|
--foreground: oklch(0.145 0 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: oklch(0.141 0.005 285.823);
|
--background: oklch(0.145 0 0);
|
||||||
--foreground: oklch(0.985 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);
|
--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);
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
--primary: oklch(0.92 0.004 286.32);
|
--primary: oklch(0.922 0 0);
|
||||||
--primary-foreground: oklch(0.21 0.006 285.885);
|
--primary-foreground: oklch(0.205 0 0);
|
||||||
--secondary: oklch(0.274 0.006 286.033);
|
--secondary: oklch(0.269 0 0);
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
--muted: oklch(0.274 0.006 286.033);
|
--muted: oklch(0.269 0 0);
|
||||||
--muted-foreground: oklch(0.705 0.015 286.067);
|
--muted-foreground: oklch(0.708 0 0);
|
||||||
--accent: oklch(0.274 0.006 286.033);
|
--accent: oklch(0.269 0 0);
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
--destructive: oklch(0.704 0.191 22.216);
|
||||||
--border: oklch(1 0 0 / 10%);
|
--border: oklch(1 0 0 / 10%);
|
||||||
--input: oklch(1 0 0 / 15%);
|
--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-1: oklch(0.488 0.243 264.376);
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
--chart-2: oklch(0.696 0.17 162.48);
|
||||||
--chart-3: oklch(0.769 0.188 70.08);
|
--chart-3: oklch(0.769 0.188 70.08);
|
||||||
--chart-4: oklch(0.627 0.265 303.9);
|
--chart-4: oklch(0.627 0.265 303.9);
|
||||||
--chart-5: oklch(0.645 0.246 16.439);
|
--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-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
--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-accent-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
--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 {
|
@layer base {
|
||||||
|
|||||||
@ -1,23 +1,16 @@
|
|||||||
import type { Metadata } from 'next';
|
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 { Providers } from '@/shared/providers/providers';
|
||||||
|
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
|
|
||||||
const geistSans = Geist({
|
const inter = Inter({ subsets: ['latin', 'cyrillic'] });
|
||||||
variable: '--font-geist-sans',
|
|
||||||
subsets: ['latin'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const geistMono = Geist_Mono({
|
|
||||||
variable: '--font-geist-mono',
|
|
||||||
subsets: ['latin'],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Create Next App',
|
title: 'GasNetwork - Сеть заправок в Таджикистане',
|
||||||
description: 'Generated by create next app',
|
description:
|
||||||
|
'Качественное топливо, удобное расположение и отличный сервис для наших клиентов',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
@ -27,9 +20,7 @@ export default function RootLayout({
|
|||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang='en' suppressHydrationWarning>
|
<html lang='en' suppressHydrationWarning>
|
||||||
<body
|
<body className={`${inter.className} antialiased`}>
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
|
||||||
>
|
|
||||||
<Providers>{children}</Providers>
|
<Providers>{children}</Providers>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
764
src/app/page.tsx
764
src/app/page.tsx
@ -1,104 +1,676 @@
|
|||||||
import { ModeToggle } from "@/shared/theme/theme-toggle";
|
import {
|
||||||
import Image from "next/image";
|
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() {
|
export default function Home() {
|
||||||
return (
|
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)]">
|
<div className='flex min-h-screen flex-col'>
|
||||||
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
|
{/* Header */}
|
||||||
<Image
|
<header className='sticky top-0 z-40 w-full border-b bg-white'>
|
||||||
className="dark:invert"
|
<div className='container flex h-16 items-center justify-between py-4'>
|
||||||
src="/next.svg"
|
<div className='flex items-center gap-2'>
|
||||||
alt="Next.js logo"
|
<Fuel className='h-6 w-6 text-red-600' />
|
||||||
width={180}
|
<span className='text-xl font-bold'>GasNetwork</span>
|
||||||
height={38}
|
</div>
|
||||||
priority
|
<nav className='hidden items-center gap-6 md:flex'>
|
||||||
/>
|
<Link
|
||||||
<ModeToggle/>
|
href='#stations'
|
||||||
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
className='text-sm font-medium transition-colors hover:text-red-600'
|
||||||
<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">
|
</Link>
|
||||||
src/app/page.tsx
|
<Link
|
||||||
</code>
|
href='#about'
|
||||||
.
|
className='text-sm font-medium transition-colors hover:text-red-600'
|
||||||
</li>
|
>
|
||||||
<li className="tracking-[-.01em]">
|
О нас
|
||||||
Save and see your changes instantly.
|
</Link>
|
||||||
</li>
|
<Link
|
||||||
</ol>
|
href='#vacancies'
|
||||||
|
className='text-sm font-medium transition-colors hover:text-red-600'
|
||||||
<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"
|
</Link>
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
<Link
|
||||||
target="_blank"
|
href='#promotions'
|
||||||
rel="noopener noreferrer"
|
className='text-sm font-medium transition-colors hover:text-red-600'
|
||||||
>
|
>
|
||||||
<Image
|
Акции
|
||||||
className="dark:invert"
|
</Link>
|
||||||
src="/vercel.svg"
|
<Link
|
||||||
alt="Vercel logomark"
|
href='#partners'
|
||||||
width={20}
|
className='text-sm font-medium transition-colors hover:text-red-600'
|
||||||
height={20}
|
>
|
||||||
/>
|
Партнеры
|
||||||
Deploy now
|
</Link>
|
||||||
</a>
|
<Link
|
||||||
<a
|
href='#charity'
|
||||||
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]"
|
className='text-sm font-medium transition-colors hover:text-red-600'
|
||||||
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"
|
</Link>
|
||||||
>
|
</nav>
|
||||||
Read our docs
|
<div className='flex items-center gap-4'>
|
||||||
</a>
|
<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>
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main className='flex-1'>
|
||||||
|
{/* Hero Section */}
|
||||||
|
<section className='relative'>
|
||||||
|
<div className='relative h-[500px] w-full overflow-hidden'>
|
||||||
|
<Image
|
||||||
|
src='/placeholder.svg?height=500&width=1920'
|
||||||
|
alt='Gas Station Network'
|
||||||
|
width={1920}
|
||||||
|
height={500}
|
||||||
|
className='object-cover'
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{/* 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
|
||||||
|
src={`/placeholder.svg?height=80&width=160&text=Partner ${partner}`}
|
||||||
|
alt={`Partner ${partner}`}
|
||||||
|
width={160}
|
||||||
|
height={80}
|
||||||
|
className='max-h-16 w-auto'
|
||||||
|
/>
|
||||||
|
</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'
|
||||||
|
>
|
||||||
|
Скачать приложение
|
||||||
|
</Button>
|
||||||
|
<Button className='bg-white text-red-600 hover:bg-gray-100'>
|
||||||
|
Получить карту лояльности
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
|
|
||||||
<a
|
{/* Footer */}
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
<footer className='bg-gray-900 py-12 text-white'>
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
<div className='container'>
|
||||||
target="_blank"
|
<div className='grid grid-cols-1 gap-8 md:grid-cols-4'>
|
||||||
rel="noopener noreferrer"
|
<div>
|
||||||
>
|
<div className='mb-4 flex items-center gap-2'>
|
||||||
<Image
|
<Fuel className='h-6 w-6 text-red-500' />
|
||||||
aria-hidden
|
<span className='text-xl font-bold'>GasNetwork</span>
|
||||||
src="/file.svg"
|
</div>
|
||||||
alt="File icon"
|
<p className='mb-4 text-gray-400'>
|
||||||
width={16}
|
Сеть современных заправок в Таджикистане. Качественное топливо и
|
||||||
height={16}
|
отличный сервис.
|
||||||
/>
|
</p>
|
||||||
Learn
|
<div className='flex space-x-4'>
|
||||||
</a>
|
<a href='#' className='text-gray-400 hover:text-white'>
|
||||||
<a
|
<svg
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
className='h-6 w-6'
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
fill='currentColor'
|
||||||
target="_blank"
|
viewBox='0 0 24 24'
|
||||||
rel="noopener noreferrer"
|
aria-hidden='true'
|
||||||
>
|
>
|
||||||
<Image
|
<path
|
||||||
aria-hidden
|
fillRule='evenodd'
|
||||||
src="/window.svg"
|
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'
|
||||||
alt="Window icon"
|
clipRule='evenodd'
|
||||||
width={16}
|
/>
|
||||||
height={16}
|
</svg>
|
||||||
/>
|
</a>
|
||||||
Examples
|
<a href='#' className='text-gray-400 hover:text-white'>
|
||||||
</a>
|
<svg
|
||||||
<a
|
className='h-6 w-6'
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
fill='currentColor'
|
||||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
viewBox='0 0 24 24'
|
||||||
target="_blank"
|
aria-hidden='true'
|
||||||
rel="noopener noreferrer"
|
>
|
||||||
>
|
<path
|
||||||
<Image
|
fillRule='evenodd'
|
||||||
aria-hidden
|
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'
|
||||||
src="/globe.svg"
|
clipRule='evenodd'
|
||||||
alt="Globe icon"
|
/>
|
||||||
width={16}
|
</svg>
|
||||||
height={16}
|
</a>
|
||||||
/>
|
<a href='#' className='text-gray-400 hover:text-white'>
|
||||||
Go to nextjs.org →
|
<svg
|
||||||
</a>
|
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>
|
||||||
|
</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'
|
||||||
|
>
|
||||||
|
Наши заправки
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link
|
||||||
|
href='#about'
|
||||||
|
className='text-gray-400 hover:text-white'
|
||||||
|
>
|
||||||
|
О нас
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link
|
||||||
|
href='#vacancies'
|
||||||
|
className='text-gray-400 hover:text-white'
|
||||||
|
>
|
||||||
|
Вакансии
|
||||||
|
</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'
|
||||||
|
/>
|
||||||
|
<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>
|
</footer>
|
||||||
</div>
|
</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 { Provider } from 'react-redux';
|
||||||
|
|
||||||
import { store } from '../store';
|
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 DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
||||||
|
|
||||||
const DropdownMenuSubTrigger = React.forwardRef<
|
const DropdownMenuSubTrigger = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
React.ComponentRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||||
inset?: boolean;
|
inset?: boolean;
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ DropdownMenuSubTrigger.displayName =
|
|||||||
DropdownMenuPrimitive.SubTrigger.displayName;
|
DropdownMenuPrimitive.SubTrigger.displayName;
|
||||||
|
|
||||||
const DropdownMenuSubContent = React.forwardRef<
|
const DropdownMenuSubContent = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
React.ComponentRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<DropdownMenuPrimitive.SubContent
|
<DropdownMenuPrimitive.SubContent
|
||||||
@ -57,7 +57,7 @@ DropdownMenuSubContent.displayName =
|
|||||||
DropdownMenuPrimitive.SubContent.displayName;
|
DropdownMenuPrimitive.SubContent.displayName;
|
||||||
|
|
||||||
const DropdownMenuContent = React.forwardRef<
|
const DropdownMenuContent = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
React.ComponentRef<typeof DropdownMenuPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||||
<DropdownMenuPrimitive.Portal>
|
<DropdownMenuPrimitive.Portal>
|
||||||
@ -75,7 +75,7 @@ const DropdownMenuContent = React.forwardRef<
|
|||||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
||||||
|
|
||||||
const DropdownMenuItem = React.forwardRef<
|
const DropdownMenuItem = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
React.ComponentRef<typeof DropdownMenuPrimitive.Item>,
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||||
inset?: boolean;
|
inset?: boolean;
|
||||||
}
|
}
|
||||||
@ -93,7 +93,7 @@ const DropdownMenuItem = React.forwardRef<
|
|||||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
||||||
|
|
||||||
const DropdownMenuCheckboxItem = React.forwardRef<
|
const DropdownMenuCheckboxItem = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
React.ComponentRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
||||||
>(({ className, children, checked, ...props }, ref) => (
|
>(({ className, children, checked, ...props }, ref) => (
|
||||||
<DropdownMenuPrimitive.CheckboxItem
|
<DropdownMenuPrimitive.CheckboxItem
|
||||||
@ -117,7 +117,7 @@ DropdownMenuCheckboxItem.displayName =
|
|||||||
DropdownMenuPrimitive.CheckboxItem.displayName;
|
DropdownMenuPrimitive.CheckboxItem.displayName;
|
||||||
|
|
||||||
const DropdownMenuRadioItem = React.forwardRef<
|
const DropdownMenuRadioItem = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
React.ComponentRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
||||||
>(({ className, children, ...props }, ref) => (
|
>(({ className, children, ...props }, ref) => (
|
||||||
<DropdownMenuPrimitive.RadioItem
|
<DropdownMenuPrimitive.RadioItem
|
||||||
@ -139,7 +139,7 @@ const DropdownMenuRadioItem = React.forwardRef<
|
|||||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
|
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
|
||||||
|
|
||||||
const DropdownMenuLabel = React.forwardRef<
|
const DropdownMenuLabel = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
React.ComponentRef<typeof DropdownMenuPrimitive.Label>,
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||||
inset?: boolean;
|
inset?: boolean;
|
||||||
}
|
}
|
||||||
@ -157,7 +157,7 @@ const DropdownMenuLabel = React.forwardRef<
|
|||||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
|
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
|
||||||
|
|
||||||
const DropdownMenuSeparator = React.forwardRef<
|
const DropdownMenuSeparator = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
React.ComponentRef<typeof DropdownMenuPrimitive.Separator>,
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<DropdownMenuPrimitive.Separator
|
<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 { useTheme } from 'next-themes';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { Button } from '@/shared/shad-cn/button';
|
import { Button } from '@/shared/shadcn-ui/button';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/shared/shad-cn/dropdown-menu';
|
} from '@/shared/shadcn-ui/dropdown-menu';
|
||||||
|
|
||||||
import { capitalize } from '../lib/capitalize';
|
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": {
|
"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"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user