update: make login page
This commit is contained in:
parent
1a679d029e
commit
e2e13129ed
@ -14,10 +14,12 @@
|
|||||||
"@radix-ui/react-collapsible": "^1.1.8",
|
"@radix-ui/react-collapsible": "^1.1.8",
|
||||||
"@radix-ui/react-dialog": "^1.1.11",
|
"@radix-ui/react-dialog": "^1.1.11",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.11",
|
"@radix-ui/react-dropdown-menu": "^2.1.11",
|
||||||
|
"@radix-ui/react-label": "^2.1.4",
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.10",
|
"@radix-ui/react-navigation-menu": "^1.2.10",
|
||||||
"@radix-ui/react-select": "^2.2.2",
|
"@radix-ui/react-select": "^2.2.2",
|
||||||
"@radix-ui/react-slot": "^1.2.0",
|
"@radix-ui/react-slot": "^1.2.0",
|
||||||
"@radix-ui/react-tabs": "^1.1.8",
|
"@radix-ui/react-tabs": "^1.1.8",
|
||||||
|
"@radix-ui/react-toast": "^1.2.11",
|
||||||
"@reduxjs/toolkit": "^2.7.0",
|
"@reduxjs/toolkit": "^2.7.0",
|
||||||
"aos": "^2.3.4",
|
"aos": "^2.3.4",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
@ -29,7 +31,9 @@
|
|||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-hook-form": "^7.56.1",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
|
"sonner": "^2.0.3",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"tw-animate-css": "^1.2.6",
|
"tw-animate-css": "^1.2.6",
|
||||||
|
|||||||
90
pnpm-lock.yaml
generated
90
pnpm-lock.yaml
generated
@ -10,7 +10,7 @@ importers:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@hookform/resolvers':
|
'@hookform/resolvers':
|
||||||
specifier: ^5.0.1
|
specifier: ^5.0.1
|
||||||
version: 5.0.1(react-hook-form@7.56.0(react@19.1.0))
|
version: 5.0.1(react-hook-form@7.56.1(react@19.1.0))
|
||||||
'@radix-ui/react-collapsible':
|
'@radix-ui/react-collapsible':
|
||||||
specifier: ^1.1.8
|
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)
|
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)
|
||||||
@ -20,6 +20,9 @@ importers:
|
|||||||
'@radix-ui/react-dropdown-menu':
|
'@radix-ui/react-dropdown-menu':
|
||||||
specifier: ^2.1.11
|
specifier: ^2.1.11
|
||||||
version: 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)
|
version: 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)
|
||||||
|
'@radix-ui/react-label':
|
||||||
|
specifier: ^2.1.4
|
||||||
|
version: 2.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-navigation-menu':
|
'@radix-ui/react-navigation-menu':
|
||||||
specifier: ^1.2.10
|
specifier: ^1.2.10
|
||||||
version: 1.2.10(@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)
|
version: 1.2.10(@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)
|
||||||
@ -32,6 +35,9 @@ importers:
|
|||||||
'@radix-ui/react-tabs':
|
'@radix-ui/react-tabs':
|
||||||
specifier: ^1.1.8
|
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)
|
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)
|
||||||
|
'@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)
|
||||||
'@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)
|
||||||
@ -65,9 +71,15 @@ importers:
|
|||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^19.0.0
|
specifier: ^19.0.0
|
||||||
version: 19.1.0(react@19.1.0)
|
version: 19.1.0(react@19.1.0)
|
||||||
|
react-hook-form:
|
||||||
|
specifier: ^7.56.1
|
||||||
|
version: 7.56.1(react@19.1.0)
|
||||||
react-redux:
|
react-redux:
|
||||||
specifier: ^9.2.0
|
specifier: ^9.2.0
|
||||||
version: 9.2.0(@types/react@19.1.2)(react@19.1.0)(redux@5.0.1)
|
version: 9.2.0(@types/react@19.1.2)(react@19.1.0)(redux@5.0.1)
|
||||||
|
sonner:
|
||||||
|
specifier: ^2.0.3
|
||||||
|
version: 2.0.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
tailwind-merge:
|
tailwind-merge:
|
||||||
specifier: ^3.2.0
|
specifier: ^3.2.0
|
||||||
version: 3.2.0
|
version: 3.2.0
|
||||||
@ -630,6 +642,19 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-label@2.1.4':
|
||||||
|
resolution: {integrity: sha512-wy3dqizZnZVV4ja0FNnUhIWNwWdoldXrneEyUcVtLYDAt8ovGS4ridtMAOGgXBBIfggL4BOveVWsjXDORdGEQg==}
|
||||||
|
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-menu@2.1.11':
|
'@radix-ui/react-menu@2.1.11':
|
||||||
resolution: {integrity: sha512-sbFI4Qaw02J0ogmR9tOMsSqsdrGNpUanlPYAqTE2JJafow8ecHtykg4fSTjNHBdDl4deiKMK+RhTEwyVhP7UDA==}
|
resolution: {integrity: sha512-sbFI4Qaw02J0ogmR9tOMsSqsdrGNpUanlPYAqTE2JJafow8ecHtykg4fSTjNHBdDl4deiKMK+RhTEwyVhP7UDA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -769,6 +794,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-toast@1.2.11':
|
||||||
|
resolution: {integrity: sha512-Ed2mlOmT+tktOsu2NZBK1bCSHh/uqULu1vWOkpQTVq53EoOuZUZw7FInQoDB3uil5wZc2oe0XN9a7uVZB7/6AQ==}
|
||||||
|
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:
|
||||||
@ -2228,8 +2266,8 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^19.1.0
|
react: ^19.1.0
|
||||||
|
|
||||||
react-hook-form@7.56.0:
|
react-hook-form@7.56.1:
|
||||||
resolution: {integrity: sha512-U2QQgx5z2Y8Z0qlXv3W19hWHJgfKdWMz0O/osuY+o+CYq568V2R/JhzC6OAXfR8k24rIN0Muan2Qliaq9eKs/g==}
|
resolution: {integrity: sha512-qWAVokhSpshhcEuQDSANHx3jiAEFzu2HAaaQIzi/r9FNPm1ioAvuJSD4EuZzWd7Al7nTRKcKPnBKO7sRn+zavQ==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||||
@ -2392,6 +2430,12 @@ packages:
|
|||||||
simple-swizzle@0.2.2:
|
simple-swizzle@0.2.2:
|
||||||
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
|
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
|
||||||
|
|
||||||
|
sonner@2.0.3:
|
||||||
|
resolution: {integrity: sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
|
||||||
source-map-js@1.2.1:
|
source-map-js@1.2.1:
|
||||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -2721,10 +2765,10 @@ snapshots:
|
|||||||
|
|
||||||
'@floating-ui/utils@0.2.9': {}
|
'@floating-ui/utils@0.2.9': {}
|
||||||
|
|
||||||
'@hookform/resolvers@5.0.1(react-hook-form@7.56.0(react@19.1.0))':
|
'@hookform/resolvers@5.0.1(react-hook-form@7.56.1(react@19.1.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@standard-schema/utils': 0.3.0
|
'@standard-schema/utils': 0.3.0
|
||||||
react-hook-form: 7.56.0(react@19.1.0)
|
react-hook-form: 7.56.1(react@19.1.0)
|
||||||
|
|
||||||
'@humanfs/core@0.19.1': {}
|
'@humanfs/core@0.19.1': {}
|
||||||
|
|
||||||
@ -3020,6 +3064,15 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
|
|
||||||
|
'@radix-ui/react-label@2.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)':
|
||||||
|
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)
|
||||||
|
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-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)':
|
'@radix-ui/react-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:
|
dependencies:
|
||||||
'@radix-ui/primitive': 1.1.2
|
'@radix-ui/primitive': 1.1.2
|
||||||
@ -3194,6 +3247,26 @@ snapshots:
|
|||||||
'@types/react': 19.1.2
|
'@types/react': 19.1.2
|
||||||
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||||
|
|
||||||
|
'@radix-ui/react-toast@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)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.2
|
||||||
|
'@radix-ui/react-collection': 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-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.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-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)
|
||||||
|
'@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.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-use-callback-ref': 1.1.1(@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-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||||
|
'@radix-ui/react-visually-hidden': 1.2.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)
|
||||||
|
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
|
||||||
@ -4714,7 +4787,7 @@ snapshots:
|
|||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
scheduler: 0.26.0
|
scheduler: 0.26.0
|
||||||
|
|
||||||
react-hook-form@7.56.0(react@19.1.0):
|
react-hook-form@7.56.1(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
|
|
||||||
@ -4922,6 +4995,11 @@ snapshots:
|
|||||||
is-arrayish: 0.3.2
|
is-arrayish: 0.3.2
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
sonner@2.0.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
|
||||||
source-map-js@1.2.1: {}
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
stable-hash@0.0.5: {}
|
stable-hash@0.0.5: {}
|
||||||
|
|||||||
93
src/app/login/page.tsx
Normal file
93
src/app/login/page.tsx
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { Building2, Fuel, User } from 'lucide-react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
import { LoginForm } from '@/features/auth/login-form';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@/shared/shadcn-ui/card';
|
||||||
|
import {
|
||||||
|
Tabs,
|
||||||
|
TabsContent,
|
||||||
|
TabsList,
|
||||||
|
TabsTrigger,
|
||||||
|
} from '@/shared/shadcn-ui/tabs';
|
||||||
|
|
||||||
|
export default function LoginPage() {
|
||||||
|
return (
|
||||||
|
<div className='flex min-h-screen flex-col'>
|
||||||
|
<main className='flex-1'>
|
||||||
|
<div className='container max-w-6xl py-16'>
|
||||||
|
<div className='mb-12 flex flex-col items-center text-center'>
|
||||||
|
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
|
||||||
|
<Fuel className='h-6 w-6 text-red-600' />
|
||||||
|
</div>
|
||||||
|
<h1 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl'>
|
||||||
|
Вход в личный кабинет
|
||||||
|
</h1>
|
||||||
|
<p className='max-w-2xl text-gray-600'>
|
||||||
|
Войдите в личный кабинет, чтобы получить доступ к информации о
|
||||||
|
ваших бонусах, истории операций и другим возможностям.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mx-auto max-w-md'>
|
||||||
|
<Tabs defaultValue='bonus' className='w-full'>
|
||||||
|
<TabsList className='mb-8 grid w-full grid-cols-2'>
|
||||||
|
<TabsTrigger value='bonus' className='text-base'>
|
||||||
|
<User className='mr-2 h-4 w-4' /> Бонусный клиент
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value='corporate' className='text-base'>
|
||||||
|
<Building2 className='mr-2 h-4 w-4' /> Корпоративный клиент
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsContent value='bonus'>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Вход для бонусных клиентов</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Введите номер телефона и номер бонусной карты для входа в
|
||||||
|
личный кабинет.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className='space-y-4'>
|
||||||
|
<LoginForm />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value='corporate'>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Вход для корпоративных клиентов</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Введите номер телефона и номер корпоративной карты для
|
||||||
|
входа в личный кабинет.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className='space-y-4'>
|
||||||
|
<LoginForm />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
<div className='mt-8 text-center text-sm text-gray-500'>
|
||||||
|
<p>
|
||||||
|
Возникли проблемы со входом?{' '}
|
||||||
|
<Link href='/contact' className='text-red-600 hover:underline'>
|
||||||
|
Свяжитесь с нами
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
1
src/features/auth/login-form/index.ts
Normal file
1
src/features/auth/login-form/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { LoginForm } from './ui/login-form';
|
||||||
18
src/features/auth/login-form/model/login-form.schema.ts
Normal file
18
src/features/auth/login-form/model/login-form.schema.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const loginFormSchema = z.object({
|
||||||
|
phoneNumber: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.regex(/^[0-9+\-() ]*$/, {
|
||||||
|
message:
|
||||||
|
'Phone number can only contain numbers, spaces, and the following symbols: + - ( )',
|
||||||
|
})
|
||||||
|
.refine((val) => !val || val.length >= 5, {
|
||||||
|
message:
|
||||||
|
'Phone number is too short. Please enter a complete phone number',
|
||||||
|
}),
|
||||||
|
cardNumber: z.string().min(16).trim(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type LoginFormData = z.infer<typeof loginFormSchema>;
|
||||||
108
src/features/auth/login-form/ui/login-form.tsx
Normal file
108
src/features/auth/login-form/ui/login-form.tsx
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { Button } from '@/shared/shadcn-ui/button';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from '@/shared/shadcn-ui/form';
|
||||||
|
import { Input } from '@/shared/shadcn-ui/input';
|
||||||
|
|
||||||
|
import { LoginFormData, loginFormSchema } from '../model/login-form.schema';
|
||||||
|
|
||||||
|
interface LoginFormProps {
|
||||||
|
// onSubmit: (data: any) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoginForm = ({}: LoginFormProps) => {
|
||||||
|
const router = useRouter();
|
||||||
|
// const [login, results] = useLoginMutation();
|
||||||
|
|
||||||
|
const form = useForm<LoginFormData>({
|
||||||
|
resolver: zodResolver(loginFormSchema),
|
||||||
|
defaultValues: {
|
||||||
|
phoneNumber: '',
|
||||||
|
cardNumber: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = async (data: LoginFormData) => {
|
||||||
|
// const response = await login(data).unwrap();
|
||||||
|
// const user = response.data;
|
||||||
|
// dispatch(
|
||||||
|
// setCredentials({
|
||||||
|
// user: {
|
||||||
|
// accessToken: user.accessToken,
|
||||||
|
// affiliateId: user.affiliateId,
|
||||||
|
// email: user.email,
|
||||||
|
// id: user.id,
|
||||||
|
// role: user.role,
|
||||||
|
// username: user.username,
|
||||||
|
// },
|
||||||
|
// }),
|
||||||
|
// );
|
||||||
|
toast.success('Logged in successfully!');
|
||||||
|
|
||||||
|
router.push('/customer-dashboard');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-4'>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='phoneNumber'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Номер телефона</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type='tel'
|
||||||
|
placeholder='+992 XXX XX XX XX'
|
||||||
|
{...field}
|
||||||
|
inputMode='tel'
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='cardNumber'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Номер карты</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type='text'
|
||||||
|
placeholder='XXXX-XXXX-XXXX-XXXX'
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
// isLoading={results.isLoading}
|
||||||
|
// title='Login'
|
||||||
|
type='submit'
|
||||||
|
className='w-full'
|
||||||
|
// variant={'default'}
|
||||||
|
// disabled={loading}
|
||||||
|
>
|
||||||
|
Войти
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -5,6 +5,7 @@ import { Provider } from 'react-redux';
|
|||||||
import { store } from '../store';
|
import { store } from '../store';
|
||||||
import { ThemeProvider } from '../theme/theme-provider';
|
import { ThemeProvider } from '../theme/theme-provider';
|
||||||
import { AosProvider } from './aos-provider';
|
import { AosProvider } from './aos-provider';
|
||||||
|
import { Toaster } from './toaster';
|
||||||
|
|
||||||
type ProvidersProps = {
|
type ProvidersProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@ -19,7 +20,10 @@ export const Providers = ({ children }: ProvidersProps) => {
|
|||||||
enableSystem
|
enableSystem
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
<AosProvider>{children}</AosProvider>
|
<AosProvider>
|
||||||
|
{children}
|
||||||
|
<Toaster />
|
||||||
|
</AosProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
31
src/shared/providers/toaster.tsx
Normal file
31
src/shared/providers/toaster.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useTheme } from 'next-themes';
|
||||||
|
import { Toaster as Sonner } from 'sonner';
|
||||||
|
|
||||||
|
type ToasterProps = React.ComponentProps<typeof Sonner>;
|
||||||
|
|
||||||
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
|
const { theme = 'system' } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sonner
|
||||||
|
theme={theme as ToasterProps['theme']}
|
||||||
|
className='toaster group'
|
||||||
|
toastOptions={{
|
||||||
|
classNames: {
|
||||||
|
toast:
|
||||||
|
'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
|
||||||
|
description: 'group-[.toast]:text-muted-foreground',
|
||||||
|
actionButton:
|
||||||
|
'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
|
||||||
|
cancelButton:
|
||||||
|
'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Toaster };
|
||||||
179
src/shared/shadcn-ui/form.tsx
Normal file
179
src/shared/shadcn-ui/form.tsx
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as LabelPrimitive from '@radix-ui/react-label';
|
||||||
|
import { Slot } from '@radix-ui/react-slot';
|
||||||
|
import * as React from 'react';
|
||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
type ControllerProps,
|
||||||
|
type FieldPath,
|
||||||
|
type FieldValues,
|
||||||
|
FormProvider,
|
||||||
|
useFormContext,
|
||||||
|
} from 'react-hook-form';
|
||||||
|
|
||||||
|
import { cn } from '@/shared/lib/utils';
|
||||||
|
import { Label } from '@/shared/shadcn-ui/label';
|
||||||
|
|
||||||
|
const Form = FormProvider;
|
||||||
|
|
||||||
|
type FormFieldContextValue<
|
||||||
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||||
|
> = {
|
||||||
|
name: TName;
|
||||||
|
};
|
||||||
|
|
||||||
|
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||||
|
{} as FormFieldContextValue,
|
||||||
|
);
|
||||||
|
|
||||||
|
const FormField = <
|
||||||
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||||
|
>({
|
||||||
|
...props
|
||||||
|
}: ControllerProps<TFieldValues, TName>) => {
|
||||||
|
return (
|
||||||
|
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||||
|
<Controller {...props} />
|
||||||
|
</FormFieldContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const useFormField = () => {
|
||||||
|
const fieldContext = React.useContext(FormFieldContext);
|
||||||
|
const itemContext = React.useContext(FormItemContext);
|
||||||
|
const { getFieldState, formState } = useFormContext();
|
||||||
|
|
||||||
|
const fieldState = getFieldState(fieldContext.name, formState);
|
||||||
|
|
||||||
|
if (!fieldContext) {
|
||||||
|
throw new Error('useFormField should be used within <FormField>');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = itemContext;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: fieldContext.name,
|
||||||
|
formItemId: `${id}-form-item`,
|
||||||
|
formDescriptionId: `${id}-form-item-description`,
|
||||||
|
formMessageId: `${id}-form-item-message`,
|
||||||
|
...fieldState,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type FormItemContextValue = {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||||
|
{} as FormItemContextValue,
|
||||||
|
);
|
||||||
|
|
||||||
|
const FormItem = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const id = React.useId();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItemContext.Provider value={{ id }}>
|
||||||
|
<div ref={ref} className={cn('space-y-2', className)} {...props} />
|
||||||
|
</FormItemContext.Provider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
FormItem.displayName = 'FormItem';
|
||||||
|
|
||||||
|
const FormLabel = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof LabelPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const { error, formItemId } = useFormField();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn(error && 'text-destructive', className)}
|
||||||
|
htmlFor={formItemId}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
FormLabel.displayName = 'FormLabel';
|
||||||
|
|
||||||
|
const FormControl = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof Slot>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof Slot>
|
||||||
|
>(({ ...props }, ref) => {
|
||||||
|
const { error, formItemId, formDescriptionId, formMessageId } =
|
||||||
|
useFormField();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Slot
|
||||||
|
ref={ref}
|
||||||
|
id={formItemId}
|
||||||
|
aria-describedby={
|
||||||
|
!error
|
||||||
|
? `${formDescriptionId}`
|
||||||
|
: `${formDescriptionId} ${formMessageId}`
|
||||||
|
}
|
||||||
|
aria-invalid={!!error}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
FormControl.displayName = 'FormControl';
|
||||||
|
|
||||||
|
const FormDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const { formDescriptionId } = useFormField();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
ref={ref}
|
||||||
|
id={formDescriptionId}
|
||||||
|
className={cn('text-muted-foreground text-sm', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
FormDescription.displayName = 'FormDescription';
|
||||||
|
|
||||||
|
const FormMessage = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, children, ...props }, ref) => {
|
||||||
|
const { error, formMessageId } = useFormField();
|
||||||
|
const body = error ? String(error?.message ?? '') : children;
|
||||||
|
|
||||||
|
if (!body) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
ref={ref}
|
||||||
|
id={formMessageId}
|
||||||
|
className={cn('text-destructive text-sm font-medium', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{body}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
FormMessage.displayName = 'FormMessage';
|
||||||
|
|
||||||
|
export {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
useFormField,
|
||||||
|
};
|
||||||
22
src/shared/shadcn-ui/input.tsx
Normal file
22
src/shared/shadcn-ui/input.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { cn } from '@/shared/lib/utils';
|
||||||
|
|
||||||
|
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
|
||||||
|
({ className, type, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
className={cn(
|
||||||
|
'border-input bg-background ring-offset-background file:text-foreground placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-base file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Input.displayName = 'Input';
|
||||||
|
|
||||||
|
export { Input };
|
||||||
26
src/shared/shadcn-ui/label.tsx
Normal file
26
src/shared/shadcn-ui/label.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as LabelPrimitive from '@radix-ui/react-label';
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { cn } from '@/shared/lib/utils';
|
||||||
|
|
||||||
|
const labelVariants = cva(
|
||||||
|
'text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||||
|
);
|
||||||
|
|
||||||
|
const Label = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof LabelPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||||
|
VariantProps<typeof labelVariants>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(labelVariants(), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
Label.displayName = LabelPrimitive.Root.displayName;
|
||||||
|
|
||||||
|
export { Label };
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
import { Logo } from '@/shared/assets/logo';
|
import { Logo } from '@/shared/assets/logo';
|
||||||
import { Button } from '@/shared/shadcn-ui/button';
|
import { Button } from '@/shared/shadcn-ui/button';
|
||||||
|
|
||||||
@ -12,7 +14,9 @@ export function Header() {
|
|||||||
<DesktopNav />
|
<DesktopNav />
|
||||||
<div className='flex items-center gap-6 md:contents'>
|
<div className='flex items-center gap-6 md:contents'>
|
||||||
<MobileNav />
|
<MobileNav />
|
||||||
|
<Link href={'/login'}>
|
||||||
<Button>Вход</Button>
|
<Button>Вход</Button>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user