Skip to content

Commit

Permalink
Merge pull request #113 from CheesecakeLabs/feat_cost_center
Browse files Browse the repository at this point in the history
OpEx admin page
  • Loading branch information
lucasmagnus authored Feb 19, 2024
2 parents b7e80ec + 7293447 commit 6724fd5
Show file tree
Hide file tree
Showing 25 changed files with 894 additions and 26 deletions.
12 changes: 10 additions & 2 deletions frontend/src/app/core/pages/contracts-create/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,15 @@ export const ContractsCreate: React.FC = () => {
const codVault = ContractsService.loadAccount(vault.wallet.key.publicKey)
const codTxInvocation = ContractsService.getTxInvocation(
codVault,
INNER_FEE
INNER_FEE,
/*{
signers: [opex],
header: {
fee: BUMP_FEE,
source: opex.getPublicKey(),
timeout: 60,
},
}*/
)

const codClient = new StellarPlus.Contracts.CertificateOfDeposit({
Expand Down Expand Up @@ -95,7 +103,7 @@ export const ContractsCreate: React.FC = () => {
if (!codContractId) throw new Error('Invalid Contract ID')

const contract = {
name: 'Yield-bearing asset',
name: 'Yield-bearing Asset',
asset_id: asset.id.toString(),
vault_id: vault.id.toString(),
address: codClient.getContractId() || '',
Expand Down
303 changes: 303 additions & 0 deletions frontend/src/app/core/pages/cost-center/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
import { Flex, VStack, useMediaQuery } from '@chakra-ui/react'
import React, { useCallback, useEffect, useState } from 'react'

import { useAssets } from 'hooks/useAssets'
import { useAuth } from 'hooks/useAuth'
import { useHorizon } from 'hooks/useHorizon'
import { useTransactions } from 'hooks/useTransactions'
import { useVaults } from 'hooks/useVaults'
import { GAService } from 'utils/ga'

import { PathRoute } from 'components/enums/path-route'
import { SettingsOptions } from 'components/enums/settings-options'
import { MenuSettings } from 'components/organisms/menu-settings'
import { Sidebar } from 'components/organisms/sidebar'
import { CostCenterTemplate } from 'components/templates/cost-center'

export interface IHorizonData {
name: string
type: string
asset: string
}

export const CostCenter: React.FC = () => {
const [isLargerThanMd] = useMediaQuery('(min-width: 768px)')
const [sponsorAccount, setSponsorAccount] = useState<string | undefined>()
const [transactions, setTransactions] =
useState<Hooks.UseHorizonTypes.ITransactions>()
const [accountData, setAccountData] =
useState<Hooks.UseHorizonTypes.IAccount>()
const [latestFeeCharged, setLatestFeeCharged] = useState<number>()
const [mostRepeatedType, setMostRepeatedType] = useState<string>()

const [vaults, setVaults] = useState<Hooks.UseVaultsTypes.IVault[]>()
const [assets, setAssets] = useState<Hooks.UseAssetsTypes.IAssetDto[]>()
const [historyTransactions, setHistoryTransactions] = useState<
Hooks.UseHorizonTypes.ITransactions[]
>([])

const { userPermissions, getUserPermissions } = useAuth()
const { getSponsorPK } = useTransactions()
const { getTransactions, getAccount } = useHorizon()
const { getVaults } = useVaults()
const { getAssets } = useAssets()

useEffect(() => {
GAService.GAPageView('Coast center')
}, [])

useEffect(() => {
getUserPermissions()
getSponsorPK().then(sponsor => setSponsorAccount(sponsor))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

useEffect(() => {
getVaults(true).then(vaults => setVaults(vaults))
}, [getVaults])

useEffect(() => {
getAssets(true).then(assets => setAssets(assets))
}, [getAssets])

useEffect(() => {
if (sponsorAccount) {
getTransactions(sponsorAccount).then(transactions => {
setTransactions(transactions)
setLatestFeeCharged(
transactions?._embedded.records.reduce(
(total, transaction) => total + Number(transaction.fee_charged),
0
)
)
})
}
}, [sponsorAccount, getTransactions])

useEffect(() => {
if (sponsorAccount) {
getAccount(sponsorAccount).then(account => setAccountData(account))
}
}, [sponsorAccount, getTransactions, getAccount])

const getTransactionsByLink = (action: 'prev' | 'next'): void => {
if (action === 'prev') {
const transactionsPrev =
historyTransactions[historyTransactions.length - 1]
setTransactions(transactionsPrev)
setHistoryTransactions(previous => previous.slice(0, -1))
return
}

const link = transactions?._links.next.href
if (link) {
setHistoryTransactions(history => [...history, transactions])
getTransactions(undefined, link).then(transactions => {
setTransactions(transactions)
})
}
}

const formatAccount = (account: string): string => {
return `${account.substring(0, 4)}...${account.substring(
account.length - 4,
account.length
)}`
}

const walletToName = useCallback((publicKey: string): string => {
if (
assets &&
assets.find(asset => asset.distributor.key.publicKey === publicKey)
) {
return 'Asset issuer'
}

return (
vaults?.find(vault => vault.wallet.key.publicKey === publicKey)?.name ||
formatAccount(publicKey)
)
}, [assets, vaults])

const getTransactionData = useCallback(
(transaction: Hooks.UseHorizonTypes.ITransactionItem): IHorizonData => {
const accountCreated = transaction.operations?.find(
operation => operation.type === 'create_account'
)
if (accountCreated) {
return {
name: walletToName(accountCreated.account || ''),
type: 'Account created',
asset: '-',
}
}

const payment = transaction.operations?.find(
operation => operation.type === 'payment'
)

if (
payment &&
assets?.find(asset => asset.distributor.key.publicKey === payment.to)
) {
return {
name: '-',
type: 'Minted',
asset: payment.asset_code || '-',
}
}

if (
payment &&
assets?.find(asset => asset.distributor.key.publicKey === payment.from)
) {
return {
name: '-',
type: assets?.find(asset => asset.issuer.key.publicKey === payment.to)
? 'Burned'
: 'Distribute',
asset: payment.asset_code || '-',
}
}

if (payment) {
return {
name: `${walletToName(payment.from || '')} to ${walletToName(
payment.to || '-'
)}`,
type: 'Payment',
asset: payment.asset_code || '-',
}
}

const contractInvoke = transaction.operations?.find(
operation => operation.type === 'invoke_host_function'
)
if (contractInvoke) {
return {
name: walletToName(contractInvoke.source_account || ''),
type: 'Contract invoke',
asset: '-',
}
}

const trustlineOperation = transaction.operations?.find(
operation => operation.type === 'set_trust_line_flags'
)

if (trustlineOperation) {
if (trustlineOperation.set_flags_s?.includes('authorized')) {
return {
name: walletToName(trustlineOperation.trustor || ''),
type: 'Authorized',
asset: trustlineOperation.asset_code || '-',
}
}

if (trustlineOperation.clear_flags_s?.includes('authorized')) {
return {
name: walletToName(trustlineOperation.trustor || ''),
type: 'Freezed',
asset: trustlineOperation.asset_code || '-',
}
}
}

const clawback = transaction.operations?.find(
operation => operation.type === 'clawback'
)

if (clawback) {
return {
name: walletToName(clawback.from || ''),
type: 'Clawbacked',
asset: clawback.asset_code || '-',
}
}

const changeTrust = transaction.operations?.find(
operation => operation.type === 'change_trust'
)
if (changeTrust) {
return {
name: walletToName(changeTrust.trustor || ''),
type: 'Change trustline',
asset: changeTrust.asset_code || '-',
}
}

const contractRestore = transaction.operations?.find(
operation => operation.type === 'restore_footprint'
)
if (contractRestore) {
return {
name: walletToName(contractRestore.source_account || ''),
type: 'Contract restore',
asset: '-',
}
}

return {
name: '-',
type: '-',
asset: '-',
}
},
[assets, walletToName]
)

useEffect(() => {
const counter: Record<string, number> = {}

transactions?._embedded.records.forEach(transaction => {
const transactionData = getTransactionData(transaction)
counter[transactionData.type] = (counter[transactionData.type] || 0) + 1
})

let mostRepeatedType: string | undefined
let maxOccurrences = 0

for (const type in counter) {
if (counter[type] > maxOccurrences) {
maxOccurrences = counter[type]
mostRepeatedType = type
}
}

setMostRepeatedType(mostRepeatedType)
}, [transactions, assets, vaults, getTransactionData])

return (
<Flex>
<Sidebar highlightMenu={PathRoute.SETTINGS}>
<Flex
flexDir={isLargerThanMd ? 'row' : 'column'}
w="full"
justifyContent="center"
gap="1.5rem"
>
<Flex maxW="966px" flexDir="column" w="full">
<CostCenterTemplate
transactions={transactions}
userPermissions={userPermissions}
sponsorAccount={sponsorAccount}
accountData={accountData}
latestFeeCharged={latestFeeCharged}
vaults={vaults}
assets={assets}
isPrevDisabled={historyTransactions.length === 0}
mostRepeatedType={mostRepeatedType}
getTransactionsByLink={getTransactionsByLink}
getTransactionData={getTransactionData}
/>
</Flex>
{isLargerThanMd && (
<VStack>
<MenuSettings option={SettingsOptions.COST_CENTER} />
</VStack>
)}
</Flex>
</Sidebar>
</Flex>
)
}
6 changes: 6 additions & 0 deletions frontend/src/app/core/routes/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ClawbackAsset } from '../pages/clawback-asset'
import { Contracts } from '../pages/contracts'
import { ContractsCreate } from '../pages/contracts-create'
import { ContractsDetail } from '../pages/contracts-detail'
import { CostCenter } from '../pages/cost-center'
import { Dashboards } from '../pages/dashboards'
import { DistributeAsset } from '../pages/distribute-asset'
import { ForgeAsset } from '../pages/forge-asset'
Expand Down Expand Up @@ -133,4 +134,9 @@ export const coreRoutes: AppRoute[] = [
component: TomlFile,
isPrivate: true,
},
{
path: PathRoute.COST_CENTER,
component: CostCenter,
isPrivate: true,
},
]
1 change: 1 addition & 0 deletions frontend/src/components/enums/path-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ export enum PathRoute {
TOKEN_MANAGEMENT = '/token-management',
DASHBOARDS = '/dashboards',
TOML_FILE = '/.well-known/stellar.toml',
COST_CENTER = '/cost-center',
}
1 change: 1 addition & 0 deletions frontend/src/components/enums/settings-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export enum SettingsOptions {
TEAM_MEMBERS = 'Team members',
ROLE_PERMISSIONS = 'Role permissions',
ROLES_MANAGE = 'Roles',
COST_CENTER = 'Cost Center'
}
4 changes: 4 additions & 0 deletions frontend/src/components/icons/fonts/coins.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions frontend/src/components/icons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { ReactComponent as TransferIcon } from './fonts/transfer.svg'
import { ReactComponent as UsdcIcon } from './fonts/usdc.svg'
import { ReactComponent as VaultIcon } from './fonts/vault.svg'
import { ReactComponent as WalletIcon } from './fonts/wallet.svg'
import { ReactComponent as CoinsIcon } from './fonts/coins.svg'

export {
MenuIcon,
Expand Down Expand Up @@ -116,4 +117,5 @@ export {
ArrowForwardIcon,
CircleCheckIcon,
FileIcon,
CoinsIcon
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const ContractsBreadcrumb: React.FC<IContractsBreadcrumb> = ({
fill="gray.650"
_dark={{ fill: 'white' }}
>
<ContractIcon /> Yield-bearing asset
<ContractIcon /> Yield-bearing Asset
</BreadcrumbLink>
</BreadcrumbItem>

Expand Down
Loading

0 comments on commit 6724fd5

Please sign in to comment.