diff --git a/client/src/entities/order/api/admin-order-api.ts b/client/src/entities/order/api/admin-order-api.ts
index 2d5b2f6..43df1d6 100644
--- a/client/src/entities/order/api/admin-order-api.ts
+++ b/client/src/entities/order/api/admin-order-api.ts
@@ -37,7 +37,7 @@ export type AdminOrderDetailResponse = {
comment: string | null
createdAt: string
updatedAt: string
- user: { id: string; email: string; name: string | null; phone: string | null }
+ user: { id: string; email: string; displayName: string | null; phone: string | null }
items: Array<{
id: string
productId: string
diff --git a/client/src/entities/review/api/admin-review-api.ts b/client/src/entities/review/api/admin-review-api.ts
index fdad3b6..b41c1ed 100644
--- a/client/src/entities/review/api/admin-review-api.ts
+++ b/client/src/entities/review/api/admin-review-api.ts
@@ -7,7 +7,7 @@ export type AdminReview = {
status: string
createdAt: string
moderatedAt: string | null
- user: { id: string; email: string; name: string | null }
+ user: { id: string; email: string; displayName: string | null }
product: { id: string; title: string }
}
diff --git a/client/src/features/auth-oauth/__tests__/OAuthButtons.test.tsx b/client/src/features/auth-oauth/__tests__/OAuthButtons.test.tsx
new file mode 100644
index 0000000..d095761
--- /dev/null
+++ b/client/src/features/auth-oauth/__tests__/OAuthButtons.test.tsx
@@ -0,0 +1,19 @@
+import { render, screen } from '@testing-library/react'
+import { describe, it, expect } from 'vitest'
+import { OAuthButtons } from '../ui/OAuthButtons'
+
+describe('OAuthButtons', () => {
+ it('renders Yandex and VK buttons', () => {
+ render()
+ expect(screen.getByText('Войти через Яндекс ID')).toBeDefined()
+ expect(screen.getByText('Войти через VK ID')).toBeDefined()
+ })
+
+ it('buttons have correct href', () => {
+ render()
+ const yaBtn = screen.getByText('Войти через Яндекс ID').closest('a')
+ const vkBtn = screen.getByText('Войти через VK ID').closest('a')
+ expect(yaBtn?.getAttribute('href')).toContain('/auth/oauth/yandex')
+ expect(vkBtn?.getAttribute('href')).toContain('/auth/oauth/vk')
+ })
+})
diff --git a/client/src/features/auth-oauth/index.ts b/client/src/features/auth-oauth/index.ts
new file mode 100644
index 0000000..bbe2712
--- /dev/null
+++ b/client/src/features/auth-oauth/index.ts
@@ -0,0 +1 @@
+export { OAuthButtons } from './ui/OAuthButtons'
diff --git a/client/src/features/auth-oauth/lib/oauth-providers.ts b/client/src/features/auth-oauth/lib/oauth-providers.ts
new file mode 100644
index 0000000..2d1a510
--- /dev/null
+++ b/client/src/features/auth-oauth/lib/oauth-providers.ts
@@ -0,0 +1,24 @@
+import { oauthAuthorizeUrl } from '@/shared/lib/oauth-authorize-url'
+
+export type OAuthProvider = {
+ id: 'yandex' | 'vk'
+ label: string
+ color: string
+}
+
+export const oauthProviders: OAuthProvider[] = [
+ {
+ id: 'yandex',
+ label: 'Яндекс ID',
+ color: '#FC3F1D',
+ },
+ {
+ id: 'vk',
+ label: 'VK ID',
+ color: '#0077FF',
+ },
+]
+
+export function getOAuthUrl(provider: 'yandex' | 'vk'): string {
+ return oauthAuthorizeUrl(provider)
+}
diff --git a/client/src/features/auth-oauth/ui/OAuthButtons.tsx b/client/src/features/auth-oauth/ui/OAuthButtons.tsx
new file mode 100644
index 0000000..18ccc53
--- /dev/null
+++ b/client/src/features/auth-oauth/ui/OAuthButtons.tsx
@@ -0,0 +1,27 @@
+import Button from '@mui/material/Button'
+import Stack from '@mui/material/Stack'
+import { getOAuthUrl, oauthProviders } from '../lib/oauth-providers'
+
+export function OAuthButtons() {
+ return (
+
+ {oauthProviders.map((p) => (
+
+ ))}
+
+ )
+}
diff --git a/client/src/features/user/user-menu/ui/UserMenu.tsx b/client/src/features/user/user-menu/ui/UserMenu.tsx
index 65607dd..ed93c19 100644
--- a/client/src/features/user/user-menu/ui/UserMenu.tsx
+++ b/client/src/features/user/user-menu/ui/UserMenu.tsx
@@ -54,7 +54,7 @@ export function UserMenu({ user, onNavigate, onLogout }: Props) {
{user ? (
<>
>
diff --git a/client/src/pages/auth/ui/AuthPage.tsx b/client/src/pages/auth/ui/AuthPage.tsx
index 11687da..49edb29 100644
--- a/client/src/pages/auth/ui/AuthPage.tsx
+++ b/client/src/pages/auth/ui/AuthPage.tsx
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'
import Alert from '@mui/material/Alert'
import Box from '@mui/material/Box'
import Button from '@mui/material/Button'
+import Divider from '@mui/material/Divider'
import Stack from '@mui/material/Stack'
import TextField from '@mui/material/TextField'
import Typography from '@mui/material/Typography'
@@ -9,10 +10,14 @@ import { useMutation } from '@tanstack/react-query'
import { useUnit } from 'effector-react'
import { useForm } from 'react-hook-form'
import { useNavigate, useSearchParams } from 'react-router-dom'
+import { OAuthButtons } from '@/features/auth-oauth'
import { apiClient } from '@/shared/api/client'
import { $user, tokenSet } from '@/shared/model/auth'
-type AuthResponse = { token: string; user: { id: string; email: string; name?: string | null; phone?: string | null } }
+type AuthResponse = {
+ token: string
+ user: { id: string; email: string; displayName?: string | null; phone?: string | null }
+}
function getApiErrorMessage(err: unknown): string | null {
if (!err || typeof err !== 'object') return null
@@ -108,6 +113,12 @@ export function AuthPage() {
+
+
+ или
+
+
+
)
}
diff --git a/client/src/pages/me/ui/MeLayoutPage.tsx b/client/src/pages/me/ui/MeLayoutPage.tsx
index 5998257..0c24ad1 100644
--- a/client/src/pages/me/ui/MeLayoutPage.tsx
+++ b/client/src/pages/me/ui/MeLayoutPage.tsx
@@ -81,7 +81,7 @@ export function MeLayoutPage() {
Кабинет
- {user.name?.trim() || user.email}
+ {user.displayName?.trim() || user.email}
diff --git a/client/src/pages/me/ui/MePage.tsx b/client/src/pages/me/ui/MePage.tsx
index 3a40bb2..107da67 100644
--- a/client/src/pages/me/ui/MePage.tsx
+++ b/client/src/pages/me/ui/MePage.tsx
@@ -38,8 +38,8 @@ export function MePage() {
mode: 'onChange',
})
- const profileForm = useForm<{ name: string }>({
- defaultValues: { name: user?.name ? String(user.name) : '' },
+ const profileForm = useForm<{ displayName: string }>({
+ defaultValues: { displayName: user?.displayName ? String(user.displayName) : '' },
mode: 'onChange',
})
@@ -80,15 +80,15 @@ export function MePage() {
label="Имя или ник"
helperText="До 40 символов"
slotProps={{ htmlInput: { maxLength: 40 } }}
- {...profileForm.register('name')}
+ {...profileForm.register('displayName')}
/>