From a2f77ca62fec4f786480deeaeae6d59f0c3adb65 Mon Sep 17 00:00:00 2001 From: abcxj123 Date: Sun, 9 Jun 2024 23:40:57 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=86=8C=EB=B9=84=ED=8C=A8=ED=84=B4=20?= =?UTF-8?q?api=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/apiClient.ts | 61 ++++- src/apis/interfaces/accountApi.ts | 7 +- src/apis/interfaces/budgetApi.ts | 10 + src/apis/interfaces/spendApi.ts | 5 + src/components/molecules/CategoryEditItem.tsx | 2 +- src/components/molecules/CategoryItem.tsx | 4 +- src/components/organisms/BudgetInfo.tsx | 42 +++- .../organisms/CategorySpendCard.tsx | 29 ++- src/pages/consume/ConsumeEdit.tsx | 217 ++++++++++++++---- src/pages/consume/ConsumePattern.tsx | 121 ++++++++-- src/pages/mission1/Mission1StartPage.tsx | 152 ++++++++++-- src/types/budget.d.ts | 25 ++ src/types/spend.d.ts | 8 + 13 files changed, 566 insertions(+), 117 deletions(-) create mode 100644 src/apis/interfaces/budgetApi.ts create mode 100644 src/apis/interfaces/spendApi.ts create mode 100644 src/types/budget.d.ts create mode 100644 src/types/spend.d.ts diff --git a/src/apis/apiClient.ts b/src/apis/apiClient.ts index 42d497b..7f3fcc5 100644 --- a/src/apis/apiClient.ts +++ b/src/apis/apiClient.ts @@ -10,9 +10,15 @@ import { } from '../types/account'; import { alarmApi } from './interfaces/alarmApi'; import { API_BASE_URL } from './url'; +import { SpendListType } from '../types/spend'; +import { spendApi } from './interfaces/spendApi'; +import { BudgetReqType, BudgetResType } from '../types/budget'; +import { budgetApi } from './interfaces/budgetApi'; const TOKEN = getCookie('token'); -export class ApiClient implements usersApi, accountApi, alarmApi { +export class ApiClient + implements usersApi, accountApi, spendApi, budgetApi, alarmApi +{ private static instance: ApiClient; private axiosInstance: AxiosInstance; @@ -41,7 +47,7 @@ export class ApiClient implements usersApi, accountApi, alarmApi { const response = await this.axiosInstance.request({ method: 'put', url: `/users/point`, - data: isMission, + data: { isMission }, }); return response.data; } @@ -82,11 +88,52 @@ export class ApiClient implements usersApi, accountApi, alarmApi { return response.data; } - async getAccountDetail(accountId: number, year: number, month: number) { - const response = await this.axiosInstance.request({ + //---------spend--------- + async getSpendList(year: number, month: number) { + const response = await this.axiosInstance.request({ method: 'get', - url: `/account/${accountId} - ?year=${year}&month=${month}`, + url: `/spend?year=${year}&month=${month}`, + }); + return response.data; + } + + //---------budget--------- + async getTotalBudget() { + const response = await this.axiosInstance.request<{ sum: number }>({ + method: 'get', + url: '/budget', + }); + return response.data; + } + + async updateTotalBudget(sum: number) { + const response = await this.axiosInstance.request<{ + isInitialUpdate: boolean; + }>({ + method: 'put', + url: '/budget', + data: { sum }, + }); + return response.data; + } + + async getCategoryBudget() { + const response = await this.axiosInstance.request({ + method: 'get', + url: '/budget/category', + }); + return response.data; + } + + async updateCategoryBudget(budget: BudgetReqType) { + const response = await this.axiosInstance.request<{ + success: boolean; + type: string; + message: string; + }>({ + method: 'put', + url: '/budget/category', + data: budget, }); return response.data; } @@ -96,7 +143,7 @@ export class ApiClient implements usersApi, accountApi, alarmApi { const response = await this.axiosInstance.request({ method: 'post', url: '/alarm', - data: contents, + data: { contents }, }); return response.data; } diff --git a/src/apis/interfaces/accountApi.ts b/src/apis/interfaces/accountApi.ts index 9d4bca3..8dccdbd 100644 --- a/src/apis/interfaces/accountApi.ts +++ b/src/apis/interfaces/accountApi.ts @@ -1,10 +1,5 @@ -import { AccountReqType, AccountType, AccountDetailType } from '../../types/account'; +import { AccountReqType, AccountType } from '../../types/account'; export interface accountApi { - getAccountDetail( - accountId: number, - year: number, - month: number - ): Promise; getAccount(type: AccountReqType): Promise; } diff --git a/src/apis/interfaces/budgetApi.ts b/src/apis/interfaces/budgetApi.ts new file mode 100644 index 0000000..644fbe9 --- /dev/null +++ b/src/apis/interfaces/budgetApi.ts @@ -0,0 +1,10 @@ +import { BudgetReqType, BudgetResType } from '../../types/budget'; + +export interface budgetApi { + getTotalBudget(): Promise<{ sum: number }>; + updateTotalBudget(sum: number): Promise<{ isInitialUpdate: boolean }>; + getCategoryBudget(): Promise; + updateCategoryBudget( + budget: BudgetReqType + ): Promise<{ success: boolean; type: string; message: string }>; +} diff --git a/src/apis/interfaces/spendApi.ts b/src/apis/interfaces/spendApi.ts new file mode 100644 index 0000000..e21909f --- /dev/null +++ b/src/apis/interfaces/spendApi.ts @@ -0,0 +1,5 @@ +import { SpendListType } from '../../types/spend'; + +export interface spendApi { + getSpendList(year: number, month: number): Promise; +} diff --git a/src/components/molecules/CategoryEditItem.tsx b/src/components/molecules/CategoryEditItem.tsx index b388ec3..6ec2740 100644 --- a/src/components/molecules/CategoryEditItem.tsx +++ b/src/components/molecules/CategoryEditItem.tsx @@ -33,7 +33,7 @@ export const CategoryEditItem: FC = ({ useEffect(() => { inputRef.current!.value = balance.toString(); - }, []); + }, [balance]); return (
diff --git a/src/components/molecules/CategoryItem.tsx b/src/components/molecules/CategoryItem.tsx index d2c4465..ff9d979 100644 --- a/src/components/molecules/CategoryItem.tsx +++ b/src/components/molecules/CategoryItem.tsx @@ -3,7 +3,7 @@ import React, { FC } from 'react'; interface IProps { color: string; name: string; - balance: number; + balance?: number; } export const CategoryItem: FC = ({ color, name, balance }) => { @@ -13,7 +13,7 @@ export const CategoryItem: FC = ({ color, name, balance }) => {

{name}

-

{balance.toLocaleString()}원

+

{balance?.toLocaleString()}원

); }; diff --git a/src/components/organisms/BudgetInfo.tsx b/src/components/organisms/BudgetInfo.tsx index 7fd586a..ba0a439 100644 --- a/src/components/organisms/BudgetInfo.tsx +++ b/src/components/organisms/BudgetInfo.tsx @@ -1,15 +1,35 @@ -import { FC, useState } from 'react'; +import { useMutation } from '@tanstack/react-query'; +import { FC, useEffect, useState } from 'react'; import { RiPencilFill } from 'react-icons/ri'; +import { ApiClient } from '../../apis/apiClient'; interface Iprops { month: number; - balance: number; + balance?: number; lastSpend?: number; + initialFunc?: () => void; } -export const BudgetInfo: FC = ({ month, balance, lastSpend }) => { +export const BudgetInfo: FC = ({ + month, + balance, + lastSpend, + initialFunc, +}) => { const [isEdit, SetIsEdit] = useState(false); - const [value, setValue] = useState(balance.toLocaleString()); + const [value, setValue] = useState(balance?.toLocaleString()); + + const { + mutate: updateBudget, + isSuccess: isSuccessUpdate, + data: budgetRes, + } = useMutation({ + mutationKey: ['updateBudget'], + mutationFn: (sum: number) => { + const res = ApiClient.getInstance().updateTotalBudget(sum); + return res; + }, + }); const valueChangeHandler = (e: any) => { let inputPrice = e.target.value; @@ -24,16 +44,28 @@ export const BudgetInfo: FC = ({ month, balance, lastSpend }) => { }; const onClickConfirm = () => { + const realValue = Number(value!.replace(/[^0-9]/g, '')); + updateBudget(realValue); SetIsEdit(false); }; + useEffect(() => { + if (isSuccessUpdate && budgetRes.isInitialUpdate) { + initialFunc!(); + } + }, [isSuccessUpdate]); + + useEffect(() => { + setValue(balance?.toLocaleString()); + }, [balance]); + return (

{month}월 예산

{/* 보기 모드 */} {!isEdit && (
-

{value.toLocaleString()}원

+

{value?.toLocaleString()}원

= ({ }) => { const [showAll, setShowAll] = useState(false); const [text, setText] = useState('전체보기'); + const [re, setRe] = useState(false); Chart.register(ArcElement); const extra = datas.length <= 4 ? null - : datas.slice(5).reduce((acc, val) => acc + val.balance, 0); + : datas.slice(5).reduce((acc, val) => acc + val.amount, 0); const colors = ['#28B2A5', '#E90061', '#FFC700', '#AD9A5F', '#B5B5B5']; const data = { labels: [ - ...datas.slice(0, Math.min(datas.length, 4)).map((item) => item.name), + ...datas.slice(0, Math.min(datas.length, 4)).map((item) => item.type), '기타', ], datasets: [ @@ -51,7 +48,7 @@ export const CategorySpendCard: FC = ({ data: [ ...datas .slice(0, Math.min(datas.length, 4)) - .map((item) => item.balance), + .map((item) => item.amount), extra, ], borderWidth: 2, @@ -138,9 +135,10 @@ export const CategorySpendCard: FC = ({
{datas.slice(0, Math.min(datas.length, 4)).map((item, idx) => ( ))} {extra && ( @@ -151,11 +149,12 @@ export const CategorySpendCard: FC = ({ {showAll && datas .slice(5) - .map((item) => ( + .map((item, idx) => ( ))}
{ const navigate = useNavigate(); - const initData = [ + const { data: spendData, isSuccess: successSpend } = useQuery({ + queryKey: ['spend3'], + queryFn: () => { + let lastYear = dateYear; + let lastMonth = dateMonth - 1; + if (lastMonth == 0) { + lastMonth = 12; + lastYear--; + } + const res = ApiClient.getInstance().getSpendList(lastYear, lastMonth); + return res; + }, + }); + + const { data: budgetData, isSuccess: successBudget } = useQuery({ + queryKey: ['budget2'], + queryFn: () => { + const res = ApiClient.getInstance().getCategoryBudget(); + return res; + }, + }); + + const { mutate: setBudget } = useMutation({ + mutationKey: ['setBudget'], + mutationFn: () => { + const res = ApiClient.getInstance().updateCategoryBudget(data); + return res; + }, + }); + + const [initData, setInitData] = useState([ { icon: 'icons/shopping.svg', name: '쇼핑', - lastSpend: 10000, - balance: 5000, + lastSpend: 0, + balance: 0, }, { icon: 'icons/food.svg', name: '요식', - lastSpend: 20000, - balance: 4000, + lastSpend: 0, + balance: 0, }, { icon: 'icons/traffic.svg', name: '교통', - lastSpend: 30000, - balance: 2000, + lastSpend: 0, + balance: 0, }, { icon: 'icons/hospital.svg', name: '의료', - lastSpend: 20000, - balance: 4000, + lastSpend: 0, + balance: 0, }, { icon: 'icons/fee.svg', name: '납부', - lastSpend: 10000, - balance: 1000, + lastSpend: 0, + balance: 0, }, { icon: 'icons/education.svg', name: '교육', - lastSpend: 20000, - balance: 5000, + lastSpend: 0, + balance: 0, }, { icon: 'icons/leisure.svg', name: '여유생활', - lastSpend: 30000, - balance: 7000, + lastSpend: 0, + balance: 0, }, { icon: 'icons/society.svg', name: '사교활동', - lastSpend: 20000, - balance: 3000, + lastSpend: 0, + balance: 0, }, { icon: 'icons/daily.svg', name: '일상생활', - lastSpend: 10000, - balance: 5000, + lastSpend: 0, + balance: 0, }, { icon: 'icons/overseas.svg', name: '해외', - lastSpend: 20000, - balance: 3000, + lastSpend: 0, + balance: 0, }, - ]; + ]); - const [data, setData] = useState({ + const [data, setData] = useState({ shopping: initData[0].balance, food: initData[1].balance, traffic: initData[2].balance, @@ -102,17 +142,17 @@ export const ConsumeEdit = () => { Object.values(data).reduce((acc, val) => acc + val) ); - const [totalSum, setTotalSum] = useState(500000); - const updateValue = (name: string, value: number) => { setData((cur) => { let newData: any = { ...cur }; - newData[name] = value; + newData[CategoryConvert[name as keyof typeof CategoryConvert]] = value; return newData; }); + setSum(Object.values(data).reduce((acc, val) => acc + val)); }; const onClickButton = () => { + setBudget(); navigate('/consume'); }; @@ -120,12 +160,102 @@ export const ConsumeEdit = () => { setSum(Object.values(data).reduce((acc, val) => acc + val)); }, [data]); + useEffect(() => { + if (successBudget) { + setInitData([ + { ...initData[0], balance: budgetData.shopping }, + { ...initData[1], balance: budgetData.food }, + { ...initData[2], balance: budgetData.traffic }, + { ...initData[3], balance: budgetData.hospital }, + { ...initData[4], balance: budgetData.fee }, + { ...initData[5], balance: budgetData.education }, + { ...initData[6], balance: budgetData.leisure }, + { ...initData[7], balance: budgetData.society }, + { ...initData[8], balance: budgetData.daily }, + { ...initData[9], balance: budgetData.overseas }, + ]); + } + }, [successBudget]); + + useEffect(() => { + if (successBudget) { + setData({ + shopping: initData[0].balance, + food: initData[1].balance, + traffic: initData[2].balance, + hospital: initData[3].balance, + fee: initData[4].balance, + education: initData[5].balance, + leisure: initData[6].balance, + society: initData[7].balance, + daily: initData[8].balance, + overseas: initData[9].balance, + }); + } + }, [initData]); + + useEffect(() => { + if (successSpend) { + let tmp = new Array(10); + spendData.spendFindByTypeResList.forEach((data) => { + switch (data.type) { + case 'SHOPPING': + tmp[0] = data.amount; + break; + case 'FOOD': + tmp[1] = data.amount; + break; + case 'TRAFFIC': + tmp[2] = data.amount; + break; + case 'HOSPITAL': + tmp[3] = data.amount; + break; + case 'FEE': + tmp[4] = data.amount; + break; + case 'EDUCATION': + tmp[5] = data.amount; + break; + case 'LEISURE': + tmp[6] = data.amount; + break; + case 'SOCIETY': + tmp[7] = data.amount; + break; + case 'DAILY': + tmp[8] = data.amount; + break; + case 'OVERSEAS': + tmp[9] = data.amount; + break; + } + }); + setInitData([ + { ...initData[0], lastSpend: tmp[0] }, + { ...initData[1], lastSpend: tmp[1] }, + { ...initData[2], lastSpend: tmp[2] }, + { ...initData[3], lastSpend: tmp[3] }, + { ...initData[4], lastSpend: tmp[4] }, + { ...initData[5], lastSpend: tmp[5] }, + { ...initData[6], lastSpend: tmp[6] }, + { ...initData[7], lastSpend: tmp[7] }, + { ...initData[8], lastSpend: tmp[8] }, + { ...initData[9], lastSpend: tmp[9] }, + ]); + } + }, [successSpend]); + return ( <>
{/* 예산 카드 영역 */} - + {/* 예산 입력 영역 */}
@@ -133,18 +263,21 @@ export const ConsumeEdit = () => { 카테고리별 예산

-

+

budgetData?.sum! && 'text-hanaRed'}`} + > {sum.toLocaleString()}원

- 전체 예산 {totalSum.toLocaleString()}원 + 전체 예산 {budgetData?.sum.toLocaleString()}원

- {initData.map((item) => ( + {initData.map((item, idx) => ( { - const datas = [ - { name: '교통', balance: 70000 }, - { name: '요식', balance: 20000 }, - { name: '납부', balance: 5000 }, - { name: '쇼핑', balance: 3000 }, - { name: '여유생활', balance: 3000 }, - { name: '사교생활', balance: 3000 }, - { name: '일상생활', balance: 3000 }, - { name: '해외', balance: 2000 }, - { name: '사교생활', balance: 2000 }, - { name: '일상생활', balance: 1000 }, - { name: '해외', balance: 1000 }, - ]; +const Category = { + SHOPPING: '쇼핑', + FOOD: '요식', + TRAFFIC: '교통', + HOSPITAL: '의료', + FEE: '납부', + EDUCATION: '교육', + LEISURE: '여유생활', + SOCIETY: '사교활동', + DAILY: '일상생활', + OVERSEAS: '해외', +} as const; +export const ConsumePattern = () => { const navigate = useNavigate(); const [year, setYear] = useState(dateYear); const [month, setMonth] = useState(dateMonth); + const [spend, setSpend] = useState(0); + const [budget, setBudget] = useState(0); + const [percent, setPercent] = useState(0); + + const spendQuery = useQuery({ + queryKey: ['category', year, month], + queryFn: () => { + const res = ApiClient.getInstance().getSpendList(year, month); + return res; + }, + staleTime: 100, + }); + + const { data: budgetData, isSuccess: successBudget } = useQuery({ + queryKey: ['budget'], + queryFn: () => { + const res = ApiClient.getInstance().getTotalBudget(); + return res; + }, + staleTime: 100, + }); + + const [datas, setDatas] = useState([]); + + useEffect(() => { + if (spendQuery.data) { + setDatas( + spendQuery.data.spendFindByTypeResList + .map((item) => { + return { + type: Category[item.type as keyof typeof Category], + amount: item.amount, + }; + }) + .filter((item) => item.amount > 0) + ); + } + }, [spendQuery.data]); + + useEffect(() => { + if (spendQuery.isSuccess && successBudget) { + try { + ApiClient.getInstance() + .getSpendList(dateYear, dateMonth) + .then((res) => { + setSpend(res.sum); + setPercent(Math.round((res.sum / budget) * 100)); + }); + } catch (e) { + console.log(e); + } + } + }, [spendQuery.isSuccess, successBudget]); + + useEffect(() => { + if (successBudget) { + setBudget(budgetData.sum); + } + }, [successBudget]); const onClickButton = () => { navigate('/consumeEdit'); @@ -44,9 +105,11 @@ export const ConsumePattern = () => {
{/* 지출 카드 영역 */}
-

{month}월 지출

+

{dateMonth}월 지출

-

500,000원

+

+ {spend.toLocaleString()}원 +

{/* 예산 영역 */} @@ -54,7 +117,9 @@ export const ConsumePattern = () => {

예산

-

500,000원

+

+ {budget.toLocaleString()}원 +

{
{/* 프로그래스바 */}
-
-
-

22%

+
+
+

{percent}%

- - + +
onClickButton()} @@ -84,7 +157,7 @@ export const ConsumePattern = () => { datas={datas} year={year} month={month} - balance={500000} + balance={datas.reduce((acc, data) => acc + data.amount, 0)} setYearFunc={setYearFunc} setMonthFunc={setMonthFunc} /> diff --git a/src/pages/mission1/Mission1StartPage.tsx b/src/pages/mission1/Mission1StartPage.tsx index c352449..544a9f0 100644 --- a/src/pages/mission1/Mission1StartPage.tsx +++ b/src/pages/mission1/Mission1StartPage.tsx @@ -3,36 +3,158 @@ import Topbar from '../../components/Topbar'; import { BudgetInfo } from '../../components/organisms/BudgetInfo'; import { dateMonth, dateYear } from '../../utils/getDate'; import { CategorySpendCard } from '../../components/organisms/CategorySpendCard'; +import { useMutation, useQuery } from '@tanstack/react-query'; +import { ApiClient } from '../../apis/apiClient'; +import { useEffect, useState } from 'react'; +import { SpendType } from '../../types/spend'; +import { AlertModal } from '../../components/AlertModal'; + +const Category = { + SHOPPING: '쇼핑', + FOOD: '요식', + TRAFFIC: '교통', + HOSPITAL: '의료', + FEE: '납부', + EDUCATION: '교육', + LEISURE: '여유생활', + SOCIETY: '사교활동', + DAILY: '일상생활', + OVERSEAS: '해외', +} as const; export const Mission1StartPage = () => { - const datas = [ - { name: '교통', balance: 70000 }, - { name: '요식', balance: 20000 }, - { name: '납부', balance: 5000 }, - { name: '쇼핑', balance: 3000 }, - { name: '여유생활', balance: 3000 }, - { name: '사교생활', balance: 3000 }, - { name: '일상생활', balance: 3000 }, - { name: '해외', balance: 2000 }, - { name: '사교생활', balance: 2000 }, - { name: '일상생활', balance: 1000 }, - { name: '해외', balance: 1000 }, - ]; + const { data: spendList, isSuccess: isSuccessSpend } = useQuery({ + queryKey: ['budget'], + queryFn: () => { + const res = ApiClient.getInstance().getSpendList(dateYear, dateMonth); + return res; + }, + staleTime: 500, + }); + + const { data: budgetData } = useQuery({ + queryKey: ['budget'], + queryFn: () => { + const res = ApiClient.getInstance().getTotalBudget(); + return res; + }, + staleTime: 100, + }); + + const userQuery = useQuery({ + queryKey: ['userInfo'], + queryFn: () => { + const res = ApiClient.getInstance().getUser(); + return res; + }, + enabled: false, + }); + + const { mutate: checkMission, isSuccess: isSuccess0 } = useMutation({ + mutationKey: ['checkMission'], + mutationFn: () => { + const res = ApiClient.getInstance().updateMissionCheck(); + return res; + }, + }); + + const { + mutate: updateHanaMoney, + isSuccess: isSuccess1, + data: hanaMoney, + } = useMutation({ + mutationKey: ['updateHanaMoney2'], + mutationFn: (isMission: boolean) => { + const res = ApiClient.getInstance().updatePoint(isMission); + return res; + }, + }); + + const { mutate: postAlarm, isSuccess: isSuccess2 } = useMutation({ + mutationKey: ['updateHanaMoney3'], + mutationFn: (contents: string) => { + const res = ApiClient.getInstance().postAlarm(contents); + return res; + }, + }); + + const [datas, setDatas] = useState([]); + const [showStepModal, setShowStepModal] = useState(false); + const [initial, setInitial] = useState(false); + + useEffect(() => { + if (isSuccessSpend) { + setDatas( + spendList.spendFindByTypeResList + .map((item) => { + return { + type: Category[item.type as keyof typeof Category], + amount: item.amount, + }; + }) + .filter((item) => item.amount > 0) + ); + } + }, [isSuccessSpend]); + + useEffect(() => { + if ( + !isSuccess0 && + userQuery.data?.step === 1 && + userQuery.data.stepStatus === 2 + ) { + checkMission(); + setShowStepModal(true); + } + }, [userQuery.data]); + + useEffect(() => { + if (isSuccess1 && !isSuccess2) { + postAlarm(`하나머니 ${hanaMoney?.points}원 적립!`); + } + }, [isSuccess1]); + + useEffect(() => { + if (initial) { + userQuery.refetch(); + setInitial(false); + } + }, [initial]); + + const onCloseStepModal = async () => { + try { + updateHanaMoney(true); + setShowStepModal(false); + } catch (e) { + console.log(e); + } + }; return ( <> + {showStepModal && ( + onCloseStepModal()}> +
+

미션을 클리어하였습니다!

+
+
+ )}
- + setInitial(true)} + /> acc + data.amount, 0)} isMission />
diff --git a/src/types/budget.d.ts b/src/types/budget.d.ts new file mode 100644 index 0000000..3ec9759 --- /dev/null +++ b/src/types/budget.d.ts @@ -0,0 +1,25 @@ +export type BudgetResType = { + sum: number; + shopping: number; + food: number; + traffic: number; + hospital: number; + fee: number; + education: number; + leisure: number; + society: number; + daily: number; + overseas: number; +}; +export type BudgetReqType = { + shopping: number; + food: number; + traffic: number; + hospital: number; + fee: number; + education: number; + leisure: number; + society: number; + daily: number; + overseas: number; +}; diff --git a/src/types/spend.d.ts b/src/types/spend.d.ts new file mode 100644 index 0000000..855a8f0 --- /dev/null +++ b/src/types/spend.d.ts @@ -0,0 +1,8 @@ +export type SpendListType = { + sum: number; + spendFindByTypeResList: SpendType[]; +}; +export type SpendType = { + type: string; + amount: number; +};