From 3e90019de222a75113b5cab18a37315112f871ca Mon Sep 17 00:00:00 2001
From: BunyodL
Date: Wed, 7 May 2025 13:55:15 +0500
Subject: [PATCH] feat: add review-form
---
package.json | 1 +
pnpm-lock.yaml | 198 +++++++++++++++
.../review-form/model/review-form.schema.ts | 13 +
src/features/review-form/ui/index.tsx | 228 ++++++++++++++++++
src/pages-templates/about/index.tsx | 31 +--
src/shared/components/rating.tsx | 16 ++
src/shared/components/review.tsx | 24 ++
src/shared/providers/providers.tsx | 11 +-
src/shared/shadcn-ui/textarea.tsx | 22 ++
src/shared/shadcn-ui/tooltip.tsx | 32 +++
10 files changed, 549 insertions(+), 27 deletions(-)
create mode 100644 src/features/review-form/model/review-form.schema.ts
create mode 100644 src/features/review-form/ui/index.tsx
create mode 100644 src/shared/components/rating.tsx
create mode 100644 src/shared/components/review.tsx
create mode 100644 src/shared/shadcn-ui/textarea.tsx
create mode 100644 src/shared/shadcn-ui/tooltip.tsx
diff --git a/package.json b/package.json
index a318bee..0d43e3d 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
"@radix-ui/react-slot": "^1.2.0",
"@radix-ui/react-tabs": "^1.1.8",
"@radix-ui/react-toast": "^1.2.11",
+ "@radix-ui/react-tooltip": "^1.2.6",
"@reduxjs/toolkit": "^2.7.0",
"aos": "^2.3.4",
"axios": "^1.9.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cdecdd5..be73421 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -47,6 +47,9 @@ importers:
'@radix-ui/react-toast':
specifier: ^1.2.11
version: 1.2.11(@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-tooltip':
+ specifier: ^1.2.6
+ version: 1.2.6(@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)
@@ -564,6 +567,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-arrow@1.1.6':
+ resolution: {integrity: sha512-2JMfHJf/eVnwq+2dewT3C0acmCWD3XiVA1Da+jTDqo342UlU13WvXtqHhG+yJw5JeQmu4ue2eMy6gcEArLBlcw==}
+ 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-collapsible@1.1.8':
resolution: {integrity: sha512-hxEsLvK9WxIAPyxdDRULL4hcaSjMZCfP7fHB0Z1uUnDoDBat1Zh46hwYfa69DeZAbJrPckjf0AGAtEZyvDyJbw==}
peerDependencies:
@@ -643,6 +659,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-dismissable-layer@1.1.9':
+ resolution: {integrity: sha512-way197PiTvNp+WBP7svMJasHl+vibhWGQDb6Mgf5mhEWJkgb85z7Lfl9TUdkqpWsf8GRNmoopx9ZxCyDzmgRMQ==}
+ 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-dropdown-menu@2.1.11':
resolution: {integrity: sha512-wbPE3cFBfLl+S+LCxChWQGX0k14zUxgvep1HEnLhJ9mNhjyO3ETzRviAeKZ3XomT/iVRRZAWFsnFZ3N0wI8OmA==}
peerDependencies:
@@ -752,6 +781,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-popper@1.2.6':
+ resolution: {integrity: sha512-7iqXaOWIjDBfIG7aq8CUEeCSsQMLFdn7VEE8TaFz704DtEzpPHR7w/uuzRflvKgltqSAImgcmxQ7fFX3X7wasg==}
+ 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-portal@1.1.6':
resolution: {integrity: sha512-XmsIl2z1n/TsYFLIdYam2rmFwf9OC/Sh2avkbmVMDuBZIe7hSpM0cYnWPAo7nHOVx8zTuwDZGByfcqLdnzp3Vw==}
peerDependencies:
@@ -765,6 +807,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-portal@1.1.8':
+ resolution: {integrity: sha512-hQsTUIn7p7fxCPvao/q6wpbxmCwgLrlz+nOrJgC+RwfZqWY/WN+UMqkXzrtKbPrF82P43eCTl3ekeKuyAQbFeg==}
+ 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-presence@1.1.3':
resolution: {integrity: sha512-IrVLIhskYhH3nLvtcBLQFZr61tBG7wx7O3kEmdzcYwRGAEBmBicGGL7ATzNgruYJ3xBTbuzEEq9OXJM3PAX3tA==}
peerDependencies:
@@ -804,6 +859,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-primitive@2.1.2':
+ resolution: {integrity: sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw==}
+ 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-roving-focus@1.1.7':
resolution: {integrity: sha512-C6oAg451/fQT3EGbWHbCQjYTtbyjNO1uzQgMzwyivcHT3GKNEmu1q3UuREhN+HzHAVtv3ivMVK08QlC+PkYw9Q==}
peerDependencies:
@@ -852,6 +920,15 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-slot@1.2.2':
+ resolution: {integrity: sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@radix-ui/react-tabs@1.1.8':
resolution: {integrity: sha512-4iUaN9SYtG+/E+hJ7jRks/Nv90f+uAsRHbLYA6BcA9EsR6GNWgsvtS4iwU2SP0tOZfDGAyqIT0yz7ckgohEIFA==}
peerDependencies:
@@ -878,6 +955,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-tooltip@1.2.6':
+ resolution: {integrity: sha512-zYb+9dc9tkoN2JjBDIIPLQtk3gGyz8FMKoqYTb8EMVQ5a5hBcdHPECrsZVI4NpPAUOixhkoqg7Hj5ry5USowfA==}
+ 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:
@@ -963,6 +1053,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-visually-hidden@1.2.2':
+ resolution: {integrity: sha512-ORCmRUbNiZIv6uV5mhFrhsIKw4UX/N3syZtyqvry61tbGm4JlgQuSn0hk5TwCARsCjkcnuRkSdCE3xfb+ADHew==}
+ 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/rect@1.1.1':
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
@@ -3102,6 +3205,15 @@ snapshots:
'@types/react': 19.1.2
'@types/react-dom': 19.1.2(@types/react@19.1.2)
+ '@radix-ui/react-arrow@1.1.6(@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/react-primitive': 2.1.2(@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)
+ 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-collapsible@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
@@ -3183,6 +3295,19 @@ snapshots:
'@types/react': 19.1.2
'@types/react-dom': 19.1.2(@types/react@19.1.2)
+ '@radix-ui/react-dismissable-layer@1.1.9(@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-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.2(@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-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-escape-keydown': 1.1.1(@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-dropdown-menu@2.1.11(@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
@@ -3320,6 +3445,24 @@ snapshots:
'@types/react': 19.1.2
'@types/react-dom': 19.1.2(@types/react@19.1.2)
+ '@radix-ui/react-popper@1.2.6(@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:
+ '@floating-ui/react-dom': 2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-arrow': 1.1.6(@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-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.2(@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-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/rect': 1.1.1
+ 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-portal@1.1.6(@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/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)
@@ -3330,6 +3473,16 @@ snapshots:
'@types/react': 19.1.2
'@types/react-dom': 19.1.2(@types/react@19.1.2)
+ '@radix-ui/react-portal@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/react-primitive': 2.1.2(@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-layout-effect': 1.1.1(@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-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)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
@@ -3359,6 +3512,15 @@ snapshots:
'@types/react': 19.1.2
'@types/react-dom': 19.1.2(@types/react@19.1.2)
+ '@radix-ui/react-primitive@2.1.2(@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/react-slot': 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-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)':
dependencies:
'@radix-ui/primitive': 1.1.2
@@ -3421,6 +3583,13 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.2
+ '@radix-ui/react-slot@1.2.2(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ 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
@@ -3457,6 +3626,26 @@ snapshots:
'@types/react': 19.1.2
'@types/react-dom': 19.1.2(@types/react@19.1.2)
+ '@radix-ui/react-tooltip@1.2.6(@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-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.9(@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-id': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-popper': 1.2.6(@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-portal': 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)
+ '@radix-ui/react-presence': 1.1.4(@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.2(@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-slot': 1.2.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-visually-hidden': 1.2.2(@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)
+ 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
@@ -3520,6 +3709,15 @@ snapshots:
'@types/react': 19.1.2
'@types/react-dom': 19.1.2(@types/react@19.1.2)
+ '@radix-ui/react-visually-hidden@1.2.2(@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/react-primitive': 2.1.2(@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)
+ 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/rect@1.1.1': {}
'@reduxjs/toolkit@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)':
diff --git a/src/features/review-form/model/review-form.schema.ts b/src/features/review-form/model/review-form.schema.ts
new file mode 100644
index 0000000..f72335e
--- /dev/null
+++ b/src/features/review-form/model/review-form.schema.ts
@@ -0,0 +1,13 @@
+import { z } from 'zod';
+
+export const reviewSchema = z.object({
+ name: z
+ .string()
+ .min(2, { message: 'Имя должно содержать не менее 2 символов' }),
+ rating: z.number().min(1, { message: 'Пожалуйста, выберите рейтинг' }).max(5),
+ text: z
+ .string()
+ .min(10, { message: 'Отзыв должен содержать не менее 10 символов' }),
+});
+
+export type ReviewFormValues = z.infer;
diff --git a/src/features/review-form/ui/index.tsx b/src/features/review-form/ui/index.tsx
new file mode 100644
index 0000000..07a96a0
--- /dev/null
+++ b/src/features/review-form/ui/index.tsx
@@ -0,0 +1,228 @@
+'use client';
+
+import { zodResolver } from '@hookform/resolvers/zod';
+import { Loader2, Plus, Star } from 'lucide-react';
+import { useState } from 'react';
+import { useForm } from 'react-hook-form';
+import { toast } from 'sonner';
+
+import { Button } from '@/shared/shadcn-ui/button';
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from '@/shared/shadcn-ui/dialog';
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/shared/shadcn-ui/form';
+import { Input } from '@/shared/shadcn-ui/input';
+import { Textarea } from '@/shared/shadcn-ui/textarea';
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from '@/shared/shadcn-ui/tooltip';
+
+import { ReviewFormValues, reviewSchema } from '../model/review-form.schema';
+
+export function ReviewForm() {
+ const [openReviewFormDialog, setOpenReviewFormDialog] = useState(false);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [hoveredStar, setHoveredStar] = useState(0);
+
+ const form = useForm({
+ resolver: zodResolver(reviewSchema),
+ defaultValues: {
+ name: '',
+ rating: 0,
+ text: '',
+ },
+ });
+
+ const onSubmit = async (data: ReviewFormValues) => {
+ setIsSubmitting(true);
+
+ try {
+ await new Promise((resolve) => setTimeout(resolve, 1500));
+
+ toast.success(
+ 'Спасибо за ваш отзыв! Он будет опубликован после модерации.',
+ {
+ duration: 5000,
+ },
+ );
+
+ form.reset();
+ setOpenReviewFormDialog(false);
+ } catch (error) {
+ toast.error(
+ 'Произошла ошибка при отправке отзыва. Пожалуйста, попробуйте позже.',
+ {
+ duration: 5000,
+ },
+ );
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ const StarRating = ({
+ value,
+ onChange,
+ }: {
+ value: number;
+ onChange: (value: number) => void;
+ }) => {
+ return (
+
+ {[1, 2, 3, 4, 5].map((star) => (
+
+ ))}
+
+ );
+ };
+
+ return (
+
+
+
+ );
+}
diff --git a/src/pages-templates/about/index.tsx b/src/pages-templates/about/index.tsx
index 40ea5a9..d7c0167 100644
--- a/src/pages-templates/about/index.tsx
+++ b/src/pages-templates/about/index.tsx
@@ -4,10 +4,13 @@ import { Fuel, History, MapPin, Star, Target, Users } from 'lucide-react';
import Image from 'next/image';
import Link from 'next/link';
+import { Reviews } from '@/app/api-utlities/@types';
import { AboutUsPageData } from '@/app/api-utlities/@types/pages';
import AnimatedCounter from '@/shared/components/animated-counter';
import { Container } from '@/shared/components/container';
+import { Rating } from '@/shared/components/rating';
+import { Review } from '@/shared/components/review';
import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button';
import { Card, CardContent } from '@/shared/shadcn-ui/card';
@@ -15,6 +18,7 @@ import { Card, CardContent } from '@/shared/shadcn-ui/card';
import { CompanyTimeline } from '@/widgets/about-page/company-timeline';
import { StationGallery } from '@/widgets/about-page/station-gallery';
import { CtaSection } from '@/widgets/cta-section';
+import { ReviewForm } from '@/features/review-form/ui';
export interface AboutPageProps {
content: AboutUsPageData;
@@ -127,7 +131,7 @@ export default function AboutPage({ content }: AboutPageProps) {
end={Number(t(`about.stats.items.${index}.value`))}
suffix={
t(`about.stats.items.${index}.suffix`) ===
- `about.stats.items.${index}.suffix`
+ `about.stats.items.${index}.suffix`
? ''
: t(`about.stats.items.${index}.suffix`) || ''
}
@@ -204,9 +208,7 @@ export default function AboutPage({ content }: AboutPageProps) {
-
+
{[0, 1, 2].map((index) => (
{content.reviews.map((review, index) => (
-
-
-
- {Array(5)
- .fill(0)
- .map((_, i) => (
-
- ))}
-
- "{review.review}"
- {review.fullname}
-
-
+
))}
+
diff --git a/src/shared/components/rating.tsx b/src/shared/components/rating.tsx
new file mode 100644
index 0000000..f011288
--- /dev/null
+++ b/src/shared/components/rating.tsx
@@ -0,0 +1,16 @@
+import { Star } from 'lucide-react';
+
+export const Rating = ({ rating }: { rating: number }) => {
+ return (
+ <>
+ {Array(5)
+ .fill(0)
+ .map((_, i) => (
+
+ ))}
+ >
+ );
+};
diff --git a/src/shared/components/review.tsx b/src/shared/components/review.tsx
new file mode 100644
index 0000000..38ee950
--- /dev/null
+++ b/src/shared/components/review.tsx
@@ -0,0 +1,24 @@
+'use client';
+
+import { Reviews } from '@/app/api-utlities/@types';
+
+import { Rating } from '@/shared/components/rating';
+import { Card, CardContent } from '@/shared/shadcn-ui/card';
+
+type ReviewProps = {
+ review: Reviews[number];
+};
+
+export const Review = ({ review }: ReviewProps) => {
+ return (
+
+
+
+
+
+ "{review.review}"
+ {review.fullname}
+
+
+ );
+};
diff --git a/src/shared/providers/providers.tsx b/src/shared/providers/providers.tsx
index b43be29..3d7de22 100644
--- a/src/shared/providers/providers.tsx
+++ b/src/shared/providers/providers.tsx
@@ -1,5 +1,6 @@
'use client';
+import { TooltipProvider } from '@radix-ui/react-tooltip';
import { Provider } from 'react-redux';
import { TextControlProvider } from '../language';
@@ -24,10 +25,12 @@ export const Providers = ({ children, textItems }: ProvidersProps) => {
enableSystem
disableTransitionOnChange
>
-
- {children}
-
-
+
+
+ {children}
+
+
+
diff --git a/src/shared/shadcn-ui/textarea.tsx b/src/shared/shadcn-ui/textarea.tsx
new file mode 100644
index 0000000..1d69af5
--- /dev/null
+++ b/src/shared/shadcn-ui/textarea.tsx
@@ -0,0 +1,22 @@
+import * as React from 'react';
+
+import { cn } from '@/shared/lib/utils';
+
+const Textarea = React.forwardRef<
+ HTMLTextAreaElement,
+ React.ComponentProps<'textarea'>
+>(({ className, ...props }, ref) => {
+ return (
+
+ );
+});
+Textarea.displayName = 'Textarea';
+
+export { Textarea };
diff --git a/src/shared/shadcn-ui/tooltip.tsx b/src/shared/shadcn-ui/tooltip.tsx
new file mode 100644
index 0000000..3546700
--- /dev/null
+++ b/src/shared/shadcn-ui/tooltip.tsx
@@ -0,0 +1,32 @@
+'use client';
+
+import * as TooltipPrimitive from '@radix-ui/react-tooltip';
+import * as React from 'react';
+
+import { cn } from '@/shared/lib/utils';
+
+const TooltipProvider = TooltipPrimitive.Provider;
+
+const Tooltip = TooltipPrimitive.Root;
+
+const TooltipTrigger = TooltipPrimitive.Trigger;
+
+const TooltipContent = React.forwardRef<
+ React.ComponentRef
,
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 4, ...props }, ref) => (
+
+
+
+));
+TooltipContent.displayName = TooltipPrimitive.Content.displayName;
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };