Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Zamelane committed Dec 9, 2024
1 parent 6eb427f commit 38ca018
Show file tree
Hide file tree
Showing 16 changed files with 147 additions and 76 deletions.
4 changes: 4 additions & 0 deletions apps/api/src/helpers/crutches.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const crutchesInit = () => {
// @ts-ignore
BigInt.prototype.toJSON = function() { return this.toString() }
}
4 changes: 4 additions & 0 deletions apps/api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import { routes } from './routes'
import './models/relations'
import staticPlugin from '@elysiajs/static'
import { syncDatabaseForDecorations } from './helpers/syncDatabaseForDecorations'
import {crutchesInit} from "./helpers/crutches";

// Подключаем костыли
crutchesInit()

// настраиваем сервер...
const port = Bun.env.PORT ?? 3003
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/models/Avatar/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { IModelPrototype } from '../types'
// REMOVE IT
// ?
export type AvatarModelType = {
id: bigint
id: number
filename: string
price: number
isAnimated: boolean
Expand All @@ -18,7 +18,7 @@ export type IAvatarModelType = IModelPrototype<AvatarModelType, 'id'>

const avatarModel = sequelize.define<IAvatarModelType>('avatar', {
id: {
type: DataTypes.BIGINT,
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/models/DiaryUser/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export type DiaryUserModelType = {
termStartDate?: Nullable<string>
isAdmin: boolean
idFromDiary: number
avatarId: Nullable<bigint>
avatarId: Nullable<number>
}

export type IDiaryUserModel = IModelPrototype<DiaryUserModelType, 'id'>
Expand Down Expand Up @@ -128,7 +128,7 @@ export const DiaryUserModel = sequelize.define<IDiaryUserModel>('diaryUser', {
comment: 'Признак администратора'
},
avatarId: {
type: DataTypes.BIGINT,
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: AvatarModel
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/models/UserAvatar/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import type { IModelPrototypeNoId } from '../types'
// REMOVE IT
// ?
export type UserAvatarModelType = {
avatarId: bigint
avatarId: number
diaryUserId: bigint
}

export type IUserAvatarModelType = IModelPrototypeNoId<UserAvatarModelType>

const userAvatarModel = sequelize.define<IUserAvatarModelType>('userAvatar', {
avatarId: {
type: DataTypes.BIGINT,
type: DataTypes.INTEGER,
references: {
model: AvatarModel
},
Expand Down
3 changes: 1 addition & 2 deletions apps/api/src/routes/market/buy/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ type OperationStatus = {

// TODO: ВЫНЕСТИ ВСЁ КРАСИВЕНЬКО ТУТ И ТАМ

const buyAvatar = async (localUserId: bigint, strAvatarId: string): Promise<OperationStatus> => {
const avatarId = BigInt(strAvatarId)
const buyAvatar = async (localUserId: bigint, avatarId: number): Promise<OperationStatus> => {
const currentBalance = await BalanceOperationModel.sum('amount', {where: {diaryUserId: localUserId}})
const avatar = await AvatarModel.findOne({where: {id: avatarId}})

Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/routes/market/buy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ export const BuyAvatar = new Elysia()
tags: ['market']
},
body: t.Object({
avatarId: t.String()
avatarId: t.Numeric()
})
})
13 changes: 2 additions & 11 deletions apps/api/src/routes/market/userSaveAvatar/handler.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
import {Nullable} from "@diary-spo/shared"
import {UserAvatarModel} from "../../../models/UserAvatar";
import {API_CODES, API_ERRORS, ApiError} from "@api";
import {DiaryUserModel} from "../../../models/DiaryUser";

type MarketUserInfo = {
firstName: string
lastName: string
avatar: Nullable<string>
balance: number
}

const userSaveAvatar = async (localUserId: bigint, stringAvatarId: string): Promise<void> => {
const avatarId = BigInt(stringAvatarId)
const avatarIsSetNull = avatarId === BigInt(-1)
const userSaveAvatar = async (localUserId: bigint, avatarId: number): Promise<void> => {
const avatarIsSetNull = avatarId === -1

if (!avatarIsSetNull) {
const userAvatar = await UserAvatarModel.findOne({where: {avatarId}})
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/routes/market/userSaveAvatar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ export const UserSaveAvatar = new Elysia()
tags: ['market']
},
body: t.Object({
avatarId: t.String()
avatarId: t.Numeric()
})
})
6 changes: 5 additions & 1 deletion apps/shared/src/api/self/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,13 @@ export type ResponseLogin = Person & {
}

export type AvatarData = {
id: bigint
id: number
isAnimated: boolean
filename: string
tags: string[]
price: number
}

export type LoadedAvatarData = AvatarData & {
isLoaded: undefined | boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,19 @@ export const BuyModal: FC<Props> = ({id}) => {
setIsError(false)
setIsLoading(true)
try {
const { data } = await client.buyAvatar.post({avatarId: `${modalData.avatar.id}`})
const { data } = await client.buyAvatar.post({avatarId: modalData.avatar.id})

if (data === null || isApiError(data))
throw new Error('Ошибка с сервера')

if (data.isSuccess) {
setIsSuccessBuy(true)
const newBalance = data.currentBalance - modalData.avatar.price
modalData.setBalance(newBalance)
modalData.balance = newBalance
} else if (modalData.balance ?? 0 >= modalData.avatar.price) {
setIsAlreadyBuy(true)
}

modalData.setBalance(data.currentBalance)
} catch {
setIsError(true)
} finally {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {winxAva} from "../../../../../../shared/config/images.ts";
import {getUrlPath} from "../../../../../../pages/Market/components/AvatarsBlock/getUrlPath.tsx";
import {Avatar, Skeleton} from "@vkontakte/vkui";
import {Icon16DoneCircle, Icon16SyncCircleFillBlack} from "@vkontakte/icons";
import {useState} from "react";
import {AvatarData} from "@diary-spo/shared";
import './index.css'

export const PreviewAvatarLoading = (
{selectAva, selectNewAvatar, avatar, isSaveNewAvatarLoading}: {selectAva: AvatarData|null, selectNewAvatar: (avatar: AvatarData) => void, isSaveNewAvatarLoading: boolean, avatar: AvatarData}) => {
let [isLoaded, setIsLoaded] = useState(false)
return (
<>
<Skeleton className={selectAva === avatar ? 'select-avatar' : ''}
width={selectAva === avatar ? 100 : 110}
height={selectAva === avatar ? 100 : 110}
borderRadius={100}
hidden={isLoaded}
/>
<Avatar
size={110}
src={avatar.filename != winxAva ? getUrlPath(avatar) : winxAva}
onClick={() => selectNewAvatar(avatar)}
className={selectAva === avatar ? 'select-avatar' : ''}
onLoad={() => setIsLoaded(true)}
onLoadStart={() => setIsLoaded(false)}
hidden={!isLoaded}
>
<Avatar.Badge
hidden={selectAva !== avatar}
className='select-avatar_badge'
>
{
isSaveNewAvatarLoading
? <Icon16SyncCircleFillBlack height={25} width={25}/>
: <Icon16DoneCircle height={25} width={25}/>
}
</Avatar.Badge>
</Avatar>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from '@vkontakte/vkui'
import './index.css'
import {
Icon16DoneCircle, Icon16SyncCircleFillBlack, Icon24Repeat,
Icon16DoneCircle, Icon16SyncCircleFillBlack,
Icon28ShoppingCartOutline, Icon56HourglassErrorBadgeOutline
} from '@vkontakte/icons'
import { useRouteNavigator } from '@vkontakte/vk-mini-apps-router'
Expand All @@ -21,10 +21,11 @@ import {winxAva} from "../../../../../../shared/config/images.ts";
import {client} from "../../../../../../shared/api/client.ts";
import {isApiError} from "../../../../../../shared";
import {useUserEditModal} from "../../../../../../store/userEditModal";
import {PreviewAvatarLoading} from "./PreviewAvatarLoading.tsx";

const defaultCollection: AvatarData[] = [
{
id: BigInt(-1),
id: -1,
filename: winxAva,
price: 0,
isAnimated: false,
Expand Down Expand Up @@ -75,7 +76,7 @@ const UserEditModal = ({ id }: { id: string }) => {
setSelectAva(avatar)

try {
const response = await client.userSaveAvatar.post({avatarId: `${avatar.id}`})
const response = await client.userSaveAvatar.post({avatarId: avatar.id})

if (response.status != 200)
throw new Error('Ошибка с сервера')
Expand Down Expand Up @@ -132,24 +133,12 @@ const UserEditModal = ({ id }: { id: string }) => {
/>
))
: getAvatars.map((avatar, index) => (
<Avatar
key={index}
size={110}
src={avatar.filename != winxAva ? getUrlPath(avatar) : winxAva}
onClick={() => selectNewAvatar(avatar)}
className={selectAva === avatar ? 'select-avatar' : ''}
>
<Avatar.Badge
hidden={selectAva !== avatar}
className='select-avatar_badge'
>
{
isSaveNewAvatarLoading
? <Icon16SyncCircleFillBlack height={25} width={25}/>
: <Icon16DoneCircle height={25} width={25}/>
}
</Avatar.Badge>
</Avatar>
<PreviewAvatarLoading key={index}
selectAva={selectAva}
selectNewAvatar={selectNewAvatar}
isSaveNewAvatarLoading={isSaveNewAvatarLoading}
avatar={avatar}
/>
))}
{
!isAvatarLoading &&
Expand Down
10 changes: 7 additions & 3 deletions apps/web/src/pages/Achievements/Tabs/Summary/UserInfo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
Group,
Header,
Placeholder,
SimpleCell,
SimpleCell, Skeleton,
Spinner
} from '@vkontakte/vkui'
import { type FC, useEffect, useState } from 'react'
Expand Down Expand Up @@ -75,13 +75,14 @@ const UserInfo: FC = () => {


const [userInfoIsLoading, setUserInfoIsLoading] = useState(true)
const [avatarImgIsLoading, setAvatarImgIsLoading] = useState(true)
const [userAvatarFilename, setUserAvatarFilename] = useState<string|null|undefined>(undefined)
const { setData } = useUserEditModal()
const routeNavigator = useRouteNavigator()

const handleEditUserButtonClick = () => {
setData({
setAvatarFilename: (value: string) => setUserAvatarFilename(value)
setAvatarFilename: (value: string|null) => setUserAvatarFilename(value)
})
routeNavigator.showModal(MODAL_PAGE_USER_EDIT)
}
Expand Down Expand Up @@ -119,7 +120,10 @@ const UserInfo: FC = () => {
return (
<Group mode='plain' header={header}>
<Gradient mode='tint' className='userInfo__Wrapper'>
<Avatar size={96} src={userAvatarFilename ? getUrlPath(userAvatarFilename) : (userAvatarFilename === null ? winxAva : undefined)} />
<Skeleton width={96} height={96} borderRadius={100} hidden={!avatarImgIsLoading}/>
<Avatar size={96}
src={userAvatarFilename ? getUrlPath(userAvatarFilename) : (userAvatarFilename === null ? winxAva : undefined)}
onLoad={() => setAvatarImgIsLoading(false)} hidden={avatarImgIsLoading}/>
<Placeholder
title={userData.name}
className='userInfo__Content'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type {AvatarData} from "@diary-spo/shared";
import React, {useState} from "react";
import {Avatar, Skeleton, ToolButton, Tooltip} from "@vkontakte/vkui";
import {getUrlPath} from "./getUrlPath.tsx";
import {balanceFormatter} from "../HeaderBlock/balanceFormatter.tsx";
import {Icon16DiamondOutline, Icon16Gift} from "@vkontakte/icons";

const colorIcon = ({avatar, getUserBalance}: {avatar: AvatarData, getUserBalance: number}) => <Icon16DiamondOutline color={avatar.price > getUserBalance ? 'red' : undefined}/>

const AvatarPreviewComponent = (
{avatar, onClickAvatarHandler, getUserBalance}: { avatar: AvatarData, onClickAvatarHandler: ((avatar: AvatarData) => void)|undefined, getUserBalance: number|undefined }
) => {
let [isLoaded, setIsLoaded] = useState(false)
return (
<>
<Skeleton width={110} height={110} borderRadius={100} hidden={isLoaded}/>
<Avatar onClick={() => onClickAvatarHandler === undefined ? null : onClickAvatarHandler(avatar)}
size={110}
src={getUrlPath(avatar)}
style={{isolation: 'auto'}}
hidden={!isLoaded}
onLoad={() => {
setIsLoaded(true)
}}
onLoadStart={() => {
setIsLoaded(false)
}}
>
{
getUserBalance !== undefined &&
<Avatar.Badge className='select-avatar_badge'>

<Tooltip
title={avatar.price ? `${balanceFormatter(avatar.price)} алмазов` : 'Бесплатно'}
>
<ToolButton
IconCompact={avatar.price ? () => colorIcon({
avatar,
getUserBalance: getUserBalance ?? 0
}) : Icon16Gift}
IconRegular={avatar.price ? () => undefined : Icon16Gift}
>
{
avatar.price > 0 &&
<label
className={avatar.price > (getUserBalance ?? 0) ? 'no-money' : ''}>{balanceFormatter(avatar.price)}</label>
}
</ToolButton>
</Tooltip>

</Avatar.Badge>
}
</Avatar>
</>
)
}

export const MemorizedAvatarPreview = React.memo(AvatarPreviewComponent)
Loading

0 comments on commit 38ca018

Please sign in to comment.