refactor(client): remove avatarType, add auth effects, simplify UserAvatar
This commit is contained in:
@@ -56,7 +56,6 @@ export function OrderChat({ messages, isPending, onSend }: Props) {
|
|||||||
<UserAvatar
|
<UserAvatar
|
||||||
userId="admin"
|
userId="admin"
|
||||||
avatarUrl={adminAv?.avatar}
|
avatarUrl={adminAv?.avatar}
|
||||||
avatarType={adminAv?.avatarType}
|
|
||||||
avatarStyle={adminAv?.avatarStyle}
|
avatarStyle={adminAv?.avatarStyle}
|
||||||
size={24}
|
size={24}
|
||||||
/>
|
/>
|
||||||
@@ -64,7 +63,6 @@ export function OrderChat({ messages, isPending, onSend }: Props) {
|
|||||||
<UserAvatar
|
<UserAvatar
|
||||||
userId={currentUser.id}
|
userId={currentUser.id}
|
||||||
avatarUrl={currentUser.avatar}
|
avatarUrl={currentUser.avatar}
|
||||||
avatarType={currentUser.avatarType}
|
|
||||||
avatarStyle={currentUser.avatarStyle}
|
avatarStyle={currentUser.avatarStyle}
|
||||||
size={24}
|
size={24}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -175,7 +175,6 @@ export function OrderDetailContent({ detail, orderId }: { detail: AdminOrderDeta
|
|||||||
<UserAvatar
|
<UserAvatar
|
||||||
userId={currentUser.id}
|
userId={currentUser.id}
|
||||||
avatarUrl={currentUser.avatar}
|
avatarUrl={currentUser.avatar}
|
||||||
avatarType={currentUser.avatarType}
|
|
||||||
avatarStyle={currentUser.avatarStyle}
|
avatarStyle={currentUser.avatarStyle}
|
||||||
size={24}
|
size={24}
|
||||||
/>
|
/>
|
||||||
@@ -184,7 +183,6 @@ export function OrderDetailContent({ detail, orderId }: { detail: AdminOrderDeta
|
|||||||
<UserAvatar
|
<UserAvatar
|
||||||
userId={detail.user.id}
|
userId={detail.user.id}
|
||||||
avatarUrl={detail.user.avatar}
|
avatarUrl={detail.user.avatar}
|
||||||
avatarType={detail.user.avatarType}
|
|
||||||
avatarStyle={detail.user.avatarStyle}
|
avatarStyle={detail.user.avatarStyle}
|
||||||
size={24}
|
size={24}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ function ReviewItem({ rv }: { rv: PublicProductReviewItem }) {
|
|||||||
<UserAvatar
|
<UserAvatar
|
||||||
userId={rv.authorDisplay}
|
userId={rv.authorDisplay}
|
||||||
avatarUrl={rv.authorAvatar}
|
avatarUrl={rv.authorAvatar}
|
||||||
avatarType={rv.authorAvatarType}
|
|
||||||
avatarStyle={rv.authorAvatarStyle}
|
avatarStyle={rv.authorAvatarStyle}
|
||||||
size={32}
|
size={32}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ export function UserMenu({ user, isAdmin = false, onNavigate, onLogout }: Props)
|
|||||||
<UserAvatar
|
<UserAvatar
|
||||||
userId={user.id}
|
userId={user.id}
|
||||||
avatarUrl={user.avatar}
|
avatarUrl={user.avatar}
|
||||||
avatarType={user.avatarType}
|
|
||||||
avatarStyle={user.avatarStyle}
|
avatarStyle={user.avatarStyle}
|
||||||
size={28}
|
size={28}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -56,9 +56,7 @@ export function AdminSettingsPage() {
|
|||||||
mode: 'onChange',
|
mode: 'onChange',
|
||||||
})
|
})
|
||||||
|
|
||||||
const hasOAuthAvatar = Boolean(user?.avatar && user.avatarType !== 'generated')
|
const hasAvatar = Boolean(user?.avatar)
|
||||||
const useOAuth = user?.avatarType === 'oauth'
|
|
||||||
const useGenerated = user?.avatarType === 'generated'
|
|
||||||
|
|
||||||
const [selectedStyle, setSelectedStyle] = useState(user?.avatarStyle || DEFAULT_STYLE_ID)
|
const [selectedStyle, setSelectedStyle] = useState(user?.avatarStyle || DEFAULT_STYLE_ID)
|
||||||
const [previewSrc, setPreviewSrc] = useState<string | null>(null)
|
const [previewSrc, setPreviewSrc] = useState<string | null>(null)
|
||||||
@@ -70,14 +68,12 @@ export function AdminSettingsPage() {
|
|||||||
mutationFn: (params: {
|
mutationFn: (params: {
|
||||||
displayName: string | null
|
displayName: string | null
|
||||||
avatar?: string | null
|
avatar?: string | null
|
||||||
avatarType?: string | null
|
|
||||||
avatarStyle?: string | null
|
avatarStyle?: string | null
|
||||||
}) => apiClient.patch('admin/profile', params),
|
}) => apiClient.patch('admin/profile', params),
|
||||||
onSuccess: (_data, variables) => {
|
onSuccess: (_data, variables) => {
|
||||||
const p: UpdateProfileParams = { displayName: variables.displayName ?? null }
|
const p: UpdateProfileParams = { displayName: variables.displayName ?? null }
|
||||||
if (variables.avatar !== undefined) {
|
if (variables.avatar !== undefined) {
|
||||||
p.avatar = variables.avatar
|
p.avatar = variables.avatar
|
||||||
p.avatarType = variables.avatarType ?? null
|
|
||||||
p.avatarStyle = variables.avatarStyle ?? null
|
p.avatarStyle = variables.avatarStyle ?? null
|
||||||
}
|
}
|
||||||
updateProfileFx(p)
|
updateProfileFx(p)
|
||||||
@@ -144,7 +140,6 @@ export function AdminSettingsPage() {
|
|||||||
<UserAvatar
|
<UserAvatar
|
||||||
userId={String(user.id)}
|
userId={String(user.id)}
|
||||||
avatarUrl={hasUnsavedPreview ? previewSrc : user.avatar}
|
avatarUrl={hasUnsavedPreview ? previewSrc : user.avatar}
|
||||||
avatarType={hasUnsavedPreview ? 'generated' : user.avatarType}
|
|
||||||
avatarStyle={hasUnsavedPreview ? previewStyle : user.avatarStyle}
|
avatarStyle={hasUnsavedPreview ? previewStyle : user.avatarStyle}
|
||||||
size={80}
|
size={80}
|
||||||
sx={{
|
sx={{
|
||||||
@@ -153,7 +148,7 @@ export function AdminSettingsPage() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.5 }}>
|
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.5 }}>
|
||||||
{hasUnsavedPreview ? 'Предпросмотр' : useOAuth ? 'Сохранён' : useGenerated ? 'Сохранён' : 'Авто'}
|
{hasUnsavedPreview ? 'Предпросмотр' : hasAvatar ? 'Сохранён' : 'Авто'}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
{hasUnsavedPreview && (
|
{hasUnsavedPreview && (
|
||||||
@@ -161,7 +156,6 @@ export function AdminSettingsPage() {
|
|||||||
<UserAvatar
|
<UserAvatar
|
||||||
userId={String(user.id)}
|
userId={String(user.id)}
|
||||||
avatarUrl={user.avatar}
|
avatarUrl={user.avatar}
|
||||||
avatarType={user.avatarType}
|
|
||||||
avatarStyle={user.avatarStyle}
|
avatarStyle={user.avatarStyle}
|
||||||
size={80}
|
size={80}
|
||||||
sx={{ border: 2, borderColor: 'divider', opacity: 0.6 }}
|
sx={{ border: 2, borderColor: 'divider', opacity: 0.6 }}
|
||||||
@@ -209,7 +203,6 @@ export function AdminSettingsPage() {
|
|||||||
profileSaveMut.mutate({
|
profileSaveMut.mutate({
|
||||||
displayName: name.length ? name : null,
|
displayName: name.length ? name : null,
|
||||||
avatar: previewSrc,
|
avatar: previewSrc,
|
||||||
avatarType: 'generated',
|
|
||||||
avatarStyle: previewStyle,
|
avatarStyle: previewStyle,
|
||||||
})
|
})
|
||||||
setPreviewSrc(null)
|
setPreviewSrc(null)
|
||||||
@@ -222,24 +215,6 @@ export function AdminSettingsPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{hasOAuthAvatar && !hasUnsavedPreview && (
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
disabled={pendingProfile || profileSaveMut.isPending || useOAuth}
|
|
||||||
onClick={() => {
|
|
||||||
const raw = profileForm.getValues('displayName')
|
|
||||||
const name = raw.trim()
|
|
||||||
profileSaveMut.mutate({
|
|
||||||
displayName: name.length ? name : null,
|
|
||||||
avatarType: 'oauth',
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
sx={{ mt: 0.5 }}
|
|
||||||
>
|
|
||||||
Использовать OAuth
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -195,7 +195,6 @@ export function AdminUsersPage() {
|
|||||||
<UserAvatar
|
<UserAvatar
|
||||||
userId={u.id}
|
userId={u.id}
|
||||||
avatarUrl={u.avatar}
|
avatarUrl={u.avatar}
|
||||||
avatarType={u.avatarType}
|
|
||||||
avatarStyle={u.avatarStyle}
|
avatarStyle={u.avatarStyle}
|
||||||
size={28}
|
size={28}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -181,7 +181,6 @@ export function MessagesPage() {
|
|||||||
<UserAvatar
|
<UserAvatar
|
||||||
userId="admin"
|
userId="admin"
|
||||||
avatarUrl={adminAv?.avatar}
|
avatarUrl={adminAv?.avatar}
|
||||||
avatarType={adminAv?.avatarType}
|
|
||||||
avatarStyle={adminAv?.avatarStyle}
|
avatarStyle={adminAv?.avatarStyle}
|
||||||
size={24}
|
size={24}
|
||||||
/>
|
/>
|
||||||
@@ -189,7 +188,6 @@ export function MessagesPage() {
|
|||||||
<UserAvatar
|
<UserAvatar
|
||||||
userId={currentUser.id}
|
userId={currentUser.id}
|
||||||
avatarUrl={currentUser.avatar}
|
avatarUrl={currentUser.avatar}
|
||||||
avatarType={currentUser.avatarType}
|
|
||||||
avatarStyle={currentUser.avatarStyle}
|
avatarStyle={currentUser.avatarStyle}
|
||||||
size={24}
|
size={24}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -56,9 +56,7 @@ export function SettingsPage() {
|
|||||||
const emailErrorMsg = getApiErrorMessage(errorEmailReq) ?? getApiErrorMessage(errorEmailVerify)
|
const emailErrorMsg = getApiErrorMessage(errorEmailReq) ?? getApiErrorMessage(errorEmailVerify)
|
||||||
const profileErrorMsg = getApiErrorMessage(errorProfile)
|
const profileErrorMsg = getApiErrorMessage(errorProfile)
|
||||||
|
|
||||||
const hasOAuthAvatar = Boolean(user?.avatar && user.avatarType !== 'generated')
|
const hasAvatar = Boolean(user?.avatar)
|
||||||
const useOAuth = user?.avatarType === 'oauth'
|
|
||||||
const useGenerated = user?.avatarType === 'generated'
|
|
||||||
|
|
||||||
const [selectedStyle, setSelectedStyle] = useState(user?.avatarStyle || DEFAULT_STYLE_ID)
|
const [selectedStyle, setSelectedStyle] = useState(user?.avatarStyle || DEFAULT_STYLE_ID)
|
||||||
const [previewSrc, setPreviewSrc] = useState<string | null>(null)
|
const [previewSrc, setPreviewSrc] = useState<string | null>(null)
|
||||||
@@ -128,7 +126,6 @@ export function SettingsPage() {
|
|||||||
<UserAvatar
|
<UserAvatar
|
||||||
userId={user.id}
|
userId={user.id}
|
||||||
avatarUrl={hasUnsavedPreview ? previewSrc : user.avatar}
|
avatarUrl={hasUnsavedPreview ? previewSrc : user.avatar}
|
||||||
avatarType={hasUnsavedPreview ? 'generated' : user.avatarType}
|
|
||||||
avatarStyle={hasUnsavedPreview ? previewStyle : user.avatarStyle}
|
avatarStyle={hasUnsavedPreview ? previewStyle : user.avatarStyle}
|
||||||
size={80}
|
size={80}
|
||||||
sx={{
|
sx={{
|
||||||
@@ -137,7 +134,7 @@ export function SettingsPage() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.5 }}>
|
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.5 }}>
|
||||||
{hasUnsavedPreview ? 'Предпросмотр' : useOAuth ? 'Сохранён' : useGenerated ? 'Сохранён' : 'Авто'}
|
{hasUnsavedPreview ? 'Предпросмотр' : hasAvatar ? 'Сохранён' : 'Авто'}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
{hasUnsavedPreview && (
|
{hasUnsavedPreview && (
|
||||||
@@ -145,7 +142,6 @@ export function SettingsPage() {
|
|||||||
<UserAvatar
|
<UserAvatar
|
||||||
userId={user.id}
|
userId={user.id}
|
||||||
avatarUrl={user.avatar}
|
avatarUrl={user.avatar}
|
||||||
avatarType={user.avatarType}
|
|
||||||
avatarStyle={user.avatarStyle}
|
avatarStyle={user.avatarStyle}
|
||||||
size={80}
|
size={80}
|
||||||
sx={{ border: 2, borderColor: 'divider', opacity: 0.6 }}
|
sx={{ border: 2, borderColor: 'divider', opacity: 0.6 }}
|
||||||
@@ -191,7 +187,6 @@ export function SettingsPage() {
|
|||||||
updateProfileFx({
|
updateProfileFx({
|
||||||
displayName: user.displayName?.trim() || null,
|
displayName: user.displayName?.trim() || null,
|
||||||
avatar: previewSrc,
|
avatar: previewSrc,
|
||||||
avatarType: 'generated',
|
|
||||||
avatarStyle: previewStyle,
|
avatarStyle: previewStyle,
|
||||||
})
|
})
|
||||||
setPreviewSrc(null)
|
setPreviewSrc(null)
|
||||||
@@ -204,22 +199,6 @@ export function SettingsPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{hasOAuthAvatar && !hasUnsavedPreview && (
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
disabled={pendingProfile || useOAuth}
|
|
||||||
onClick={() => {
|
|
||||||
updateProfileFx({
|
|
||||||
displayName: user.displayName?.trim() || null,
|
|
||||||
avatarType: 'oauth',
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
sx={{ mt: 0.5 }}
|
|
||||||
>
|
|
||||||
Использовать OAuth
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|||||||
@@ -11,11 +11,15 @@ export type AuthUser = {
|
|||||||
lastName?: string | null
|
lastName?: string | null
|
||||||
gender?: string | null
|
gender?: string | null
|
||||||
avatar?: string | null
|
avatar?: string | null
|
||||||
avatarType?: string | null
|
|
||||||
avatarStyle?: string | null
|
avatarStyle?: string | null
|
||||||
isAdmin?: boolean
|
isAdmin?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AuthMethod = {
|
||||||
|
type: 'password' | 'vk' | 'yandex'
|
||||||
|
active: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export const tokenSet = createEvent<string | null>()
|
export const tokenSet = createEvent<string | null>()
|
||||||
export const logout = createEvent()
|
export const logout = createEvent()
|
||||||
|
|
||||||
@@ -72,7 +76,6 @@ export const verifyEmailChangeFx = createEffect(async (params: { newEmail: strin
|
|||||||
export type UpdateProfileParams = {
|
export type UpdateProfileParams = {
|
||||||
displayName: string | null
|
displayName: string | null
|
||||||
avatar?: string | null
|
avatar?: string | null
|
||||||
avatarType?: string | null
|
|
||||||
avatarStyle?: string | null
|
avatarStyle?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +84,35 @@ export const updateProfileFx = createEffect(async (params: UpdateProfileParams)
|
|||||||
return data.user
|
return data.user
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ----- Auth effects -----
|
||||||
|
|
||||||
|
export const loginFx = createEffect(async (params: { email: string; password: string }) => {
|
||||||
|
const { data } = await apiClient.post<{ token: string; user: AuthUser }>('auth/login', params)
|
||||||
|
tokenSet(data.token)
|
||||||
|
return data.user
|
||||||
|
})
|
||||||
|
|
||||||
|
export const registerFx = createEffect(
|
||||||
|
async (params: { email: string; password: string; displayName?: string }) => {
|
||||||
|
const { data } = await apiClient.post<{ token: string; user: AuthUser }>('auth/register', params)
|
||||||
|
tokenSet(data.token)
|
||||||
|
return data.user
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export const fetchAuthMethodsFx = createEffect(async () => {
|
||||||
|
const { data } = await apiClient.get<{ methods: AuthMethod[] }>('me/auth-methods')
|
||||||
|
return data.methods
|
||||||
|
})
|
||||||
|
|
||||||
|
export const setPasswordFx = createEffect(async (password: string) => {
|
||||||
|
await apiClient.post('me/password', { password })
|
||||||
|
})
|
||||||
|
|
||||||
|
export const unlinkOAuthFx = createEffect(async (provider: 'vk' | 'yandex') => {
|
||||||
|
await apiClient.delete(`me/oauth/${provider}`)
|
||||||
|
})
|
||||||
|
|
||||||
// ----- Error stores -----
|
// ----- Error stores -----
|
||||||
|
|
||||||
export const $requestEmailChangeCodeError = createErrorStore(requestEmailChangeCodeFx).$error
|
export const $requestEmailChangeCodeError = createErrorStore(requestEmailChangeCodeFx).$error
|
||||||
|
|||||||
@@ -7,20 +7,19 @@ import { DEFAULT_STYLE_ID, getStyleById } from '@/shared/lib/avatar-styles'
|
|||||||
type UserAvatarProps = {
|
type UserAvatarProps = {
|
||||||
userId: string
|
userId: string
|
||||||
avatarUrl?: string | null
|
avatarUrl?: string | null
|
||||||
avatarType?: string | null
|
|
||||||
avatarStyle?: string | null
|
avatarStyle?: string | null
|
||||||
size?: number
|
size?: number
|
||||||
sx?: SxProps<Theme>
|
sx?: SxProps<Theme>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UserAvatar({ userId, avatarUrl, avatarType, avatarStyle, size = 40, sx }: UserAvatarProps) {
|
export function UserAvatar({ userId, avatarUrl, avatarStyle, size = 40, sx }: UserAvatarProps) {
|
||||||
const generatedSrc = useMemo(() => {
|
const generatedSrc = useMemo(() => {
|
||||||
const styleDef = getStyleById(avatarStyle || DEFAULT_STYLE_ID)
|
const styleDef = getStyleById(avatarStyle || DEFAULT_STYLE_ID)
|
||||||
const avatar = createAvatar(styleDef.style, { seed: userId })
|
const avatar = createAvatar(styleDef.style, { seed: userId })
|
||||||
return avatar.toDataUri()
|
return avatar.toDataUri()
|
||||||
}, [userId, avatarStyle])
|
}, [userId, avatarStyle])
|
||||||
|
|
||||||
const src = avatarType && avatarUrl ? avatarUrl : generatedSrc
|
const src = avatarUrl || generatedSrc
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Avatar src={src} sx={{ width: size, height: size, bgcolor: 'primary.main', ...sx }}>
|
<Avatar src={src} sx={{ width: size, height: size, bgcolor: 'primary.main', ...sx }}>
|
||||||
|
|||||||
@@ -104,7 +104,6 @@ export function ReviewsBlock() {
|
|||||||
<UserAvatar
|
<UserAvatar
|
||||||
userId={r.authorDisplay}
|
userId={r.authorDisplay}
|
||||||
avatarUrl={r.authorAvatar}
|
avatarUrl={r.authorAvatar}
|
||||||
avatarType={r.authorAvatarType}
|
|
||||||
avatarStyle={r.authorAvatarStyle}
|
avatarStyle={r.authorAvatarStyle}
|
||||||
size={40}
|
size={40}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user