From 038159749a0f9da1eae2a48afb0793987f17ffae Mon Sep 17 00:00:00 2001
From: gmolki
Date: Mon, 25 Aug 2025 13:42:53 +0200
Subject: [PATCH 01/41] wip: credits dashboard
---
src/components/common/CompositeTitle/cmp.tsx | 12 +-
src/components/common/ToggleDashboard/cmp.tsx | 30 +-
.../account/ComputeResourceNodesPage/cmp.tsx | 10 +-
.../account/CoreChannelNodesPage/cmp.tsx | 9 +-
.../pages/account/StakingPage/cmp.tsx | 5 +-
.../DashboardPage/CreditsDashboard/cmp.tsx | 165 ++++++++
.../DashboardPage/CreditsDashboard/index.ts | 1 +
.../pages/console/DashboardPage/cmp.tsx | 366 +++++++++---------
8 files changed, 406 insertions(+), 192 deletions(-)
create mode 100644 src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx
create mode 100644 src/components/pages/console/DashboardPage/CreditsDashboard/index.ts
diff --git a/src/components/common/CompositeTitle/cmp.tsx b/src/components/common/CompositeTitle/cmp.tsx
index ab887f745..a3dcbe515 100644
--- a/src/components/common/CompositeTitle/cmp.tsx
+++ b/src/components/common/CompositeTitle/cmp.tsx
@@ -1,11 +1,11 @@
-import { memo } from 'react'
-import { CompositeTitle, CompositeTitleProps } from '@aleph-front/core'
+import { memo, ReactNode } from 'react'
+import { TextGradient } from '@aleph-front/core'
-export const SectionTitle = (props: CompositeTitleProps) => {
+export const SectionTitle = ({ children }: { children: ReactNode }) => {
return (
-
+
+ {children}
+
)
}
SectionTitle.displayName = 'SectionTitle'
diff --git a/src/components/common/ToggleDashboard/cmp.tsx b/src/components/common/ToggleDashboard/cmp.tsx
index fbda95468..0bb7d582a 100644
--- a/src/components/common/ToggleDashboard/cmp.tsx
+++ b/src/components/common/ToggleDashboard/cmp.tsx
@@ -1,19 +1,41 @@
-import { ReactNode, memo, useCallback, useRef, useState } from 'react'
+import {
+ Dispatch,
+ ReactNode,
+ SetStateAction,
+ memo,
+ useCallback,
+ useRef,
+ useState,
+} from 'react'
import tw from 'twin.macro'
import { Button, Icon, useTransition, useBounds } from '@aleph-front/core'
import { StyledButtonsContainer, StyledToggleContainer } from './styles'
export type ToggleDashboardProps = {
+ open: boolean
+ setOpen: Dispatch>
+
buttons?: ReactNode
+ toggleButton?: { children: ReactNode; disabled?: boolean }
children?: ReactNode
}
export const ToggleDashboard = ({
buttons,
children,
+ open,
+ setOpen,
+ toggleButton = {
+ children: (
+ <>
+
+ open dashboard
+ >
+ ),
+ disabled: false,
+ },
...rest
}: ToggleDashboardProps) => {
- const [open, setOpen] = useState(true)
const handleToogle = useCallback(() => setOpen((prev) => !prev), [])
const ref = useRef(null)
@@ -57,9 +79,9 @@ export const ToggleDashboard = ({
size="md"
onClick={handleToogle}
tw="gap-2.5"
+ disabled={toggleButton.disabled}
>
-
- open dashboard
+ {toggleButton.children}
)}
diff --git a/src/components/pages/account/ComputeResourceNodesPage/cmp.tsx b/src/components/pages/account/ComputeResourceNodesPage/cmp.tsx
index d82e02526..bf7489a90 100644
--- a/src/components/pages/account/ComputeResourceNodesPage/cmp.tsx
+++ b/src/components/pages/account/ComputeResourceNodesPage/cmp.tsx
@@ -1,4 +1,4 @@
-import { memo } from 'react'
+import { memo, useState } from 'react'
import Head from 'next/head'
import Link from 'next/link'
import {
@@ -72,6 +72,8 @@ export const ComputeResourceNodesPage = (
)
+ const [dashboardOpen, setDashboardOpen] = useState(true)
+
return (
<>
@@ -84,7 +86,11 @@ export const ComputeResourceNodesPage = (
Compute nodes
-
+
diff --git a/src/components/pages/account/CoreChannelNodesPage/cmp.tsx b/src/components/pages/account/CoreChannelNodesPage/cmp.tsx
index 54892d87e..52840779d 100644
--- a/src/components/pages/account/CoreChannelNodesPage/cmp.tsx
+++ b/src/components/pages/account/CoreChannelNodesPage/cmp.tsx
@@ -1,4 +1,4 @@
-import { memo } from 'react'
+import { memo, useState } from 'react'
import Head from 'next/head'
import {
Button,
@@ -64,6 +64,7 @@ export const CoreChannelNodesPage = (props: UseCoreChannelNodesPageProps) => {
)
+ const [dashboardOpen, setDashboardOpen] = useState(true)
return (
<>
@@ -77,7 +78,11 @@ export const CoreChannelNodesPage = (props: UseCoreChannelNodesPageProps) => {
Core nodes
-
+
diff --git a/src/components/pages/account/StakingPage/cmp.tsx b/src/components/pages/account/StakingPage/cmp.tsx
index 625fd8aaf..0d0f1c144 100644
--- a/src/components/pages/account/StakingPage/cmp.tsx
+++ b/src/components/pages/account/StakingPage/cmp.tsx
@@ -1,4 +1,4 @@
-import { memo } from 'react'
+import { memo, useState } from 'react'
import Head from 'next/head'
import {
Checkbox,
@@ -41,6 +41,7 @@ export const StakingPage = (props: UseStakingPageProps) => {
} = useStakingPage(props)
const { render } = useLazyRender()
+ const [dashboardOpen, setDashboardOpen] = useState(true)
return (
<>
@@ -57,7 +58,7 @@ export const StakingPage = (props: UseStakingPageProps) => {
Staking
-
+
diff --git a/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx b/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx
new file mode 100644
index 000000000..53fcd24ca
--- /dev/null
+++ b/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx
@@ -0,0 +1,165 @@
+import React, { useState } from 'react'
+import {
+ Button,
+ Icon,
+ NoisyContainer,
+ ObjectImg,
+ TextGradient,
+} from '@aleph-front/core'
+import { SectionTitle } from '@/components/common/CompositeTitle'
+
+import ToggleDashboard from '@/components/common/ToggleDashboard'
+import StyledTable from '@/components/common/Table'
+import ButtonLink from '@/components/common/ButtonLink'
+import tw from 'twin.macro'
+
+export default function CreditsDashboard() {
+ const [creditsDashboardOpen, setCreditsDashboardOpen] = useState(true)
+
+ const data = [
+ {
+ id: 1,
+ status: 'finished',
+ date: 1756121519678,
+ amount: 100,
+ asset: 'USDC',
+ credits: 120,
+ },
+ {
+ id: 2,
+ status: 'ongoing',
+ date: 1756121546481,
+ amount: 80,
+ asset: 'USDC',
+ credits: 95,
+ },
+ ]
+
+ return (
+
+ Credits
+
+ Open credits
+ >
+ ),
+ disabled: false,
+ }}
+ >
+
+
+
+
+
+
+ Top-up Credits
+
+
+
+
+
+
+
+
+
+ Purchases
+
+
+
+ History
+
+
+
row.id}
+ data={data}
+ columns={[
+ {
+ label: 'STATUS',
+ align: 'left',
+ sortable: true,
+ render: (row) => row.status,
+ },
+ {
+ label: 'DATE',
+ align: 'left',
+ sortable: true,
+ render: (row) => row.date,
+ },
+ {
+ label: 'AMOUNT',
+ align: 'left',
+ sortable: true,
+ render: (row) => row.amount,
+ },
+ {
+ label: 'ASSET',
+ align: 'left',
+ sortable: true,
+ render: (row) => row.asset,
+ },
+ {
+ label: 'CREDITS',
+ align: 'left',
+ sortable: true,
+ render: (row) => `~${row.credits}`,
+ },
+ {
+ label: '',
+ width: '100%',
+ align: 'right',
+ render: (row) => (
+
+
+
+
+
+ ),
+ cellProps: () => ({
+ css: tw`pl-3!`,
+ }),
+ },
+ ]}
+ />
+
+
+
+
+ )
+}
diff --git a/src/components/pages/console/DashboardPage/CreditsDashboard/index.ts b/src/components/pages/console/DashboardPage/CreditsDashboard/index.ts
new file mode 100644
index 000000000..7a9f83f3c
--- /dev/null
+++ b/src/components/pages/console/DashboardPage/CreditsDashboard/index.ts
@@ -0,0 +1 @@
+export { default } from './cmp'
diff --git a/src/components/pages/console/DashboardPage/cmp.tsx b/src/components/pages/console/DashboardPage/cmp.tsx
index 8186fffb3..3549c521d 100644
--- a/src/components/pages/console/DashboardPage/cmp.tsx
+++ b/src/components/pages/console/DashboardPage/cmp.tsx
@@ -1,6 +1,6 @@
-import React, { useMemo } from 'react'
+import React, { useMemo, useState } from 'react'
import Head from 'next/head'
-import { Icon } from '@aleph-front/core'
+import { Button, Icon, NoisyContainer, ObjectImg } from '@aleph-front/core'
import { useDashboardPage } from '@/components/pages/console/DashboardPage/hook'
import { SectionTitle } from '@/components/common/CompositeTitle'
import { CenteredContainer } from '@/components/common/CenteredContainer'
@@ -17,6 +17,8 @@ import {
EntityTypeObject,
NAVIGATION_URLS,
} from '@/helpers/constants'
+import ToggleDashboard from '@/components/common/ToggleDashboard'
+import CreditsDashboard from './CreditsDashboard'
export default function DashboardPage() {
const {
@@ -87,180 +89,192 @@ export default function DashboardPage() {
/>
-
-
- Web3 Hosting
-
-
-
- Experience the future of web hosting with our Web3 solutions.
- Whether you're building static sites or dynamic web apps with
- Next.js, React, or Vue.js, our platform offers seamless deployment
- and robust support. Connect your custom domains and leverage the
- power of decentralized technology.
-
-
-
-
-
-
-
-
-
- Computing
-
-
-
- Unleash the full potential of your applications with our advanced
- computing solutions. From serverless functions that run on-demand to
- fully managed instances and confidential VMs, our platform provides
- the flexibility and power you need. Secure, scalable, and easy to
- manage.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Storage
-
-
-
- Ensure your data is safe, secure, and always available with our
- cutting-edge storage solutions. Create immutable volumes for
- consistent and reliable data storage, perfect for dependency volumes
- and other critical data. Harness the power of decentralized storage
- with ease.
-
-
-
-
-
-
-
+
+
+
+
+
+ Computing
+
+
+
+ Unleash the full potential of your applications with our
+ advanced computing solutions. From serverless functions that run
+ on-demand to fully managed instances and confidential VMs, our
+ platform provides the flexibility and power you need. Secure,
+ scalable, and easy to manage.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Web3 Hosting
+
+
+
+ Experience the future of web hosting with our Web3 solutions.
+ Whether you're building static sites or dynamic web apps
+ with Next.js, React, or Vue.js, our platform offers seamless
+ deployment and robust support. Connect your custom domains and
+ leverage the power of decentralized technology.
+
+
+
+
+
+
+
+
+
+ Storage
+
+
+
+ Ensure your data is safe, secure, and always available with our
+ cutting-edge storage solutions. Create immutable volumes for
+ consistent and reliable data storage, perfect for dependency
+ volumes and other critical data. Harness the power of
+ decentralized storage with ease.
+
+
+
+
+
+
+
+
+
From 13c274e5523031470c03dea31f55262c5e1239b7 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Mon, 25 Aug 2025 17:18:35 +0200
Subject: [PATCH 02/41] wip: detail menu button
---
.../common/DetailsMenuButton/cmp.tsx | 65 +++++++
.../common/DetailsMenuButton/index.ts | 2 +
.../common/DetailsMenuButton/types.ts | 9 +
.../DashboardPage/CreditsDashboard/cmp.tsx | 159 +++++++++++-------
4 files changed, 173 insertions(+), 62 deletions(-)
create mode 100644 src/components/common/DetailsMenuButton/cmp.tsx
create mode 100644 src/components/common/DetailsMenuButton/index.ts
create mode 100644 src/components/common/DetailsMenuButton/types.ts
diff --git a/src/components/common/DetailsMenuButton/cmp.tsx b/src/components/common/DetailsMenuButton/cmp.tsx
new file mode 100644
index 000000000..ec80e701c
--- /dev/null
+++ b/src/components/common/DetailsMenuButton/cmp.tsx
@@ -0,0 +1,65 @@
+import { memo, RefObject, useCallback, useRef, useState } from 'react'
+import { Button, Icon, Tooltip } from '@aleph-front/core'
+import Link from 'next/link'
+
+type MenuItem = {
+ label: string
+ href: string
+}
+
+type DetailsMenuButtonProps = {
+ menuItems: MenuItem[]
+}
+
+export const DetailsMenuButton = ({ menuItems }: DetailsMenuButtonProps) => {
+ const [open, setOpen] = useState(false)
+ const tooltipRef: RefObject = useRef(null)
+
+ const handleClick = useCallback(() => setOpen(!open), [open, setOpen])
+ const handleClose = useCallback(() => setOpen(false), [setOpen])
+
+ return (
+ <>
+
+ {menuItems.map((item) => (
+
+ {item.label}
+
+ ))}
+
+ }
+ my="top-right"
+ at="bottom-right"
+ margin={{ x: 0, y: 2 }}
+ />
+
+
+
+
+
+ >
+ )
+}
+DetailsMenuButton.displayName = 'DetailsMenuButton'
+
+export default memo(DetailsMenuButton) as typeof DetailsMenuButton
diff --git a/src/components/common/DetailsMenuButton/index.ts b/src/components/common/DetailsMenuButton/index.ts
new file mode 100644
index 000000000..1e69fa948
--- /dev/null
+++ b/src/components/common/DetailsMenuButton/index.ts
@@ -0,0 +1,2 @@
+export { default } from './cmp'
+export type { ButtonLinkProps } from './types'
diff --git a/src/components/common/DetailsMenuButton/types.ts b/src/components/common/DetailsMenuButton/types.ts
new file mode 100644
index 000000000..da7f35fdf
--- /dev/null
+++ b/src/components/common/DetailsMenuButton/types.ts
@@ -0,0 +1,9 @@
+import { ButtonProps } from '@aleph-front/core'
+import { AnchorHTMLAttributes, ReactNode } from 'react'
+
+export type ButtonLinkProps = AnchorHTMLAttributes
&
+ Omit &
+ Partial> & {
+ href: string
+ children: ReactNode
+ }
diff --git a/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx b/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx
index 53fcd24ca..db74a4858 100644
--- a/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx
+++ b/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx
@@ -10,8 +10,8 @@ import { SectionTitle } from '@/components/common/CompositeTitle'
import ToggleDashboard from '@/components/common/ToggleDashboard'
import StyledTable from '@/components/common/Table'
-import ButtonLink from '@/components/common/ButtonLink'
import tw from 'twin.macro'
+import DetailsMenuButton from '@/components/common/DetailsMenuButton'
export default function CreditsDashboard() {
const [creditsDashboardOpen, setCreditsDashboardOpen] = useState(true)
@@ -94,69 +94,104 @@ export default function CreditsDashboard() {
- History
+ History
- row.id}
- data={data}
- columns={[
- {
- label: 'STATUS',
- align: 'left',
- sortable: true,
- render: (row) => row.status,
- },
- {
- label: 'DATE',
- align: 'left',
- sortable: true,
- render: (row) => row.date,
- },
- {
- label: 'AMOUNT',
- align: 'left',
- sortable: true,
- render: (row) => row.amount,
- },
- {
- label: 'ASSET',
- align: 'left',
- sortable: true,
- render: (row) => row.asset,
- },
- {
- label: 'CREDITS',
- align: 'left',
- sortable: true,
- render: (row) => `~${row.credits}`,
- },
- {
- label: '',
- width: '100%',
- align: 'right',
- render: (row) => (
-
-
-
-
-
- ),
- cellProps: () => ({
- css: tw`pl-3!`,
- }),
- },
- ]}
- />
+
+
row.id}
+ data={data}
+ columns={[
+ {
+ label: 'STATUS',
+ align: 'left',
+ sortable: true,
+ render: (row) => {
+ let color = 'warning'
+
+ switch (row.status) {
+ case 'finished':
+ color = 'success'
+ break
+ case 'ongoing':
+ color = 'warning'
+ break
+ case 'failed':
+ color = 'error'
+ break
+ }
+
+ return (
+
+
+
+
+
+ )
+ },
+ },
+ {
+ label: 'DATE',
+ align: 'left',
+ sortable: true,
+ render: (row) => row.date,
+ },
+ {
+ label: 'AMOUNT',
+ align: 'left',
+ sortable: true,
+ render: (row) => row.amount,
+ },
+ {
+ label: 'ASSET',
+ align: 'left',
+ sortable: true,
+ render: (row) => row.asset,
+ },
+ {
+ label: 'CREDITS',
+ align: 'left',
+ sortable: true,
+ render: (row) => `~${row.credits}`,
+ },
+ {
+ label: '',
+ width: '100%',
+ align: 'right',
+ render: (row) => {
+ return (
+
+ )
+ },
+ cellProps: () => ({
+ css: tw`pl-3!`,
+ }),
+ },
+ ]}
+ />
+
From 0a084e66c11044484cc0e93032317a5308484ce1 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Mon, 1 Sep 2025 12:36:39 +0200
Subject: [PATCH 03/41] wip: submenu table fix
---
.../common/DetailsMenuButton/cmp.tsx | 30 ++++++++++++++++++-
.../DashboardPage/CreditsDashboard/cmp.tsx | 5 +++-
2 files changed, 33 insertions(+), 2 deletions(-)
diff --git a/src/components/common/DetailsMenuButton/cmp.tsx b/src/components/common/DetailsMenuButton/cmp.tsx
index ec80e701c..1c2e91651 100644
--- a/src/components/common/DetailsMenuButton/cmp.tsx
+++ b/src/components/common/DetailsMenuButton/cmp.tsx
@@ -1,4 +1,11 @@
-import { memo, RefObject, useCallback, useRef, useState } from 'react'
+import {
+ memo,
+ RefObject,
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+} from 'react'
import { Button, Icon, Tooltip } from '@aleph-front/core'
import Link from 'next/link'
@@ -18,6 +25,27 @@ export const DetailsMenuButton = ({ menuItems }: DetailsMenuButtonProps) => {
const handleClick = useCallback(() => setOpen(!open), [open, setOpen])
const handleClose = useCallback(() => setOpen(false), [setOpen])
+ // Handle click outside of the tooltip to close it
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (
+ open &&
+ tooltipRef.current &&
+ !tooltipRef.current.contains(event.target as Node)
+ ) {
+ setOpen(false)
+ }
+ }
+
+ if (open) {
+ document.addEventListener('mousedown', handleClickOutside)
+ }
+
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside)
+ }
+ }, [open])
+
return (
<>
From 28745278acfa2c2617fd5ddb9a8cc25b11254a23 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Mon, 1 Sep 2025 13:59:12 +0200
Subject: [PATCH 04/41] feat: toggle dashboard states
---
.../DashboardPage/CreditsDashboard/cmp.tsx | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx b/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx
index 1afedbe32..84ba79bd1 100644
--- a/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx
+++ b/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react'
+import React, { useState, useEffect } from 'react'
import {
Button,
Icon,
@@ -12,9 +12,18 @@ import ToggleDashboard from '@/components/common/ToggleDashboard'
import StyledTable from '@/components/common/Table'
import tw from 'twin.macro'
import DetailsMenuButton from '@/components/common/DetailsMenuButton'
+import { useConnection } from '@/hooks/common/useConnection'
export default function CreditsDashboard() {
- const [creditsDashboardOpen, setCreditsDashboardOpen] = useState(true)
+ const [creditsDashboardOpen, setCreditsDashboardOpen] = useState(false)
+ const { account } = useConnection({ triggerOnMount: false })
+ const isConnected = !!account
+
+ useEffect(() => {
+ if (!isConnected && creditsDashboardOpen) {
+ setCreditsDashboardOpen(false)
+ }
+ }, [isConnected, creditsDashboardOpen])
const data = [
{
@@ -47,7 +56,7 @@ export default function CreditsDashboard() {
Open credits
>
),
- disabled: false,
+ disabled: !isConnected,
}}
>
From c5fef15addba09bfa16e1f9a9b18c06859f38174 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Tue, 2 Sep 2025 14:32:37 +0200
Subject: [PATCH 05/41] wip: checkout summary
---
src/components/form/CheckoutSummary/cmp.tsx | 102 ++++++------------
.../form/CheckoutSummaryFooter/cmp.tsx | 33 +++---
2 files changed, 50 insertions(+), 85 deletions(-)
diff --git a/src/components/form/CheckoutSummary/cmp.tsx b/src/components/form/CheckoutSummary/cmp.tsx
index 020686e21..cf6d5919e 100644
--- a/src/components/form/CheckoutSummary/cmp.tsx
+++ b/src/components/form/CheckoutSummary/cmp.tsx
@@ -185,30 +185,12 @@ export const CheckoutSummary = ({
})
const nftVoucherBalance = useNFTVoucherBalance()
- const disabledHold =
- disablePaymentMethod && paymentMethod !== PaymentMethod.Hold
- const disabledStream =
- disablePaymentMethod && paymentMethod !== PaymentMethod.Stream
-
- const paymentMethodSwitchNode = control && (
-
- )
-
return (
<>
)}
- {control && (
- <>
-
-
-
- Payment Method
-
-
{paymentMethodSwitchNode}
-
-
- >
- )}
-
- UNLOCKED
+ AVAILABLE CREDITS
CURRENT WALLET {ellipseAddress(address)}
-
+ {unlockedAmount}
+ {/*
*/}
{nftVoucherBalance > 0 && (
@@ -274,17 +244,21 @@ export const CheckoutSummary = ({
{/*
{line.detail}
*/}
{line.detail}
-
+
{line.cost !== 0 ? (
-
+
+ {line.cost}
+ / h
+
) : (
+ //
'-'
)}
@@ -324,42 +298,32 @@ export const CheckoutSummary = ({
})} */}
-
- {paymentMethod === PaymentMethod.Hold
- ? 'Total'
- : 'Total / h'}
+
Total credits / h
+
+
+ {/* */}
+
+ {cost?.cost}
+ / h
+
+
+
+
+
+ Min. required
-
+ {/* */}
+
+ {cost?.cost * 4}
+
- {paymentMethod === PaymentMethod.Stream && (
-
-
- Min. required
-
-
- )}
- {paymentMethod === PaymentMethod.Stream && receiverAddress && (
-
- )}
-
{buttonNode &&
{buttonNode}
}
diff --git a/src/components/form/CheckoutSummaryFooter/cmp.tsx b/src/components/form/CheckoutSummaryFooter/cmp.tsx
index 36b8c4789..8ecdc192f 100644
--- a/src/components/form/CheckoutSummaryFooter/cmp.tsx
+++ b/src/components/form/CheckoutSummaryFooter/cmp.tsx
@@ -2,8 +2,6 @@ import React, { cloneElement, isValidElement, memo } from 'react'
import { Button, ButtonProps } from '@aleph-front/core'
import { StyledSeparator } from './styles'
import { CheckoutSummaryFooterProps } from './types'
-import { PaymentMethod } from '@/helpers/constants'
-import Price from '@/components/common/Price'
import FloatingFooter from '../FloatingFooter'
// ------------------------------------------
@@ -35,23 +33,26 @@ export const CheckoutSummaryFooter = ({
...rest,
}}
>
-
-
{paymentMethodSwitchNode}
+
-
-
- {paymentMethod === PaymentMethod.Stream
- ? 'Total per hour'
- : 'Total hold'}
-
-
+
+ 3
+
+ Credits / h
+
+
+
+ $0.30/h
+
+
+ {/*
-
+ /> */}
{footerSubmitButtonNode}
From 3c174f773186ab659a0de30714559cedd6d8784a Mon Sep 17 00:00:00 2001
From: gmolki
Date: Tue, 2 Sep 2025 14:33:37 +0200
Subject: [PATCH 06/41] use staging
---
src/helpers/constants.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/helpers/constants.ts b/src/helpers/constants.ts
index 82b71895c..e53cda6c2 100644
--- a/src/helpers/constants.ts
+++ b/src/helpers/constants.ts
@@ -9,8 +9,8 @@ export const channel = 'FOUNDATION'
export const tags = ['mainnet']
export const postType = 'corechan-operation'
-export const apiServer = 'https://api.aleph.im'
-export const wsServer = 'wss://api.aleph.im'
+export const apiServer = 'http://51.159.106.166:4024'
+export const wsServer = 'ws://51.159.106.166:4024'
export const mbPerAleph = 3
export const communityWalletAddress =
From 092a9d3ef93f551c0b4cdd67a48792001bd3cd80 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Wed, 3 Sep 2025 12:17:02 +0200
Subject: [PATCH 07/41] fix: composite title
---
src/components/common/CompositeTitle/cmp.tsx | 19 +++++++++++---
src/components/common/CompositeTitle/index.ts | 2 +-
.../confidential/NewConfidentialPage/cmp.tsx | 20 ++++++++------
.../console/domain/NewDomainPage/cmp.tsx | 10 ++++---
.../console/function/ManageFunction/hook.ts | 4 +--
.../console/function/NewFunctionPage/cmp.tsx | 22 +++++++++++-----
.../gpuInstance/NewGpuInstancePage/cmp.tsx | 26 +++++++++++--------
.../console/instance/NewInstancePage/cmp.tsx | 26 +++++++++++--------
.../console/sshKey/NewSSHKeyPage/cmp.tsx | 6 +++--
.../console/volume/NewVolumePage/cmp.tsx | 4 +--
.../console/website/NewWebsitePage/cmp.tsx | 18 ++++++++-----
11 files changed, 100 insertions(+), 57 deletions(-)
diff --git a/src/components/common/CompositeTitle/cmp.tsx b/src/components/common/CompositeTitle/cmp.tsx
index a3dcbe515..2cf726e1b 100644
--- a/src/components/common/CompositeTitle/cmp.tsx
+++ b/src/components/common/CompositeTitle/cmp.tsx
@@ -1,5 +1,18 @@
-import { memo, ReactNode } from 'react'
-import { TextGradient } from '@aleph-front/core'
+import { ReactNode } from 'react'
+import {
+ CompositeTitle,
+ CompositeTitleProps,
+ TextGradient,
+} from '@aleph-front/core'
+
+export const CompositeSectionTitle = (props: CompositeTitleProps) => {
+ return (
+
+ )
+}
+CompositeSectionTitle.displayName = 'CompositeSectionTitle'
export const SectionTitle = ({ children }: { children: ReactNode }) => {
return (
@@ -9,5 +22,3 @@ export const SectionTitle = ({ children }: { children: ReactNode }) => {
)
}
SectionTitle.displayName = 'SectionTitle'
-
-export default memo(SectionTitle) as typeof SectionTitle
diff --git a/src/components/common/CompositeTitle/index.ts b/src/components/common/CompositeTitle/index.ts
index 1b31447c7..6f6984db6 100644
--- a/src/components/common/CompositeTitle/index.ts
+++ b/src/components/common/CompositeTitle/index.ts
@@ -1 +1 @@
-export { default, default as SectionTitle } from './cmp'
+export { SectionTitle, CompositeSectionTitle } from './cmp'
diff --git a/src/components/pages/console/confidential/NewConfidentialPage/cmp.tsx b/src/components/pages/console/confidential/NewConfidentialPage/cmp.tsx
index 27d531203..ccb3a85cc 100644
--- a/src/components/pages/console/confidential/NewConfidentialPage/cmp.tsx
+++ b/src/components/pages/console/confidential/NewConfidentialPage/cmp.tsx
@@ -1,7 +1,7 @@
import Head from 'next/head'
import BackButtonSection from '@/components/common/BackButtonSection'
import { CenteredContainer } from '@/components/common/CenteredContainer'
-import { SectionTitle } from '@/components/common/CompositeTitle'
+import { CompositeSectionTitle } from '@/components/common/CompositeTitle'
import {
BulletItem,
BulletList,
@@ -97,7 +97,11 @@ export default function NewConfidentialPage() {
Requirements}
+ toggleTitle={
+
+ Requirements
+
+ }
>
@@ -153,9 +157,9 @@ export default function NewConfidentialPage() {
+
Create encrypted disk image
-
+
}
>
@@ -294,9 +298,9 @@ export default function NewConfidentialPage() {
+
Upload encrypted disk image
-
+
}
>
@@ -369,9 +373,9 @@ export default function NewConfidentialPage() {
+
Create Confidential Instance
-
+
}
>
diff --git a/src/components/pages/console/domain/NewDomainPage/cmp.tsx b/src/components/pages/console/domain/NewDomainPage/cmp.tsx
index 0b576e61d..19c01691f 100644
--- a/src/components/pages/console/domain/NewDomainPage/cmp.tsx
+++ b/src/components/pages/console/domain/NewDomainPage/cmp.tsx
@@ -20,7 +20,7 @@ import {
NAVIGATION_URLS,
} from '@/helpers/constants'
import { useNewDomainPage } from './hook'
-import { SectionTitle } from '@/components/common/CompositeTitle'
+import { CompositeSectionTitle } from '@/components/common/CompositeTitle'
import BackButtonSection from '@/components/common/BackButtonSection'
export default function NewDomain() {
@@ -67,7 +67,9 @@ export default function NewDomain() {
@@ -131,6 +136,7 @@ const PaymentCard = ({ paymentData }: { paymentData: PaymentData }) => {
* Takes an array of payment data objects and renders a card for each one
*/
export const EntityPayment = ({ payments }: EntityPaymentProps) => {
+ console.log('Rendering EntityPayment with payments:', payments)
return (
<>
diff --git a/src/components/common/entityData/EntityPayment/hook.ts b/src/components/common/entityData/EntityPayment/hook.ts
index eee7ae4d8..e78fd4181 100644
--- a/src/components/common/entityData/EntityPayment/hook.ts
+++ b/src/components/common/entityData/EntityPayment/hook.ts
@@ -45,17 +45,24 @@ export function useFormatPayment(
[paymentType],
)
+ // Determine if payment is pay-as-you-go
+ const isCredit = useMemo(
+ () => paymentType === PaymentType.credit,
+ [paymentType],
+ )
+
// Format total spent amount using the time components for PAYG type
const totalSpent = useMemo(() => {
if (!cost) return
- if (!isStream) return cost.toString()
+ if (!isStream && !isCredit) return cost.toString()
if (!runningTime) return
// Use only the remainder (hours, minutes, seconds) from runningTime
const { days, hours, minutes } = getTimeComponents(runningTime)
const runningTimeInHours = days * 24 + hours + minutes / 60
+
return (cost * runningTimeInHours).toFixed(6)
- }, [cost, isStream, runningTime])
+ }, [cost, isStream, isCredit, runningTime])
// Format blockchain name
const formattedBlockchain = useMemo(() => {
@@ -65,12 +72,12 @@ export function useFormatPayment(
// Format flow rate to show daily cost
const formattedFlowRate = useMemo(() => {
- if (!isStream) return
+ if (!isStream && !isCredit) return
if (!cost) return
const dailyRate = cost * 24
return `~${dailyRate.toFixed(4)}/day`
- }, [cost, isStream])
+ }, [cost, isCredit, isStream])
// Format start date
const formattedStartDate = useMemo(() => {
@@ -120,6 +127,7 @@ export function useFormatPayment(
return {
isStream,
+ isCredit,
totalSpent,
formattedBlockchain,
formattedFlowRate,
diff --git a/src/components/common/entityData/EntityPayment/types.ts b/src/components/common/entityData/EntityPayment/types.ts
index a6edeb29c..53472b8d5 100644
--- a/src/components/common/entityData/EntityPayment/types.ts
+++ b/src/components/common/entityData/EntityPayment/types.ts
@@ -16,6 +16,11 @@ export interface HoldingPaymentData extends BasePaymentData {
paymentType: PaymentType.hold
}
+// Credit payment data (credit payment)
+export interface CreditPaymentData extends BasePaymentData {
+ paymentType: PaymentType.credit
+}
+
// Stream payment data (pay-as-you-go)
export interface StreamPaymentData extends BasePaymentData {
paymentType: PaymentType.superfluid
@@ -23,7 +28,10 @@ export interface StreamPaymentData extends BasePaymentData {
}
// Union type for all payment data types
-export type PaymentData = HoldingPaymentData | StreamPaymentData
+export type PaymentData =
+ | HoldingPaymentData
+ | CreditPaymentData
+ | StreamPaymentData
// Props for the EntityPayment component - just an array of payment data
export interface EntityPaymentProps {
@@ -33,6 +41,7 @@ export interface EntityPaymentProps {
// Formatted data returned by the hook for display
export interface FormattedPaymentData {
isStream: boolean
+ isCredit: boolean
totalSpent?: string
formattedBlockchain?: string
formattedFlowRate?: string
diff --git a/src/components/form/CheckoutSummary/cmp.tsx b/src/components/form/CheckoutSummary/cmp.tsx
index 213f4b879..e0b13942a 100644
--- a/src/components/form/CheckoutSummary/cmp.tsx
+++ b/src/components/form/CheckoutSummary/cmp.tsx
@@ -2,6 +2,7 @@ import {
ellipseAddress,
convertByteUnits,
humanReadableSize,
+ humanReadableCurrency,
} from '@/helpers/utils'
import { Label, StyledHoldingSummaryLine } from './styles'
import {
@@ -206,10 +207,7 @@ export const CheckoutSummary = ({
AVAILABLE CREDITS
CURRENT WALLET {ellipseAddress(address)}
-
- {unlockedAmount}
- {/*
*/}
-
+ {humanReadableCurrency(unlockedAmount)}
{nftVoucherBalance > 0 && (
{/* */}
- {cost?.cost}
+ {humanReadableCurrency(cost?.cost)}
/ h
@@ -307,7 +305,7 @@ export const CheckoutSummary = ({
{/* */}
- {cost?.cost * 4}
+ {humanReadableCurrency(cost?.cost * 4)}
diff --git a/src/components/form/CheckoutSummaryFooter/cmp.tsx b/src/components/form/CheckoutSummaryFooter/cmp.tsx
index 202b8ad1b..6fe260765 100644
--- a/src/components/form/CheckoutSummaryFooter/cmp.tsx
+++ b/src/components/form/CheckoutSummaryFooter/cmp.tsx
@@ -3,6 +3,7 @@ import { Button, ButtonProps } from '@aleph-front/core'
import { StyledSeparator } from './styles'
import { CheckoutSummaryFooterProps } from './types'
import FloatingFooter from '../FloatingFooter'
+import { humanReadableCurrency } from '@/helpers/utils'
// ------------------------------------------
@@ -35,22 +36,21 @@ export const CheckoutSummaryFooter = ({
- 3
+
+ {humanReadableCurrency(totalCost)}
+
Credits / h
-
$0.30/h
-
+
*/}
- {/* */}
{footerSubmitButtonNode}
diff --git a/src/components/pages/console/function/ManageFunction/hook.ts b/src/components/pages/console/function/ManageFunction/hook.ts
index 17eaef50e..ce5674a4b 100644
--- a/src/components/pages/console/function/ManageFunction/hook.ts
+++ b/src/components/pages/console/function/ManageFunction/hook.ts
@@ -11,6 +11,7 @@ import {
import { ellipseAddress } from '@/helpers/utils'
import useDownloadLogs from '@/hooks/common/useDownloadLogs'
import {
+ CreditPaymentData,
HoldingPaymentData,
PaymentData,
} from '@/components/common/entityData/EntityPayment/types'
@@ -195,6 +196,17 @@ export function useManageFunction(): ManageFunction {
loading,
} as HoldingPaymentData,
]
+ case PaymentType.credit:
+ return [
+ {
+ cost,
+ paymentType: PaymentType.credit,
+ runningTime,
+ startTime: program.time,
+ blockchain: program.payment.chain,
+ loading,
+ } as CreditPaymentData,
+ ]
default:
return [
{
diff --git a/src/components/pages/console/function/NewFunctionPage/hook.ts b/src/components/pages/console/function/NewFunctionPage/hook.ts
index 080a39e44..936813dd3 100644
--- a/src/components/pages/console/function/NewFunctionPage/hook.ts
+++ b/src/components/pages/console/function/NewFunctionPage/hook.ts
@@ -31,7 +31,10 @@ import Err from '@/helpers/errors'
import { useDefaultTiers } from '@/hooks/common/pricing/useDefaultTiers'
import { useCanAfford } from '@/hooks/common/useCanAfford'
import { useConnection } from '@/hooks/common/useConnection'
-import { CreditPaymentConfiguration } from '@/domain/executable'
+import {
+ CreditPaymentConfiguration,
+ PaymentConfiguration,
+} from '@/domain/executable'
import { BlockchainId } from '@/domain/connect/base'
export type NewFunctionFormState = NameAndTagsField & {
@@ -49,7 +52,7 @@ export type NewFunctionFormState = NameAndTagsField & {
export type UseNewFunctionPage = {
address: string
- accountBalance: number
+ accountCreditBalance: number
createFunctionDisabled: boolean
createFunctionButtonTitle: string
values: any
@@ -66,7 +69,7 @@ export function useNewFunctionPage(): UseNewFunctionPage {
const {
blockchain,
account,
- balance: accountBalance = 0,
+ creditBalance: accountCreditBalance = 0,
handleConnect,
} = useConnection({
triggerOnMount: false,
@@ -152,6 +155,13 @@ export function useNewFunctionPage(): UseNewFunctionPage {
// @note: dont use watch, use useWatch instead: https://github.com/react-hook-form/react-hook-form/issues/10753
const values = useWatch({ control }) as NewFunctionFormState
+ const payment: PaymentConfiguration = useMemo(() => {
+ return {
+ chain: blockchain,
+ type: PaymentMethod.Credit,
+ } as CreditPaymentConfiguration
+ }, [blockchain])
+
const costProps: UseProgramCostProps = useMemo(
() => ({
entityType: EntityType.Program,
@@ -162,17 +172,18 @@ export function useNewFunctionPage(): UseNewFunctionPage {
volumes: values.volumes,
domains: values.domains,
paymentMethod: values.paymentMethod,
+ payment,
code: values.code,
},
}),
- [values],
+ [payment, values],
)
const cost = useEntityCost(costProps)
const { canAfford, isCreateButtonDisabled } = useCanAfford({
cost,
- accountBalance,
+ accountCreditBalance,
})
// Checks if user can afford with current balance
@@ -200,7 +211,7 @@ export function useNewFunctionPage(): UseNewFunctionPage {
return {
address: account?.address || '',
- accountBalance,
+ accountCreditBalance,
createFunctionDisabled,
createFunctionButtonTitle,
values,
diff --git a/src/components/pages/console/gpuInstance/NewGpuInstancePage/hook.ts b/src/components/pages/console/gpuInstance/NewGpuInstancePage/hook.ts
index c2e880f46..a61932f21 100644
--- a/src/components/pages/console/gpuInstance/NewGpuInstancePage/hook.ts
+++ b/src/components/pages/console/gpuInstance/NewGpuInstancePage/hook.ts
@@ -74,7 +74,7 @@ export type Modal = 'node-list' | 'terms-and-conditions'
export type UseNewGpuInstancePageReturn = {
address: string
- accountBalance: number
+ accountCreditBalance: number
blockchainName: string
manuallySelectCRNDisabled: boolean
manuallySelectCRNDisabledMessage?: TooltipProps['content']
@@ -110,7 +110,7 @@ export function useNewGpuInstancePage(): UseNewGpuInstancePageReturn {
const {
blockchain,
account,
- balance: accountBalance = 0,
+ creditBalance: accountCreditBalance = 0,
handleConnect,
} = useConnection({
triggerOnMount: false,
@@ -344,7 +344,7 @@ export function useNewGpuInstancePage(): UseNewGpuInstancePageReturn {
const { canAfford, isCreateButtonDisabled } = useCanAfford({
cost,
- accountBalance,
+ accountCreditBalance,
})
// Checks if user can afford with current balance
@@ -447,7 +447,7 @@ export function useNewGpuInstancePage(): UseNewGpuInstancePageReturn {
return {
address,
- accountBalance,
+ accountCreditBalance,
blockchainName,
createInstanceDisabled,
createInstanceButtonTitle,
diff --git a/src/components/pages/console/instance/NewInstancePage/cmp.tsx b/src/components/pages/console/instance/NewInstancePage/cmp.tsx
index a2a02ba0b..78d9cda88 100644
--- a/src/components/pages/console/instance/NewInstancePage/cmp.tsx
+++ b/src/components/pages/console/instance/NewInstancePage/cmp.tsx
@@ -40,7 +40,7 @@ import CheckoutButton from '@/components/form/CheckoutButton'
export default function NewInstancePage({ mainRef }: PageProps) {
const {
address,
- accountBalance,
+ accountCreditBalance,
blockchainName,
manuallySelectCRNDisabled,
manuallySelectCRNDisabledMessage,
@@ -426,7 +426,7 @@ export default function NewInstancePage({ mainRef }: PageProps) {
control={control}
address={address}
cost={cost}
- unlockedAmount={accountBalance}
+ unlockedAmount={accountCreditBalance}
mainRef={mainRef}
description={
<>
diff --git a/src/components/pages/console/instance/NewInstancePage/hook.ts b/src/components/pages/console/instance/NewInstancePage/hook.ts
index bf6414252..3919e5817 100644
--- a/src/components/pages/console/instance/NewInstancePage/hook.ts
+++ b/src/components/pages/console/instance/NewInstancePage/hook.ts
@@ -74,7 +74,7 @@ export type Modal = 'node-list' | 'terms-and-conditions'
export type UseNewInstancePageReturn = {
address: string
- accountBalance: number
+ accountCreditBalance: number
blockchainName: string
manuallySelectCRNDisabled: boolean
manuallySelectCRNDisabledMessage?: TooltipProps['content']
@@ -111,7 +111,7 @@ export function useNewInstancePage(): UseNewInstancePageReturn {
const {
blockchain,
account,
- balance: accountBalance = 0,
+ creditBalance: accountCreditBalance = 0,
handleConnect,
} = useConnection({
triggerOnMount: false,
@@ -329,7 +329,7 @@ export function useNewInstancePage(): UseNewInstancePageReturn {
const { canAfford, isCreateButtonDisabled } = useCanAfford({
cost,
- accountBalance,
+ accountCreditBalance,
})
// Checks if user can afford with current balance
@@ -436,7 +436,7 @@ export function useNewInstancePage(): UseNewInstancePageReturn {
return {
address,
- accountBalance,
+ accountCreditBalance,
blockchainName,
createInstanceDisabled,
createInstanceButtonTitle,
diff --git a/src/components/pages/console/volume/NewVolumePage/cmp.tsx b/src/components/pages/console/volume/NewVolumePage/cmp.tsx
index 578dfc65a..1633bf45c 100644
--- a/src/components/pages/console/volume/NewVolumePage/cmp.tsx
+++ b/src/components/pages/console/volume/NewVolumePage/cmp.tsx
@@ -15,7 +15,7 @@ export function NewVolumePage() {
const {
control,
address,
- accountBalance,
+ accountCreditBalance,
isCreateButtonDisabled,
errors,
cost,
@@ -43,8 +43,7 @@ export function NewVolumePage() {
This amount needs to be present in your wallet until the volume is
diff --git a/src/components/pages/console/volume/NewVolumePage/hook.ts b/src/components/pages/console/volume/NewVolumePage/hook.ts
index 84f2d0844..d5969b467 100644
--- a/src/components/pages/console/volume/NewVolumePage/hook.ts
+++ b/src/components/pages/console/volume/NewVolumePage/hook.ts
@@ -30,7 +30,7 @@ export const defaultValues: NewVolumeFormState = {
export type UseNewVolumePageReturn = {
address: string
- accountBalance: number
+ accountCreditBalance: number
isCreateButtonDisabled: boolean
values: any
control: Control
@@ -43,7 +43,8 @@ export type UseNewVolumePageReturn = {
export function useNewVolumePage(): UseNewVolumePageReturn {
const router = useRouter()
const [appState, dispatch] = useAppState()
- const { account, balance: accountBalance = 0 } = appState.connection
+ const { account, creditBalance: accountCreditBalance = 0 } =
+ appState.connection
const manager = useVolumeManager()
const { next, stop } = useCheckoutNotification({})
@@ -107,7 +108,7 @@ export function useNewVolumePage(): UseNewVolumePageReturn {
const { isCreateButtonDisabled } = useCanAfford({
cost,
- accountBalance,
+ accountCreditBalance,
})
const handleBack = () => {
@@ -116,7 +117,7 @@ export function useNewVolumePage(): UseNewVolumePageReturn {
return {
address: account?.address || '',
- accountBalance,
+ accountCreditBalance,
isCreateButtonDisabled,
values,
control,
diff --git a/src/components/pages/console/website/NewWebsitePage/cmp.tsx b/src/components/pages/console/website/NewWebsitePage/cmp.tsx
index 245d3cf5c..ae4e90166 100644
--- a/src/components/pages/console/website/NewWebsitePage/cmp.tsx
+++ b/src/components/pages/console/website/NewWebsitePage/cmp.tsx
@@ -22,7 +22,7 @@ export default function NewWebsitePage({ mainRef }: PageProps) {
const {
control,
address,
- accountBalance,
+ accountCreditBalance,
isCreateButtonDisabled,
errors,
cost,
@@ -133,8 +133,7 @@ export default function NewWebsitePage({ mainRef }: PageProps) {
control={control}
address={address}
cost={cost}
- unlockedAmount={accountBalance}
- paymentMethod={PaymentMethod.Hold}
+ unlockedAmount={accountCreditBalance}
mainRef={mainRef}
description={
<>
diff --git a/src/components/pages/console/website/NewWebsitePage/hook.ts b/src/components/pages/console/website/NewWebsitePage/hook.ts
index 382fc78e4..7efa642ba 100644
--- a/src/components/pages/console/website/NewWebsitePage/hook.ts
+++ b/src/components/pages/console/website/NewWebsitePage/hook.ts
@@ -39,12 +39,12 @@ export type NewWebsiteFormState = NameAndTagsField &
export const defaultValues: Partial = {
...defaultNameAndTags,
- paymentMethod: PaymentMethod.Hold,
+ paymentMethod: PaymentMethod.Credit,
}
export type UseNewWebsitePagePageReturn = {
address: string
- accountBalance: number
+ accountCreditBalance: number
isCreateButtonDisabled: boolean
values: any
control: Control
@@ -57,7 +57,8 @@ export type UseNewWebsitePagePageReturn = {
export function useNewWebsitePage(): UseNewWebsitePagePageReturn {
const router = useRouter()
const [appState, dispatch] = useAppState()
- const { account, balance: accountBalance = 0 } = appState.connection
+ const { account, creditBalance: accountCreditBalance = 0 } =
+ appState.connection
const manager = useWebsiteManager()
const { next, stop } = useCheckoutNotification({})
@@ -69,7 +70,7 @@ export function useNewWebsitePage(): UseNewWebsitePagePageReturn {
// @todo: Refactor this
const payment: WebsitePayment = {
chain: BlockchainId.ETH,
- type: PaymentMethod.Hold,
+ type: PaymentMethod.Credit,
}
const website = {
@@ -137,7 +138,7 @@ export function useNewWebsitePage(): UseNewWebsitePagePageReturn {
const { isCreateButtonDisabled } = useCanAfford({
cost,
- accountBalance,
+ accountCreditBalance,
})
const handleBack = () => {
@@ -152,7 +153,7 @@ export function useNewWebsitePage(): UseNewWebsitePagePageReturn {
return {
address: account?.address || '',
- accountBalance,
+ accountCreditBalance,
isCreateButtonDisabled,
values,
control,
diff --git a/src/domain/connect/base.ts b/src/domain/connect/base.ts
index 7a9648aef..f7d6f829b 100644
--- a/src/domain/connect/base.ts
+++ b/src/domain/connect/base.ts
@@ -196,7 +196,7 @@ export abstract class BaseConnectionProviderManager {
const blockchain = blockchainId || (await this.getBlockchain())
const account = await this.getAccount()
- const balance = await this.getBalance(account)
+ const { balance } = await this.getBalance(account)
this.events.emit('update', {
provider: this.providerId,
@@ -279,7 +279,9 @@ export abstract class BaseConnectionProviderManager {
return account
}
- async getBalance(account: Account): Promise {
+ async getBalance(
+ account: Account,
+ ): Promise<{ balance: number; creditBalance: number }> {
return getAccountBalance(account, PaymentMethod.Hold)
}
diff --git a/src/domain/cost.ts b/src/domain/cost.ts
index f15d41954..6f79b9854 100644
--- a/src/domain/cost.ts
+++ b/src/domain/cost.ts
@@ -93,7 +93,8 @@ export class CostManager {
) {}
protected static readonly aggregateSourceAddress =
- '0xFba561a84A537fCaa567bb7A2257e7142701ae2A'
+ // '0xFba561a84A537fCaa567bb7A2257e7142701ae2A'
+ '0xA07B1214bAe0D5ccAA25449C3149c0aC83658874'
async getSettingsAggregate(): Promise {
const response = await this.sdkClient.fetchAggregate(
diff --git a/src/domain/executable.ts b/src/domain/executable.ts
index 2ebecf597..797b5a6d4 100644
--- a/src/domain/executable.ts
+++ b/src/domain/executable.ts
@@ -304,7 +304,6 @@ export abstract class ExecutableManager {
const nodes = await this.nodeManager.getAllCRNsSpecs()
// @note: 1) Try to filter the node by the requirements field on the executable message (legacy messages doesn't contain it)
-
let node = nodes.find(
(node) => node.hash === executable.requirements?.node?.node_hash,
)
@@ -318,6 +317,17 @@ export abstract class ExecutableManager {
return node
}
+ if (executable.payment?.type === PaymentType.credit) {
+ const nodes = await this.nodeManager.getAllCRNsSpecs()
+
+ // Try to filter the node by the requirements field on the executable message (legacy messages doesn't contain it)
+ const node = nodes.find(
+ (node) => node.hash === executable.requirements?.node?.node_hash,
+ )
+
+ return node
+ }
+
const query = await fetch(
`https://scheduler.api.aleph.sh/api/v0/allocation/${executable.id}`,
)
@@ -866,9 +876,13 @@ export abstract class ExecutableManager {
receiver: payment.receiver,
}
} else {
+ console.log('Parsing payment for cost estimation:', payment)
return {
chain: payment?.chain || BlockchainId.ETH,
- type: SDKPaymentType.hold,
+ type:
+ payment?.type === PaymentMethod.Credit
+ ? SDKPaymentType.credit
+ : SDKPaymentType.hold,
}
}
}
@@ -889,6 +903,12 @@ export abstract class ExecutableManager {
}
throw Err.StreamNotSupported
}
+ if (payment.type === PaymentMethod.Credit) {
+ return {
+ chain: payment.chain,
+ type: SDKPaymentType.credit,
+ }
+ }
return {
chain: payment.chain,
type: SDKPaymentType.hold,
@@ -927,13 +947,22 @@ export abstract class ExecutableManager {
{} as Record,
)
- const paymentMethod =
- costs.payment_type === PaymentType.hold
- ? PaymentMethod.Hold
- : PaymentMethod.Stream
-
- const costProp =
- paymentMethod === PaymentMethod.Hold ? 'cost_hold' : 'cost_stream'
+ let paymentMethod: PaymentMethod
+ let costProp: 'cost_hold' | 'cost_stream' | 'cost_credit'
+
+ switch (costs.payment_type) {
+ case PaymentType.hold:
+ paymentMethod = PaymentMethod.Hold
+ costProp = 'cost_hold'
+ break
+ case PaymentType.superfluid:
+ paymentMethod = PaymentMethod.Stream
+ costProp = 'cost_stream'
+ break
+ default:
+ paymentMethod = PaymentMethod.Credit
+ costProp = 'cost_credit'
+ }
// Execution
diff --git a/src/domain/instance.ts b/src/domain/instance.ts
index bd726cd4b..0e2d3d091 100644
--- a/src/domain/instance.ts
+++ b/src/domain/instance.ts
@@ -37,12 +37,8 @@ import { DomainField } from '@/hooks/form/useAddDomains'
import { DomainManager } from './domain'
import { EntityManager } from './types'
import { ForwardedPortsManager } from './forwardedPorts'
-import {
- instanceSchema,
- instanceStreamSchema,
-} from '@/helpers/schemas/instance'
+import { instanceSchema } from '@/helpers/schemas/instance'
import { NameAndTagsField } from '@/hooks/form/useAddNameAndTags'
-import { getHours } from '@/hooks/form/useSelectStreamDuration'
import { CRNSpecs, NodeManager } from './node'
import { CheckoutStepType } from '@/hooks/form/useCheckoutNotification'
import {
@@ -111,7 +107,6 @@ export class InstanceManager
implements EntityManager
{
static addSchema = instanceSchema
- static addStreamSchema = instanceStreamSchema
constructor(
protected account: Account | undefined,
@@ -252,14 +247,12 @@ export class InstanceManager
// @note: Send the instance creation message to the network
yield
+
const response = await this.sdkClient.createInstance({
...instanceMessage,
})
const [entity] = await this.parseMessages([response])
- // @note: Create PAYG superfluid flows
- yield* this.addPAYGStreamSteps(newInstance, account)
-
// @note: Add the domain link
yield* this.parseDomainsSteps(entity.id, newInstance.domains)
@@ -457,7 +450,7 @@ export class InstanceManager
| EntityType.GpuInstance = EntityType.Instance,
): Promise {
let totalCost = Number.POSITIVE_INFINITY
- const paymentMethod = newInstance.payment?.type || PaymentMethod.Hold
+ const paymentMethod = newInstance.payment?.type || PaymentMethod.Credit
const parsedInstance: InstancePublishConfiguration =
await this.parseInstanceForCostEstimation(newInstance)
@@ -571,67 +564,10 @@ export class InstanceManager
return instance
}
- protected async *addPAYGStreamSteps(
- newInstance: AddInstance,
- account?: SuperfluidAccount,
- ): AsyncGenerator {
- if (newInstance.payment?.type !== PaymentMethod.Stream) return
- if (!newInstance.node || !newInstance.node.address) throw Err.InvalidNode
- if (!account) throw Err.ConnectYourWallet
-
- const { streamCost, streamDuration, receiver } = newInstance.payment
-
- const { communityWalletAddress } =
- await this.costManager.getSettingsAggregate()
-
- const costByHour = streamCost / getHours(streamDuration)
- const streamCostByHourToReceiver = this.calculateReceiverFlow(costByHour)
- const streamCostByHourToCommunity = this.calculateCommunityFlow(costByHour)
-
- const alephxBalance = await account.getALEPHBalance()
- const recieverAlephxFlow = await account.getALEPHFlow(receiver)
- const communityAlephxFlow = await account.getALEPHFlow(
- communityWalletAddress,
- )
-
- const receiverTotalFlow = recieverAlephxFlow.add(streamCostByHourToReceiver)
- const communityTotalFlow = communityAlephxFlow.add(
- streamCostByHourToCommunity,
- )
-
- if (
- receiverTotalFlow.greaterThan(100) ||
- communityTotalFlow.greaterThan(100)
- )
- throw Err.MaxFlowRate
-
- const totalAlephxFlow = recieverAlephxFlow.add(communityAlephxFlow)
- const usedAlephInDuration = totalAlephxFlow.mul(getHours(streamDuration))
- const totalRequiredAleph = usedAlephInDuration.add(streamCost)
-
- if (alephxBalance.lt(totalRequiredAleph))
- throw Err.InsufficientBalance(
- totalRequiredAleph.sub(alephxBalance).toNumber(),
- )
-
- yield
-
- // Split the stream cost between the community wallet (20%) and the receiver (80%)
- await account.increaseALEPHFlow(
- communityWalletAddress,
- streamCostByHourToCommunity + EXTRA_WEI,
- )
- await account.increaseALEPHFlow(
- receiver,
- streamCostByHourToReceiver + EXTRA_WEI,
- )
- }
-
protected async *addPAYGReservationSteps(
newInstance: AddInstance,
instanceMessage: InstancePublishConfiguration,
): AsyncGenerator {
- if (newInstance.payment?.type !== PaymentMethod.Stream) return
if (!newInstance.node || !newInstance.node.address) throw Err.InvalidNode
yield
@@ -642,7 +578,6 @@ export class InstanceManager
newInstance: AddInstance,
entity: InstanceEntity,
): AsyncGenerator {
- if (newInstance.payment?.type !== PaymentMethod.Stream) return
if (!newInstance.node || !newInstance.node.address) throw Err.InvalidNode
yield
@@ -692,9 +627,7 @@ export class InstanceManager
): AsyncGenerator {
if (!this.account) throw Err.InvalidAccount
- const schema = !newInstance.node
- ? InstanceManager.addSchema
- : InstanceManager.addStreamSchema
+ const schema = InstanceManager.addSchema
newInstance = await schema.parseAsync(newInstance)
diff --git a/src/domain/program.ts b/src/domain/program.ts
index 4fee2d578..efa8034ed 100644
--- a/src/domain/program.ts
+++ b/src/domain/program.ts
@@ -271,11 +271,12 @@ export class ProgramManager
async getCost(newProgram: ProgramCostProps): Promise {
let totalCost = Number.POSITIVE_INFINITY
- const paymentMethod = newProgram.payment?.type || PaymentMethod.Hold
+ const paymentMethod = newProgram.payment?.type || PaymentMethod.Credit
const parsedProgram: ProgramPublishConfiguration =
await this.parseProgramForCostEstimation(newProgram)
+ console.log('Parsed program for cost estimation:', parsedProgram)
const costs =
await this.sdkClient.programClient.getEstimatedCost(parsedProgram)
@@ -353,6 +354,7 @@ export class ProgramManager
): Promise {
const { account = mockAccount, channel } = this
const { isPersistent, specs } = newProgram
+ console.log('Parsing program for cost estimation:', newProgram)
const parsedSpecs = this.parseSpecs(specs)
const memory = parsedSpecs?.memory
diff --git a/src/domain/volume.ts b/src/domain/volume.ts
index 1a5113d8a..dde0862b1 100644
--- a/src/domain/volume.ts
+++ b/src/domain/volume.ts
@@ -1,5 +1,10 @@
import { Account } from '@aleph-sdk/account'
-import { MessageCostLine, MessageType, StoreContent } from '@aleph-sdk/message'
+import {
+ MessageCostLine,
+ MessageType,
+ PaymentType,
+ StoreContent,
+} from '@aleph-sdk/message'
import Err from '@/helpers/errors'
import {
EntityType,
@@ -28,6 +33,7 @@ import {
} from '@aleph-sdk/client'
import { CostLine, CostSummary } from './cost'
import { mockAccount } from './account'
+import { Blockchain } from '@aleph-sdk/core'
export const mockVolumeRef =
'cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe'
@@ -312,7 +318,7 @@ export class VolumeManager implements EntityManager {
async getCost(props: VolumeCostProps): Promise {
let totalCost = Number.POSITIVE_INFINITY
- const { volume, paymentMethod = PaymentMethod.Hold } = props
+ const { volume, paymentMethod = PaymentMethod.Credit } = props
const emptyCost = {
paymentMethod,
@@ -332,6 +338,7 @@ export class VolumeManager implements EntityManager {
const costs = await this.sdkClient.storeClient.getEstimatedCost({
account,
fileObject: newVolume.file,
+ payment: { chain: Blockchain.ETH, type: PaymentType.credit },
})
totalCost = Number(costs.cost)
@@ -359,7 +366,9 @@ export class VolumeManager implements EntityManager {
cost:
paymentMethod === PaymentMethod.Hold
? +line.cost_hold
- : +line.cost_stream,
+ : paymentMethod === PaymentMethod.Stream
+ ? +line.cost_stream
+ : +line.cost_credit,
}))
}
}
diff --git a/src/domain/website.ts b/src/domain/website.ts
index 4c1402f3e..c21c91712 100644
--- a/src/domain/website.ts
+++ b/src/domain/website.ts
@@ -616,7 +616,7 @@ export class WebsiteManager implements EntityManager {
async getCost(props: WebsiteCostProps): Promise {
let totalCost = Number.POSITIVE_INFINITY
- const { website, paymentMethod = PaymentMethod.Hold } = props
+ const { website, paymentMethod = PaymentMethod.Credit } = props
const emptyCost: WebsiteCost = {
paymentMethod,
@@ -660,7 +660,9 @@ export class WebsiteManager implements EntityManager {
cost:
paymentMethod === PaymentMethod.Hold
? +line.cost_hold
- : +line.cost_stream,
+ : paymentMethod === PaymentMethod.Stream
+ ? +line.cost_stream
+ : +line.cost_credit,
}))
}
diff --git a/src/helpers/schemas/base.ts b/src/helpers/schemas/base.ts
index 588501e5f..c6bb565cf 100644
--- a/src/helpers/schemas/base.ts
+++ b/src/helpers/schemas/base.ts
@@ -174,6 +174,7 @@ export const targetSchema = z.enum([
export const paymentMethodSchema = z.enum([
PaymentMethod.Hold,
PaymentMethod.Stream,
+ PaymentMethod.Credit,
])
export const blockchainSchema = z.enum([
diff --git a/src/helpers/schemas/instance.ts b/src/helpers/schemas/instance.ts
index f21bf67fe..dc19bc61f 100644
--- a/src/helpers/schemas/instance.ts
+++ b/src/helpers/schemas/instance.ts
@@ -107,15 +107,6 @@ export const addSSHKeysSchema = z
path: ['0.isSelected'],
})
-// STREAM DURATION
-
-export const streamDurationUnitSchema = z.enum(['h', 'd', 'm', 'y'])
-
-export const streamDurationSchema = z.object({
- duration: z.coerce.number(),
- unit: streamDurationUnitSchema,
-})
-
export const systemVolumeSchema = z.object({
size: z
.number()
@@ -146,16 +137,10 @@ export const instanceBaseSchema = z
})
.merge(addNameAndTagsSchema)
-export const instanceSchema = instanceBaseSchema.superRefine(
- checkMinInstanceSystemVolumeSize,
-)
-
-export const instanceStreamSchema = instanceBaseSchema
+export const instanceSchema = instanceBaseSchema
.merge(
z.object({
nodeSpecs: nodeSpecsSchema,
- streamDuration: streamDurationSchema,
- streamCost: z.number(),
}),
)
.refine(
diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts
index 5c1946ad9..70664b23a 100644
--- a/src/helpers/utils.ts
+++ b/src/helpers/utils.ts
@@ -56,7 +56,7 @@ export const ellipseAddress = (address: string) => {
* Get the Aleph balance for a given blockchain address
*
* @param address An blockchain address
- * returns The Aleph balance of the address
+ * returns The Aleph Tokens and Credit balance of the address
*/
export const getAddressBalance = async (address: string) => {
try {
@@ -65,10 +65,14 @@ export const getAddressBalance = async (address: string) => {
)
// @note: 404 means the balance is 0, don't throw error in that case
- if (query.status === 404) return 0
+ if (query.status === 404) return { balance: 0, creditBalance: 0 }
- const { balance } = await query.json()
- return balance
+ const { balance, credit_balance } = await query.json()
+
+ return {
+ balance: balance as number,
+ creditBalance: (credit_balance as number) || 150000, // @todo: temporary hack to give free credit balance
+ }
} catch (error) {
throw Err.RequestFailed(error)
}
@@ -78,7 +82,7 @@ export async function getAccountBalance(
account: Account,
paymentMethod: PaymentMethod,
) {
- let balance: number
+ let balance = 0
if (paymentMethod === PaymentMethod.Stream && isAccountSupported(account)) {
try {
@@ -92,12 +96,14 @@ export async function getAccountBalance(
console.error(e)
balance = 0
}
- } else {
- // For Hold payment method, fetch balance from pyaleph API
- balance = await getAddressBalance(account.address)
}
- return balance
+ const addressBalance = await getAddressBalance(account.address)
+
+ return {
+ balance: balance || addressBalance.balance,
+ creditBalance: addressBalance.creditBalance || 0,
+ }
}
export function round(num: number, decimals = 2) {
diff --git a/src/hooks/common/useCanAfford.ts b/src/hooks/common/useCanAfford.ts
index 1239d1c08..c44152092 100644
--- a/src/hooks/common/useCanAfford.ts
+++ b/src/hooks/common/useCanAfford.ts
@@ -1,7 +1,7 @@
import { CostSummary } from '@/domain/cost'
export type UseCanAffordProps = {
- accountBalance: number
+ accountCreditBalance: number
cost?: CostSummary
}
@@ -11,11 +11,11 @@ export type UseCanAffordReturn = {
}
export function useCanAfford({
- accountBalance,
+ accountCreditBalance,
cost,
}: UseCanAffordProps): UseCanAffordReturn {
const canAfford =
- accountBalance >= (cost ? cost.cost : Number.MAX_SAFE_INTEGER)
+ accountCreditBalance >= (cost ? cost.cost : Number.MAX_SAFE_INTEGER)
const isCreateButtonDisabled =
process.env.NEXT_PUBLIC_OVERRIDE_ALEPH_BALANCE === 'true'
diff --git a/src/hooks/common/useEntity/useManageInstanceEntity.ts b/src/hooks/common/useEntity/useManageInstanceEntity.ts
index 121151c38..c3c53b6de 100644
--- a/src/hooks/common/useEntity/useManageInstanceEntity.ts
+++ b/src/hooks/common/useEntity/useManageInstanceEntity.ts
@@ -7,6 +7,7 @@ import {
useExecutableActions,
} from '@/hooks/common/useExecutableActions'
import {
+ CreditPaymentData,
HoldingPaymentData,
PaymentData,
StreamPaymentData,
@@ -217,6 +218,17 @@ export function useManageInstanceEntity<
}) as StreamPaymentData,
)
}
+ case PaymentType.credit:
+ return [
+ {
+ cost,
+ paymentType: PaymentType.credit,
+ runningTime,
+ startTime: entity.time,
+ blockchain: entity.payment.chain,
+ loading: loadingPaymentData,
+ } as CreditPaymentData,
+ ]
default:
return [
{
diff --git a/src/hooks/common/useEntityCost.ts b/src/hooks/common/useEntityCost.ts
index a80566bd7..18e1de309 100644
--- a/src/hooks/common/useEntityCost.ts
+++ b/src/hooks/common/useEntityCost.ts
@@ -52,7 +52,7 @@ export function useEntityCost(props: UseEntityCostProps): UseEntityCostReturn {
// Use useMemo to prevent the object from being recreated on every render
const emptyCost = useMemo(
() => ({
- paymentMethod: PaymentMethod.Hold,
+ paymentMethod: PaymentMethod.Credit,
cost: Number.POSITIVE_INFINITY,
lines: [],
}),
@@ -108,9 +108,11 @@ export function useEntityCost(props: UseEntityCostProps): UseEntityCostReturn {
result = await gpuInstanceManager.getCost(props)
break
case EntityType.Program:
+ console.log('Fetching program cost with props:', props)
if (programManager) result = await programManager.getCost(props)
break
case EntityType.Website:
+ console.log('Fetching website cost with props:', props)
if (websiteManager) result = await websiteManager.getCost(props)
break
}
diff --git a/src/hooks/common/useExecutableActions.ts b/src/hooks/common/useExecutableActions.ts
index a388c9cf2..5c5d6a32d 100644
--- a/src/hooks/common/useExecutableActions.ts
+++ b/src/hooks/common/useExecutableActions.ts
@@ -71,6 +71,7 @@ export function useExecutableActions({
subscribeLogs,
}: UseExecutableActionsProps): UseExecutableActionsReturn {
const isPAYG = executable?.payment?.type === PaymentType.superfluid
+ const isCredit = executable?.payment?.type === PaymentType.credit
const executableId = executable?.id
const { status, calculatedStatus } = useExecutableStatus({
@@ -120,6 +121,7 @@ export function useExecutableActions({
}, [executable, manager])
const nodeDetails = useMemo(() => {
+ console.log('crn', crn)
if (!crn) return
return {
name: crn.name || crn.hash,
@@ -177,47 +179,76 @@ export function useExecutableActions({
)
const handleStart = useCallback(async () => {
+ console.log('starting')
if (!manager) throw Err.ConnectYourWallet
if (!executable) throw Err.InstanceNotFound
- if (!isPAYG) throw Err.StreamNotSupported
- try {
- setStartLoading(true)
+ if (isPAYG) {
+ try {
+ setStartLoading(true)
- const instanceNetwork = executable.payment?.chain
- const incompatibleNetwork = checkNetworkCompatibility(instanceNetwork)
+ const instanceNetwork = executable.payment?.chain
+ const incompatibleNetwork = checkNetworkCompatibility(instanceNetwork)
- if (incompatibleNetwork) {
- throw Err.NetworkMismatch(incompatibleNetwork)
- }
+ if (incompatibleNetwork) {
+ throw Err.NetworkMismatch(incompatibleNetwork)
+ }
- if (isPAYG && !isAccountPAYGCompatible(account)) {
- throw Err.ConnectYourPaymentWallet
+ if (isPAYG && !isAccountPAYGCompatible(account)) {
+ throw Err.ConnectYourPaymentWallet
+ }
+
+ if (!crn) throw Err.InvalidCRNAddress
+
+ // For PAYG, notify CRN
+ if (isPAYG) {
+ await manager.notifyCRNAllocation(crn, executable.id)
+ }
+ } catch (e) {
+ noti?.add({
+ variant: 'error',
+ title: 'Error',
+ text: (e as Error)?.message,
+ })
+ } finally {
+ setStartLoading(false)
}
+ } else if (!isCredit) {
+ throw Err.StreamNotSupported
+ } else if (isCredit) {
+ try {
+ setStartLoading(true)
- if (!crn) throw Err.ConnectYourPaymentWallet
+ const instanceNetwork = executable.payment?.chain
+ const incompatibleNetwork = checkNetworkCompatibility(instanceNetwork)
- // For PAYG, notify CRN
- if (isPAYG) {
+ if (incompatibleNetwork) {
+ throw Err.NetworkMismatch(incompatibleNetwork)
+ }
+
+ if (!crn) throw Err.InvalidCRNAddress
+
+ // For PAYG, notify CRN
await manager.notifyCRNAllocation(crn, executable.id)
+ } catch (e) {
+ noti?.add({
+ variant: 'error',
+ title: 'Error',
+ text: (e as Error)?.message,
+ })
+ } finally {
+ setStartLoading(false)
}
- } catch (e) {
- noti?.add({
- variant: 'error',
- title: 'Error',
- text: (e as Error)?.message,
- })
- } finally {
- setStartLoading(false)
}
}, [
- crn,
+ manager,
executable,
isPAYG,
- manager,
- noti,
+ isCredit,
checkNetworkCompatibility,
account,
+ crn,
+ noti,
])
const isAllocated = !!status?.ipv6Parsed
diff --git a/src/hooks/common/usePaymentMethod.ts b/src/hooks/common/usePaymentMethod.ts
index 567613652..158a8556e 100644
--- a/src/hooks/common/usePaymentMethod.ts
+++ b/src/hooks/common/usePaymentMethod.ts
@@ -51,10 +51,13 @@ export function usePaymentMethod({
if (!account) return
try {
- const balance = await getAccountBalance(account, paymentMethod)
+ const { balance, creditBalance } = await getAccountBalance(
+ account,
+ paymentMethod,
+ )
- if (balance !== undefined) {
- dispatch(new ConnectionSetBalanceAction({ balance }))
+ if (balance !== undefined || creditBalance !== undefined) {
+ dispatch(new ConnectionSetBalanceAction({ balance, creditBalance }))
}
} catch (error) {
console.error('Error fetching balance:', error)
diff --git a/src/hooks/form/useSelectInstanceSpecs.ts b/src/hooks/form/useSelectInstanceSpecs.ts
index 29c99fac2..82070fd21 100644
--- a/src/hooks/form/useSelectInstanceSpecs.ts
+++ b/src/hooks/form/useSelectInstanceSpecs.ts
@@ -61,11 +61,12 @@ export function useSelectInstanceSpecs({
const filterValidNodeSpecs = useCallback(
(option: Tier) => {
+ if (type === EntityType.Program) return true
if (!nodeSpecs) return false
return nodeManager.validateMinNodeSpecs(option, nodeSpecs)
},
- [nodeManager, nodeSpecs],
+ [nodeManager, nodeSpecs, type],
)
// Process and cache valid voucher configurations
diff --git a/src/store/connection.ts b/src/store/connection.ts
index f73387b58..5f328a2e3 100644
--- a/src/store/connection.ts
+++ b/src/store/connection.ts
@@ -10,6 +10,7 @@ import { PaymentMethod } from '@/helpers/constants'
export type ConnectionState = {
account?: Account
balance?: number
+ creditBalance?: number
blockchain?: BlockchainId
provider?: ProviderId
paymentMethod: PaymentMethod
@@ -59,6 +60,7 @@ export class ConnectionUpdateAction {
provider: ProviderId
blockchain: BlockchainId
balance?: number
+ creditBalance?: number
},
) {}
}
@@ -68,6 +70,7 @@ export class ConnectionSetBalanceAction {
constructor(
public payload: {
balance: number
+ creditBalance?: number
},
) {}
}
@@ -113,6 +116,9 @@ export function getConnectionReducer(): ConnectionReducer {
let newProvider = provider
let newBalance =
(action as ConnectionUpdateAction).payload.balance || state.balance
+ let newCreditBalance =
+ (action as ConnectionUpdateAction).payload.creditBalance ||
+ state.creditBalance
// If we are switching between EVM and Solana, hardcode the provider
if (currentProvider) {
@@ -130,14 +136,17 @@ export function getConnectionReducer(): ConnectionReducer {
}
// If we are switching blockchains, reset the balance
- if (currentBlockchain && currentBlockchain !== blockchain)
+ if (currentBlockchain && currentBlockchain !== blockchain) {
newBalance = undefined
+ newCreditBalance = undefined
+ }
return {
...state,
...action.payload,
provider: newProvider,
balance: newBalance,
+ creditBalance: newCreditBalance,
}
}
case ConnectionActionType.CONNECTION_SET_BALANCE: {
From 734c2890846792a086ef49e42a0af8ffc897c691 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Tue, 30 Sep 2025 18:21:10 +0200
Subject: [PATCH 14/41] redirect to legacy console
---
src/components/common/ButtonLink/cmp.tsx | 26 +++++++++-
src/components/common/ButtonLink/types.ts | 5 +-
src/components/common/EntityCard/cmp.tsx | 6 +++
src/components/common/EntityCard/types.ts | 2 +
src/components/common/Header/cmp.tsx | 4 +-
src/components/common/NewEntityTab/cmp.tsx | 1 +
src/components/form/AddVolume/cmp.tsx | 1 +
.../pages/console/DashboardPage/cmp.tsx | 48 +++++++++++++++++--
.../function/FunctionDashboardPage/cmp.tsx | 12 +++--
.../function/FunctionsTabContent/cmp.tsx | 4 +-
.../pages/console/volume/ManageVolume/cmp.tsx | 4 +-
.../volume/VolumeDashboardPage/cmp.tsx | 16 ++++---
.../console/website/ManageWebsite/cmp.tsx | 4 +-
.../website/WebsiteDashboardPage/cmp.tsx | 10 ++--
.../website/WebsitesTabContent/cmp.tsx | 4 +-
src/helpers/constants.ts | 47 ++++++++++++++++++
src/hooks/form/useAddVolume.ts | 6 +--
17 files changed, 168 insertions(+), 32 deletions(-)
diff --git a/src/components/common/ButtonLink/cmp.tsx b/src/components/common/ButtonLink/cmp.tsx
index 12028a839..988e4bf99 100644
--- a/src/components/common/ButtonLink/cmp.tsx
+++ b/src/components/common/ButtonLink/cmp.tsx
@@ -1,7 +1,8 @@
-import { memo } from 'react'
+import { memo, useEffect, useRef, useState } from 'react'
import Link from 'next/link'
import { Button } from '@aleph-front/core'
import { ButtonLinkProps } from './types'
+import ResponsiveTooltip from '../ResponsiveTooltip'
/**
* A wrapper for the nextjs links that are styled as buttons
@@ -14,9 +15,19 @@ export const ButtonLink = ({
kind = 'default',
size = 'md',
disabled,
+ disabledMessage,
+ tooltipPosition,
children,
...rest
}: ButtonLinkProps) => {
+ const targetRef = useRef(null)
+
+ // Wait until after client-side hydration to show tooltip
+ const [renderTooltip, setRenderTooltip] = useState(false)
+ useEffect(() => {
+ setRenderTooltip(true)
+ }, [])
+
const buttonNode = (
@@ -38,7 +50,17 @@ export const ButtonLink = ({
{buttonNode}
) : (
- buttonNode
+
+ {buttonNode}
+ {renderTooltip && disabledMessage && (
+
+ )}
+
)
}
ButtonLink.displayName = 'ButtonLink'
diff --git a/src/components/common/ButtonLink/types.ts b/src/components/common/ButtonLink/types.ts
index da7f35fdf..1d99afd95 100644
--- a/src/components/common/ButtonLink/types.ts
+++ b/src/components/common/ButtonLink/types.ts
@@ -1,4 +1,4 @@
-import { ButtonProps } from '@aleph-front/core'
+import { ButtonProps, TooltipProps } from '@aleph-front/core'
import { AnchorHTMLAttributes, ReactNode } from 'react'
export type ButtonLinkProps = AnchorHTMLAttributes &
@@ -6,4 +6,7 @@ export type ButtonLinkProps = AnchorHTMLAttributes &
Partial> & {
href: string
children: ReactNode
+ } & {
+ disabledMessage?: ReactNode
+ tooltipPosition?: Pick
}
diff --git a/src/components/common/EntityCard/cmp.tsx b/src/components/common/EntityCard/cmp.tsx
index 58a8ccd6a..b61635244 100644
--- a/src/components/common/EntityCard/cmp.tsx
+++ b/src/components/common/EntityCard/cmp.tsx
@@ -52,6 +52,8 @@ export const EntityCard = ({
dashboardPath = '#',
createPath = '#',
createTarget = '_self',
+ createDisabled = false,
+ createDisabledMessage,
introductionButtonText,
subItems = [],
information,
@@ -159,6 +161,8 @@ export const EntityCard = ({
size="sm"
href={createPath}
target={createTarget}
+ disabled={createDisabled}
+ disabledMessage={createDisabledMessage}
>
{introductionButtonText}
@@ -168,6 +172,8 @@ export const EntityCard = ({
}, [
createPath,
createTarget,
+ createDisabled,
+ createDisabledMessage,
dashboardPath,
information,
introductionButtonText,
diff --git a/src/components/common/EntityCard/types.ts b/src/components/common/EntityCard/types.ts
index e34cceb31..ee61a1462 100644
--- a/src/components/common/EntityCard/types.ts
+++ b/src/components/common/EntityCard/types.ts
@@ -32,6 +32,8 @@ export type EntityCardProps = {
dashboardPath?: string
createPath?: string
createTarget?: HTMLAttributeAnchorTarget
+ createDisabled?: boolean
+ createDisabledMessage?: ReactNode
introductionButtonText?: string
information: InformationProps
storage?: number
diff --git a/src/components/common/Header/cmp.tsx b/src/components/common/Header/cmp.tsx
index d0f48f27b..c1c2a5995 100644
--- a/src/components/common/Header/cmp.tsx
+++ b/src/components/common/Header/cmp.tsx
@@ -4,7 +4,7 @@ import { AccountPicker, RenderLinkProps } from '@aleph-front/core'
import { StyledHeader, StyledNavbarDesktop, StyledNavbarMobile } from './styles'
import { useHeader } from '@/components/common/Header/hook'
import AutoBreadcrumb from '@/components/common/AutoBreadcrumb'
-import { websiteUrl } from '@/helpers/constants'
+import { NAVIGATION_URLS, websiteUrl } from '@/helpers/constants'
import { blockchains } from '@/domain/connect/base'
import { useEnsNameLookup } from '@/hooks/common/useENSLookup'
import LoadingProgress from '../LoadingProgres'
@@ -94,7 +94,7 @@ export const Header = () => {
Link={CustomLinkMemo}
externalUrl={{
text: 'Legacy console',
- url: 'https://app.aleph.cloud/',
+ url: NAVIGATION_URLS.legacyConsole.home,
}}
/>
diff --git a/src/components/common/NewEntityTab/cmp.tsx b/src/components/common/NewEntityTab/cmp.tsx
index 28ced088e..093690c19 100644
--- a/src/components/common/NewEntityTab/cmp.tsx
+++ b/src/components/common/NewEntityTab/cmp.tsx
@@ -14,6 +14,7 @@ export default function NewEntityTab(props: NewEntityTabProps) {
{
id: 'function',
name: 'Function',
+ disabled: true,
},
{
id: 'instance',
diff --git a/src/components/form/AddVolume/cmp.tsx b/src/components/form/AddVolume/cmp.tsx
index 4c99933c4..56163a02c 100644
--- a/src/components/form/AddVolume/cmp.tsx
+++ b/src/components/form/AddVolume/cmp.tsx
@@ -283,6 +283,7 @@ export const AddVolume = memo((props: AddVolumeProps) => {
{
id: VolumeType.New,
name: 'New volume',
+ disabled: true,
},
{
id: VolumeType.Existing,
diff --git a/src/components/pages/console/DashboardPage/cmp.tsx b/src/components/pages/console/DashboardPage/cmp.tsx
index 3549c521d..70a278b2b 100644
--- a/src/components/pages/console/DashboardPage/cmp.tsx
+++ b/src/components/pages/console/DashboardPage/cmp.tsx
@@ -1,6 +1,6 @@
-import React, { useMemo, useState } from 'react'
+import React, { useMemo } from 'react'
import Head from 'next/head'
-import { Button, Icon, NoisyContainer, ObjectImg } from '@aleph-front/core'
+import { Icon } from '@aleph-front/core'
import { useDashboardPage } from '@/components/pages/console/DashboardPage/hook'
import { SectionTitle } from '@/components/common/CompositeTitle'
import { CenteredContainer } from '@/components/common/CenteredContainer'
@@ -17,8 +17,8 @@ import {
EntityTypeObject,
NAVIGATION_URLS,
} from '@/helpers/constants'
-import ToggleDashboard from '@/components/common/ToggleDashboard'
import CreditsDashboard from './CreditsDashboard'
+import { ExternalLink } from '@/components/common/ExternalLink/cmp'
export default function DashboardPage() {
const {
@@ -123,6 +123,20 @@ export default function DashboardPage() {
introductionButtonText="Create your function"
dashboardPath="/console/computing/function"
createPath="/console/computing/function/new"
+ createDisabled
+ createDisabledMessage={
+
+ To create a Function, navigate to the{' '}
+
+
+ }
information={{
type: 'amount',
data: programAggregatedStatus.total,
@@ -225,6 +239,20 @@ export default function DashboardPage() {
introductionButtonText="Deploy your website"
dashboardPath="/console/hosting/website"
createPath={NAVIGATION_URLS.console.web3Hosting.website.new}
+ createDisabled
+ createDisabledMessage={
+
+ To deploy a Website, navigate to the{' '}
+
+
+ }
information={{
type: 'amount',
data: websitesAggregatedStatus.total,
@@ -262,6 +290,20 @@ export default function DashboardPage() {
img={EntityTypeObject[EntityType.Volume]}
dashboardPath="/console/storage"
createPath="/console/storage/volume/new"
+ createDisabled
+ createDisabledMessage={
+
+ To create a new volume, navigate to the{' '}
+
+
+ }
description="Secure and reliable immutable volumes for your data storage needs. Ideal for dependency volumes and critical data, ensuring consistency and integrity."
introductionButtonText="Create your volume"
information={{
diff --git a/src/components/pages/console/function/FunctionDashboardPage/cmp.tsx b/src/components/pages/console/function/FunctionDashboardPage/cmp.tsx
index d852097e7..c354e39ed 100644
--- a/src/components/pages/console/function/FunctionDashboardPage/cmp.tsx
+++ b/src/components/pages/console/function/FunctionDashboardPage/cmp.tsx
@@ -40,10 +40,14 @@ function FunctionDashboardPage() {
info="WHAT ARE..."
title="Functions"
description="Deploy and manage serverless functions effortlessly with our robust computing platform. Run code on-demand or persistently, with seamless integration and scalability."
- withButton={programs?.length === 0}
- buttonUrl="/console/computing/function/new"
- buttonText="Create function"
- externalLinkUrl={NAVIGATION_URLS.docs.functions}
+ // withButton={programs?.length === 0}
+ // buttonUrl="/console/computing/function/new"
+ // buttonText="Create function"
+ // externalLinkUrl={NAVIGATION_URLS.docs.functions}
+ externalLinkText="Create on Legacy console"
+ externalLinkUrl={
+ NAVIGATION_URLS.legacyConsole.computing.functions.home
+ }
/>
>
) : tabId === 'volume' ? (
diff --git a/src/components/pages/console/function/FunctionsTabContent/cmp.tsx b/src/components/pages/console/function/FunctionsTabContent/cmp.tsx
index fd23688ca..958cd2a03 100644
--- a/src/components/pages/console/function/FunctionsTabContent/cmp.tsx
+++ b/src/components/pages/console/function/FunctionsTabContent/cmp.tsx
@@ -88,14 +88,14 @@ export const FunctionsTabContent = React.memo(
]}
/>
-
+ {/*
Create function
-
+
*/}
>
) : (
diff --git a/src/components/pages/console/volume/ManageVolume/cmp.tsx b/src/components/pages/console/volume/ManageVolume/cmp.tsx
index 8eaff0844..a1558650c 100644
--- a/src/components/pages/console/volume/ManageVolume/cmp.tsx
+++ b/src/components/pages/console/volume/ManageVolume/cmp.tsx
@@ -22,11 +22,11 @@ export default function ManageVolume() {
-
+ {/*
Create new volume
-
+
*/}
>
diff --git a/src/components/pages/console/volume/VolumeDashboardPage/cmp.tsx b/src/components/pages/console/volume/VolumeDashboardPage/cmp.tsx
index ebddc98b9..4f4bd5cd3 100644
--- a/src/components/pages/console/volume/VolumeDashboardPage/cmp.tsx
+++ b/src/components/pages/console/volume/VolumeDashboardPage/cmp.tsx
@@ -44,7 +44,7 @@ export default function VolumeDashboardPage() {
{
title: 'Volumes',
img: EntityTypeObject[EntityType.Volume],
- buttonUrl: '/console/storage/volume/new',
+ // buttonUrl: '/console/storage/volume/new',
information: {
type: 'storage',
data: total,
@@ -78,7 +78,7 @@ export default function VolumeDashboardPage() {
<>
{!!volumes.length && (
-
+
)}
>
) : (
diff --git a/src/components/pages/console/website/ManageWebsite/cmp.tsx b/src/components/pages/console/website/ManageWebsite/cmp.tsx
index adb30accd..dd4fe3a13 100644
--- a/src/components/pages/console/website/ManageWebsite/cmp.tsx
+++ b/src/components/pages/console/website/ManageWebsite/cmp.tsx
@@ -372,14 +372,14 @@ export function ManageWebsite() {
-
+ {/*
Create new website
-
+
*/}
>
diff --git a/src/components/pages/console/website/WebsiteDashboardPage/cmp.tsx b/src/components/pages/console/website/WebsiteDashboardPage/cmp.tsx
index 5ebd9ce06..c94980280 100644
--- a/src/components/pages/console/website/WebsiteDashboardPage/cmp.tsx
+++ b/src/components/pages/console/website/WebsiteDashboardPage/cmp.tsx
@@ -44,9 +44,13 @@ export default function WebsiteDashboardPage() {
info="HOW TO..."
title="Host your Website!"
description="Build and deploy your website effortlessly using our web3 hosting solutions. Support for static pages, Next.js, React, and Vue.js ensures you have the flexibility to create the perfect site."
- withButton={websites?.length === 0}
- buttonUrl={NAVIGATION_URLS.console.web3Hosting.website.new}
- buttonText="Deploy your website"
+ // withButton={websites?.length === 0}
+ // buttonUrl={NAVIGATION_URLS.console.web3Hosting.website.new}
+ // buttonText="Deploy your website"
+ externalLinkText="Create on Legacy console"
+ externalLinkUrl={
+ NAVIGATION_URLS.legacyConsole.web3Hosting.website.home
+ }
/>
>
) : tabId === 'domain' ? (
diff --git a/src/components/pages/console/website/WebsitesTabContent/cmp.tsx b/src/components/pages/console/website/WebsitesTabContent/cmp.tsx
index d7a78868c..7b2850763 100644
--- a/src/components/pages/console/website/WebsitesTabContent/cmp.tsx
+++ b/src/components/pages/console/website/WebsitesTabContent/cmp.tsx
@@ -68,14 +68,14 @@ export const WebsitesTabContent = React.memo(
]}
/>
-
*/}
>
) : (
diff --git a/src/helpers/constants.ts b/src/helpers/constants.ts
index c890456b2..1300d9e56 100644
--- a/src/helpers/constants.ts
+++ b/src/helpers/constants.ts
@@ -198,6 +198,53 @@ export enum WebsiteFrameworkId {
export const EXTRA_WEI = 3600 / 10 ** 18
export const NAVIGATION_URLS = {
+ legacyConsole: {
+ home: 'https://app.aleph.cloud/console',
+ settings: {
+ home: 'https://app.aleph.cloud/console/settings',
+ ssh: {
+ home: 'https://app.aleph.cloud/console/settings/ssh',
+ new: 'https://app.aleph.cloud/console/settings/ssh/new',
+ },
+ domain: {
+ home: 'https://app.aleph.cloud/console/settings/domain',
+ new: 'https://app.aleph.cloud/console/settings/domain/new',
+ },
+ },
+ web3Hosting: {
+ home: 'https://app.aleph.cloud/console/hosting/',
+ website: {
+ home: 'https://app.aleph.cloud/console/hosting/website',
+ new: 'https://app.aleph.cloud/console/hosting/website/new',
+ },
+ },
+ computing: {
+ home: 'https://app.aleph.cloud/console/computing',
+ functions: {
+ home: 'https://app.aleph.cloud/console/computing/function',
+ new: 'https://app.aleph.cloud/console/computing/function/new',
+ },
+ instances: {
+ home: 'https://app.aleph.cloud/console/computing/instance',
+ new: 'https://app.aleph.cloud/console/computing/instance/new',
+ },
+ gpus: {
+ home: 'https://app.aleph.cloud/console/computing/gpu-instance',
+ new: 'https://app.aleph.cloud/console/computing/gpu-instance/new',
+ },
+ confidentials: {
+ home: 'https://app.aleph.cloud/console/computing/confidential',
+ new: 'https://app.aleph.cloud/console/computing/confidential/new',
+ },
+ },
+ storage: {
+ home: 'https://app.aleph.cloud/console/storage',
+ volumes: {
+ home: 'https://app.aleph.cloud/console/storage/volume',
+ new: 'https://app.aleph.cloud/console/storage/volume/new',
+ },
+ },
+ },
console: {
home: '/console',
settings: {
diff --git a/src/hooks/form/useAddVolume.ts b/src/hooks/form/useAddVolume.ts
index d2b19068e..bcddbe28c 100644
--- a/src/hooks/form/useAddVolume.ts
+++ b/src/hooks/form/useAddVolume.ts
@@ -4,7 +4,7 @@ import { Volume, VolumeManager, VolumeType } from '@/domain/volume'
import { Control, UseControllerReturn, useController } from 'react-hook-form'
export type NewVolumeStandaloneField = {
- volumeType: VolumeType.New
+ volumeType: VolumeType.Existing
file?: File
}
@@ -32,7 +32,7 @@ export type InstanceSystemVolumeField = {
}
export const defaultVolume: NewVolumeStandaloneField = {
- volumeType: VolumeType.New,
+ volumeType: VolumeType.Existing,
}
export type VolumeField =
@@ -313,7 +313,7 @@ export function useAddVolume({
const volumeTypeCtrl = useController({
control,
name: `${n}.volumeType`,
- defaultValue: VolumeType.New,
+ defaultValue: VolumeType.Existing,
})
const handleRemove = useCallback(() => {
From caf8f54e5b0cb7083ece0f5dbba097d0913ec40e Mon Sep 17 00:00:00 2001
From: gmolki
Date: Tue, 30 Sep 2025 18:34:47 +0200
Subject: [PATCH 15/41] fix credit entities
---
.../DashboardPage/CreditsDashboard/cmp.tsx | 119 ++++++-----
.../DashboardPage/CreditsDashboard/hook.ts | 200 ++++++++++++++++++
src/domain/confidential.ts | 2 +-
src/domain/gpuInstance.ts | 2 +-
4 files changed, 261 insertions(+), 62 deletions(-)
create mode 100644 src/components/pages/console/DashboardPage/CreditsDashboard/hook.ts
diff --git a/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx b/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx
index 84ba79bd1..5dcb9d035 100644
--- a/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx
+++ b/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react'
+import React from 'react'
import {
Button,
Icon,
@@ -9,40 +9,37 @@ import {
import { SectionTitle } from '@/components/common/CompositeTitle'
import ToggleDashboard from '@/components/common/ToggleDashboard'
-import StyledTable from '@/components/common/Table'
-import tw from 'twin.macro'
-import DetailsMenuButton from '@/components/common/DetailsMenuButton'
-import { useConnection } from '@/hooks/common/useConnection'
+import Skeleton from '@/components/common/Skeleton'
+import { useCreditsDashboard } from './hook'
export default function CreditsDashboard() {
- const [creditsDashboardOpen, setCreditsDashboardOpen] = useState(false)
- const { account } = useConnection({ triggerOnMount: false })
- const isConnected = !!account
+ const {
+ runRateDays,
+ creditsDashboardOpen,
+ setCreditsDashboardOpen,
+ isConnected,
+ accountCreditBalance,
+ isCalculatingCosts,
+ } = useCreditsDashboard()
- useEffect(() => {
- if (!isConnected && creditsDashboardOpen) {
- setCreditsDashboardOpen(false)
- }
- }, [isConnected, creditsDashboardOpen])
-
- const data = [
- {
- id: 1,
- status: 'finished',
- date: 1756121519678,
- amount: 100,
- asset: 'USDC',
- credits: 120,
- },
- {
- id: 2,
- status: 'ongoing',
- date: 1756121546481,
- amount: 80,
- asset: 'USDC',
- credits: 95,
- },
- ]
+ // const data = [
+ // {
+ // id: 1,
+ // status: 'finished',
+ // date: 1756121519678,
+ // amount: 100,
+ // asset: 'USDC',
+ // credits: 120,
+ // },
+ // {
+ // id: 2,
+ // status: 'ongoing',
+ // date: 1756121546481,
+ // amount: 80,
+ // asset: 'USDC',
+ // credits: 95,
+ // },
+ // ]
return (
@@ -71,19 +68,27 @@ export default function CreditsDashboard() {
tw="flex flex-col items-start justify-between px-3 py-2 min-w-[6.875rem] min-h-[3.8125rem]"
>
AVAILABLE
- 1080
+ {accountCreditBalance !== undefined ? (
+ {accountCreditBalance}
+ ) : (
+
+ )}
RUN-RATE
-
+ {isCalculatingCosts ? (
+
+ ) : (
+
+
{runRateDays || '∞'}
+
+ {runRateDays === 1 ? 'DAY' : 'DAYS'}
+
+
+ )}
@@ -106,7 +111,7 @@ export default function CreditsDashboard() {
History
-
+ {/*
row.date,
},
- {
- label: 'AMOUNT',
- align: 'left',
- sortable: true,
- render: (row) => row.amount,
- },
- {
- label: 'ASSET',
- align: 'left',
- sortable: true,
- render: (row) => row.asset,
- },
+ // {
+ // label: 'AMOUNT',
+ // align: 'left',
+ // sortable: true,
+ // render: (row) => row.amount,
+ // },
+ // {
+ // label: 'ASSET',
+ // align: 'left',
+ // sortable: true,
+ // render: (row) => row.asset,
+ // },
{
label: 'CREDITS',
align: 'left',
@@ -187,13 +192,7 @@ export default function CreditsDashboard() {
render: (row) => {
return (
)
},
@@ -203,7 +202,7 @@ export default function CreditsDashboard() {
},
]}
/>
-
+
*/}
diff --git a/src/components/pages/console/DashboardPage/CreditsDashboard/hook.ts b/src/components/pages/console/DashboardPage/CreditsDashboard/hook.ts
new file mode 100644
index 000000000..f3f1c3f92
--- /dev/null
+++ b/src/components/pages/console/DashboardPage/CreditsDashboard/hook.ts
@@ -0,0 +1,200 @@
+import {
+ useEffect,
+ useState,
+ useMemo,
+ Dispatch,
+ SetStateAction,
+ useCallback,
+} from 'react'
+import { useAccountEntities } from '@/hooks/common/useAccountEntities'
+import { useInstanceManager } from '@/hooks/common/useManager/useInstanceManager'
+import { useGpuInstanceManager } from '@/hooks/common/useManager/useGpuInstanceManager'
+import { useConfidentialManager } from '@/hooks/common/useManager/useConfidentialManager'
+import { PaymentMethod } from '@/helpers/constants'
+import { useRequestExecutableStatus } from '@/hooks/common/useRequestEntity/useRequestExecutableStatus'
+import { PaymentType } from '@aleph-sdk/message'
+import { useConnection } from '@/hooks/common/useConnection'
+
+export type UseCreditsDashboardReturn = {
+ totalCostPerHour: number
+ runRateDays: number
+ creditsDashboardOpen: boolean
+ setCreditsDashboardOpen: Dispatch>
+ isConnected: boolean
+ accountCreditBalance?: number
+ isCalculatingCosts: boolean
+}
+
+export function useCreditsDashboard(): UseCreditsDashboardReturn {
+ const [totalCostPerHour, setTotalCostPerHour] = useState(0)
+ const [creditsDashboardOpen, setCreditsDashboardOpen] = useState(false)
+ const [isCalculatingCosts, setIsCalculatingCosts] = useState(true)
+
+ const { account, creditBalance: accountCreditBalance } = useConnection({
+ triggerOnMount: false,
+ })
+
+ const isConnected = useMemo(() => !!account, [account])
+
+ // Get all entities
+ const { instances, gpuInstances, confidentials } = useAccountEntities()
+
+ const creditInstances = useMemo(
+ () =>
+ instances.filter(
+ (instance) => instance.payment?.type === PaymentType.credit,
+ ),
+ [instances],
+ )
+ const creditGpuInstances = useMemo(
+ () =>
+ gpuInstances.filter(
+ (gpuInstance) => gpuInstance.payment?.type === PaymentType.credit,
+ ),
+ [gpuInstances],
+ )
+ const creditConfidentials = useMemo(
+ () =>
+ confidentials.filter(
+ (confidential) => confidential.payment?.type === PaymentType.credit,
+ ),
+ [confidentials],
+ )
+
+ // Get managers
+ const instanceManager = useInstanceManager()
+ const gpuInstanceManager = useGpuInstanceManager()
+ const confidentialManager = useConfidentialManager()
+
+ // Get status for running entities
+ const { status: creditInstancesStatus } = useRequestExecutableStatus({
+ entities: creditInstances,
+ })
+ const { status: creditGpuInstancesStatus } = useRequestExecutableStatus({
+ entities: creditGpuInstances,
+ })
+ const { status: creditConfidentialsStatus } = useRequestExecutableStatus({
+ entities: creditConfidentials,
+ managerHook: useConfidentialManager,
+ })
+
+ // Helper function to check if entity is running
+ const isRunning = useCallback((entityId: string, status?: any) => {
+ const statusData = status?.data
+
+ return (
+ statusData &&
+ (statusData.vm_ipv6 || statusData.ipv6Parsed || statusData.ipv6)
+ )
+ }, [])
+
+ // Helper function to calculate cost for a computing entity
+ const calculateComputingEntityCost = useCallback(
+ async (entityId: string, entityStatus: any, manager: any) => {
+ try {
+ if (isRunning(entityId, entityStatus) && manager) {
+ const cost = await manager.getTotalCostByHash(
+ PaymentMethod.Credit,
+ entityId,
+ )
+
+ return cost
+ } else {
+ return 0
+ }
+ } catch (err) {
+ console.error('Error calculating entity cost:', err)
+ return 0
+ }
+ },
+ [isRunning],
+ )
+
+ // Calculate total cost per hour
+ useEffect(() => {
+ async function calculateTotalCost() {
+ setIsCalculatingCosts(true)
+
+ try {
+ let total = 0
+
+ // Calculate costs for regular credit instances
+ for (const instance of creditInstances) {
+ total += await calculateComputingEntityCost(
+ instance.id,
+ creditInstancesStatus[instance.id],
+ instanceManager,
+ )
+ }
+
+ // Calculate costs for credit GPU instances
+ for (const gpuInstance of creditGpuInstances) {
+ total += await calculateComputingEntityCost(
+ gpuInstance.id,
+ creditGpuInstancesStatus[gpuInstance.id],
+ gpuInstanceManager,
+ )
+ }
+
+ // Calculate costs for credit confidential instances
+ for (const confidential of creditConfidentials) {
+ total += await calculateComputingEntityCost(
+ confidential.id,
+ creditConfidentialsStatus[confidential.id],
+ confidentialManager,
+ )
+ }
+
+ setTotalCostPerHour(total)
+ } catch (err) {
+ console.error('Error calculating total cost:', err)
+ } finally {
+ setIsCalculatingCosts(false)
+ }
+ }
+
+ calculateTotalCost()
+ }, [
+ calculateComputingEntityCost,
+ confidentialManager,
+ creditConfidentials,
+ creditConfidentialsStatus,
+ creditGpuInstances,
+ creditGpuInstancesStatus,
+ creditInstances,
+ creditInstancesStatus,
+ gpuInstanceManager,
+ instanceManager,
+ ])
+
+ // Calculate run rate days
+ const runRateDays = useMemo(() => {
+ if (
+ !accountCreditBalance ||
+ accountCreditBalance <= 0 ||
+ totalCostPerHour <= 0
+ ) {
+ return 0
+ }
+
+ const totalCostPerDay = totalCostPerHour * 24
+ return Math.floor(accountCreditBalance / totalCostPerDay)
+ }, [accountCreditBalance, totalCostPerHour])
+
+ // Handle dashboard open/close based on connection
+ useEffect(() => {
+ if (!isConnected && creditsDashboardOpen) {
+ setCreditsDashboardOpen(false)
+ }
+ }, [isConnected, creditsDashboardOpen])
+
+ return {
+ totalCostPerHour,
+ runRateDays,
+ creditsDashboardOpen,
+ setCreditsDashboardOpen,
+ isConnected,
+ accountCreditBalance,
+ isCalculatingCosts,
+ }
+}
diff --git a/src/domain/confidential.ts b/src/domain/confidential.ts
index b27043bee..767d036ba 100644
--- a/src/domain/confidential.ts
+++ b/src/domain/confidential.ts
@@ -23,7 +23,7 @@ import { SuperfluidAccount } from '@aleph-sdk/superfluid'
export type Confidential = Omit & {
type: EntityType.GpuInstance
payment: Payment & {
- type: PaymentType.superfluid
+ type: PaymentType.superfluid | PaymentType.credit
}
}
diff --git a/src/domain/gpuInstance.ts b/src/domain/gpuInstance.ts
index 11bc61ea9..54298adda 100644
--- a/src/domain/gpuInstance.ts
+++ b/src/domain/gpuInstance.ts
@@ -21,7 +21,7 @@ export type GpuInstanceCost = CostSummary
export type GpuInstance = Omit & {
type: EntityType.GpuInstance
payment: Payment & {
- type: PaymentType.superfluid
+ type: PaymentType.superfluid | PaymentType.credit
}
}
From 11f08b81199b9b7ce176bc5cbca80edf8ae2ce73 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Wed, 1 Oct 2025 16:14:09 +0200
Subject: [PATCH 16/41] fix: build
---
src/components/common/ToggleDashboard/cmp.tsx | 3 +-
src/components/form/CheckoutSummary/cmp.tsx | 269 +++++++++---------
.../console/function/NewFunctionPage/cmp.tsx | 4 +-
.../gpuInstance/NewGpuInstancePage/cmp.tsx | 4 +-
.../pages/console/volume/ManageVolume/cmp.tsx | 1 -
.../console/volume/NewVolumePage/cmp.tsx | 1 -
.../console/website/ManageWebsite/cmp.tsx | 3 +-
.../console/website/NewWebsitePage/cmp.tsx | 6 +-
src/hooks/form/useAddVolume.ts | 14 +-
src/hooks/form/useAddVolumes.ts | 4 +-
10 files changed, 147 insertions(+), 162 deletions(-)
diff --git a/src/components/common/ToggleDashboard/cmp.tsx b/src/components/common/ToggleDashboard/cmp.tsx
index 0bb7d582a..7020553b3 100644
--- a/src/components/common/ToggleDashboard/cmp.tsx
+++ b/src/components/common/ToggleDashboard/cmp.tsx
@@ -5,7 +5,6 @@ import {
memo,
useCallback,
useRef,
- useState,
} from 'react'
import tw from 'twin.macro'
import { Button, Icon, useTransition, useBounds } from '@aleph-front/core'
@@ -36,7 +35,7 @@ export const ToggleDashboard = ({
},
...rest
}: ToggleDashboardProps) => {
- const handleToogle = useCallback(() => setOpen((prev) => !prev), [])
+ const handleToogle = useCallback(() => setOpen((prev) => !prev), [setOpen])
const ref = useRef(null)
diff --git a/src/components/form/CheckoutSummary/cmp.tsx b/src/components/form/CheckoutSummary/cmp.tsx
index e0b13942a..c51b5435f 100644
--- a/src/components/form/CheckoutSummary/cmp.tsx
+++ b/src/components/form/CheckoutSummary/cmp.tsx
@@ -1,164 +1,151 @@
-import {
- ellipseAddress,
- convertByteUnits,
- humanReadableSize,
- humanReadableCurrency,
-} from '@/helpers/utils'
+import { ellipseAddress, humanReadableCurrency } from '@/helpers/utils'
import { Label, StyledHoldingSummaryLine } from './styles'
-import {
- CheckoutSummaryProps,
- CheckoutSummaryVolumeLineProps,
- CheckoutSummaryWebsiteLineProps,
-} from './types'
-import { memo, useEffect, useState } from 'react'
+import { CheckoutSummaryProps } from './types'
+import { memo } from 'react'
import React from 'react'
-import { VolumeManager, VolumeType } from '@/domain/volume'
-import InfoTooltipButton from '../../common/InfoTooltipButton'
import { CenteredContainer } from '@/components/common/CenteredContainer'
import { TextGradient } from '@aleph-front/core'
-import Price from '@/components/common/Price'
import CheckoutSummaryFooter from '../CheckoutSummaryFooter'
-import { AddWebsite, WebsiteManager } from '@/domain/website'
import { useConnection } from '@/hooks/common/useConnection'
import { Blockchain } from '@aleph-sdk/core'
import { useNFTVoucherBalance } from '@/hooks/common/useNFTVoucherBalance'
-const CheckoutSummaryVolumeLine = ({
- volume,
- cost,
- specs,
- priceDuration,
-}: CheckoutSummaryVolumeLineProps) => {
- const [size, setSize] = useState(0)
+// const CheckoutSummaryVolumeLine = ({
+// volume,
+// cost,
+// specs,
+// priceDuration,
+// }: CheckoutSummaryVolumeLineProps) => {
+// const [size, setSize] = useState(0)
- useEffect(() => {
- async function load() {
- const size = await VolumeManager.getVolumeSize(volume)
- setSize(size)
- }
+// useEffect(() => {
+// async function load() {
+// const size = await VolumeManager.getVolumeSize(volume)
+// setSize(size)
+// }
- load()
- }, [volume])
+// load()
+// }, [volume])
- if (!cost) return <>>
+// if (!cost) return <>>
- const hasDiscount = !!cost.discount
- const fullDiscount = !cost.cost
+// const hasDiscount = !!cost.discount
+// const fullDiscount = !cost.cost
- return (
-
-
-
- STORAGE
-
- {volume.volumeType === VolumeType.Persistent
- ? 'PERSISTENT'
- : 'VOLUME'}
-
-
-
-
-
{humanReadableSize(size, 'MiB')}
-
-
-
- {hasDiscount ? (
-
-
- {fullDiscount ? (
- <>
- The cost displayed for the added storage is{' '}
-
-
- {' '}
- as this resource is already included in your selected
- package at no additional charge.
- >
- ) : (
- <>
- Good news! The displayed price is lower than usual due
- to a discount of{' '}
-
-
-
- {specs && (
- <>
- {` for `}
-
- {convertByteUnits(specs.storage, {
- from: 'MiB',
- to: 'GiB',
- displayUnit: true,
- })}
- {' '}
- included in your package.
- >
- )}
- >
- )}
-
-
- }
- >
-
-
- ) : (
- <>
-
- >
- )}
-
-
-
- )
-}
-CheckoutSummaryVolumeLine.displayName = 'CheckoutSummaryVolumeLine'
+// return (
+//
+//
+//
+// STORAGE
+//
+// {volume.volumeType === VolumeType.Persistent
+// ? 'PERSISTENT'
+// : 'VOLUME'}
+//
+//
+//
+//
+//
{humanReadableSize(size, 'MiB')}
+//
+//
+//
+// {hasDiscount ? (
+//
+//
+// {fullDiscount ? (
+// <>
+// The cost displayed for the added storage is{' '}
+//
+//
+// {' '}
+// as this resource is already included in your selected
+// package at no additional charge.
+// >
+// ) : (
+// <>
+// Good news! The displayed price is lower than usual due
+// to a discount of{' '}
+//
+//
+//
+// {specs && (
+// <>
+// {` for `}
+//
+// {convertByteUnits(specs.storage, {
+// from: 'MiB',
+// to: 'GiB',
+// displayUnit: true,
+// })}
+// {' '}
+// included in your package.
+// >
+// )}
+// >
+// )}
+//
+//
+// }
+// >
+//
+//
+// ) : (
+// <>
+//
+// >
+// )}
+//
+//
+//
+// )
+// }
+// CheckoutSummaryVolumeLine.displayName = 'CheckoutSummaryVolumeLine'
-// ------------------------------------------
+// // ------------------------------------------
-const CheckoutSummaryWebsiteLine = ({
- website,
- cost,
-}: CheckoutSummaryWebsiteLineProps) => {
- const [size, setSize] = useState
(0)
+// const CheckoutSummaryWebsiteLine = ({
+// website,
+// cost,
+// }: CheckoutSummaryWebsiteLineProps) => {
+// const [size, setSize] = useState(0)
- useEffect(() => {
- async function load() {
- const size = await WebsiteManager.getWebsiteSize({
- website,
- } as AddWebsite)
- setSize(size)
- }
+// useEffect(() => {
+// async function load() {
+// const size = await WebsiteManager.getWebsiteSize({
+// website,
+// } as AddWebsite)
+// setSize(size)
+// }
- load()
- }, [website])
+// load()
+// }, [website])
- if (!cost) return <>>
+// if (!cost) return <>>
- return (
-
-
-
-
{humanReadableSize(size, 'MiB')}
-
-
-
- )
-}
-CheckoutSummaryWebsiteLine.displayName = 'CheckoutSummaryWebsiteLine'
+// return (
+//
+//
+//
+//
{humanReadableSize(size, 'MiB')}
+//
+//
+//
+// )
+// }
+// CheckoutSummaryWebsiteLine.displayName = 'CheckoutSummaryWebsiteLine'
// ------------------------------------------
diff --git a/src/components/pages/console/function/NewFunctionPage/cmp.tsx b/src/components/pages/console/function/NewFunctionPage/cmp.tsx
index d8ced9a35..946befb36 100644
--- a/src/components/pages/console/function/NewFunctionPage/cmp.tsx
+++ b/src/components/pages/console/function/NewFunctionPage/cmp.tsx
@@ -24,7 +24,7 @@ import CheckoutButton from '@/components/form/CheckoutButton'
export default function NewFunctionPage({ mainRef }: PageProps) {
const {
address,
- accountBalance,
+ accountCreditBalance,
createFunctionDisabled,
createFunctionButtonTitle,
values,
@@ -174,7 +174,7 @@ export default function NewFunctionPage({ mainRef }: PageProps) {
control={control}
address={address}
cost={cost}
- unlockedAmount={accountBalance}
+ unlockedAmount={accountCreditBalance}
mainRef={mainRef}
description={
<>
diff --git a/src/components/pages/console/gpuInstance/NewGpuInstancePage/cmp.tsx b/src/components/pages/console/gpuInstance/NewGpuInstancePage/cmp.tsx
index 7c542735b..8ecc14a41 100644
--- a/src/components/pages/console/gpuInstance/NewGpuInstancePage/cmp.tsx
+++ b/src/components/pages/console/gpuInstance/NewGpuInstancePage/cmp.tsx
@@ -39,7 +39,7 @@ import CheckoutButton from '@/components/form/CheckoutButton'
export default function NewGpuInstancePage({ mainRef }: PageProps) {
const {
address,
- accountBalance,
+ accountCreditBalance,
blockchainName,
manuallySelectCRNDisabled,
manuallySelectCRNDisabledMessage,
@@ -416,7 +416,7 @@ export default function NewGpuInstancePage({ mainRef }: PageProps) {
control={control}
address={address}
cost={cost}
- unlockedAmount={accountBalance}
+ unlockedAmount={accountCreditBalance}
mainRef={mainRef}
description={
<>
diff --git a/src/components/pages/console/volume/ManageVolume/cmp.tsx b/src/components/pages/console/volume/ManageVolume/cmp.tsx
index a1558650c..99267edd3 100644
--- a/src/components/pages/console/volume/ManageVolume/cmp.tsx
+++ b/src/components/pages/console/volume/ManageVolume/cmp.tsx
@@ -1,4 +1,3 @@
-import ButtonLink from '@/components/common/ButtonLink'
import Head from 'next/head'
import { useManageVolume } from './hook'
import { CenteredContainer } from '@/components/common/CenteredContainer'
diff --git a/src/components/pages/console/volume/NewVolumePage/cmp.tsx b/src/components/pages/console/volume/NewVolumePage/cmp.tsx
index 1633bf45c..a7ec627cc 100644
--- a/src/components/pages/console/volume/NewVolumePage/cmp.tsx
+++ b/src/components/pages/console/volume/NewVolumePage/cmp.tsx
@@ -1,6 +1,5 @@
import React from 'react'
import Head from 'next/head'
-import { PaymentMethod } from '@/helpers/constants'
import { useNewVolumePage } from './hook'
import CheckoutSummary from '@/components/form/CheckoutSummary'
import { CenteredContainer } from '@/components/common/CenteredContainer'
diff --git a/src/components/pages/console/website/ManageWebsite/cmp.tsx b/src/components/pages/console/website/ManageWebsite/cmp.tsx
index dd4fe3a13..04ceeb7eb 100644
--- a/src/components/pages/console/website/ManageWebsite/cmp.tsx
+++ b/src/components/pages/console/website/ManageWebsite/cmp.tsx
@@ -10,7 +10,7 @@ import {
TextGradient,
useCopyToClipboardAndNotify,
} from '@aleph-front/core'
-import { EntityTypeName, NAVIGATION_URLS } from '@/helpers/constants'
+import { EntityTypeName } from '@/helpers/constants'
import { useManageWebsite } from './hook'
import { humanReadableSize } from '@/helpers/utils'
import { Text, Separator } from '../../common'
@@ -20,7 +20,6 @@ import { WebsiteFrameworks } from '@/domain/website'
import { getDate, cidV0Tov1 } from '@/helpers/utils'
import UpdateWebsiteFolder from '@/components/form/UpdateWebsiteFolder'
import { Volume } from '@/domain/volume'
-import ButtonLink from '@/components/common/ButtonLink'
import IconText from '@/components/common/IconText'
import BackButtonSection from '@/components/common/BackButtonSection'
diff --git a/src/components/pages/console/website/NewWebsitePage/cmp.tsx b/src/components/pages/console/website/NewWebsitePage/cmp.tsx
index ae4e90166..3273f89dd 100644
--- a/src/components/pages/console/website/NewWebsitePage/cmp.tsx
+++ b/src/components/pages/console/website/NewWebsitePage/cmp.tsx
@@ -1,9 +1,5 @@
import Head from 'next/head'
-import {
- EntityType,
- EntityDomainType,
- PaymentMethod,
-} from '@/helpers/constants'
+import { EntityType, EntityDomainType } from '@/helpers/constants'
import { useNewWebsitePage } from '@/components/pages/console/website/NewWebsitePage/hook'
import { Button, TextGradient } from '@aleph-front/core'
import CheckoutSummary from '@/components/form/CheckoutSummary'
diff --git a/src/hooks/form/useAddVolume.ts b/src/hooks/form/useAddVolume.ts
index bcddbe28c..3387df8e7 100644
--- a/src/hooks/form/useAddVolume.ts
+++ b/src/hooks/form/useAddVolume.ts
@@ -4,17 +4,23 @@ import { Volume, VolumeManager, VolumeType } from '@/domain/volume'
import { Control, UseControllerReturn, useController } from 'react-hook-form'
export type NewVolumeStandaloneField = {
- volumeType: VolumeType.Existing
+ volumeType: VolumeType.New
file?: File
}
+export type ExistingVolumeStandaloneField = {
+ volumeType: VolumeType.Existing
+ mountPath?: string
+ refHash?: string
+ useLatest?: boolean
+}
+
export type NewVolumeField = NewVolumeStandaloneField & {
mountPath: string
useLatest: boolean
}
-export type ExistingVolumeField = {
- volumeType: VolumeType.Existing
+export type ExistingVolumeField = ExistingVolumeStandaloneField & {
mountPath: string
refHash: string
useLatest: boolean
@@ -31,7 +37,7 @@ export type InstanceSystemVolumeField = {
size: number
}
-export const defaultVolume: NewVolumeStandaloneField = {
+export const defaultVolume: ExistingVolumeStandaloneField = {
volumeType: VolumeType.Existing,
}
diff --git a/src/hooks/form/useAddVolumes.ts b/src/hooks/form/useAddVolumes.ts
index b3f0a0290..3fd29a49b 100644
--- a/src/hooks/form/useAddVolumes.ts
+++ b/src/hooks/form/useAddVolumes.ts
@@ -1,7 +1,7 @@
import { useCallback } from 'react'
import {
+ ExistingVolumeStandaloneField,
InstanceSystemVolumeField,
- NewVolumeField,
VolumeField,
defaultVolume as defaultStandaloneVolume,
} from './useAddVolume'
@@ -22,7 +22,7 @@ export type UseAddVolumesReturn = {
handleRemove: (index?: number) => void
}
-export const defaultVolume: NewVolumeField = {
+export const defaultVolume: ExistingVolumeStandaloneField = {
...defaultStandaloneVolume,
mountPath: '',
useLatest: false,
From 8701ea37b73268fa4c171785aa83fb173bd05307 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Wed, 1 Oct 2025 18:38:24 +0200
Subject: [PATCH 17/41] deps: update core & message
---
package-lock.json | 28 ++++++++++++++--------------
package.json | 4 ++--
2 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 493220957..31e700c9b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,14 +8,14 @@
"name": "front-aleph-cloud",
"version": "0.34.3",
"dependencies": {
- "@aleph-front/core": "file:/../front-core/aleph-front-core-1.29.1-beta-0.tgz",
+ "@aleph-front/core": "^1.30.1",
"@aleph-sdk/account": "^1.2.0",
"@aleph-sdk/avalanche": "^1.5.0",
"@aleph-sdk/client": "^1.4.5",
"@aleph-sdk/core": "^1.6.2",
"@aleph-sdk/ethereum": "^1.5.0",
"@aleph-sdk/evm": "^1.6.2",
- "@aleph-sdk/message": "file:/../aleph-sdk-ts/packages/message/aleph-sdk-message-1.6.2-beta-2.tgz",
+ "@aleph-sdk/message": "^1.6.3",
"@aleph-sdk/solana": "^1.6.2",
"@aleph-sdk/superfluid": "^1.4.5",
"@fortawesome/fontawesome-svg-core": "^6.3.0",
@@ -61,10 +61,9 @@
}
},
"node_modules/@aleph-front/core": {
- "version": "1.29.1-beta-0",
- "resolved": "file:../front-core/aleph-front-core-1.29.1-beta-0.tgz",
- "integrity": "sha512-ZNhvd8r7lHwEPpEmtmkhS2Jc9VMwYN412kwoVb3SE1YD/2xQ9NQX3hXUdG80EjH6P2KtxyLypKsIS/dd2tzmfg==",
- "license": "ISC",
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/@aleph-front/core/-/core-1.30.1.tgz",
+ "integrity": "sha512-si2EjrUG2TwPp1MsD+sq1FA14f/+bz5UrMbAGNhkSKZlFpCVakbp9PdnbHXekgtBktwNJsMgLra2RjV1W74ljg==",
"dependencies": {
"@monaco-editor/react": "^4.4.6",
"react-infinite-scroll-hook": "^4.1.1",
@@ -242,10 +241,9 @@
}
},
"node_modules/@aleph-sdk/message": {
- "version": "1.6.2-beta-2",
- "resolved": "file:../aleph-sdk-ts/packages/message/aleph-sdk-message-1.6.2-beta-2.tgz",
- "integrity": "sha512-SXoYWMQxbkUg09eQ4RbSz29XDQuS8Ea1EuwSk5XBp/YTQZuTvteHAm6lEyDQas8Ew8ZDr3l3FwlIChLXMf2s2A==",
- "license": "MIT",
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@aleph-sdk/message/-/message-1.6.3.tgz",
+ "integrity": "sha512-UMs1TVf28Fj24jwdgtH8rDLdbVGRJyo/pAZmEfJl0dWAyRKzaJWB0EsLJVF07I3Ls4YOhACy9AHz7RVCszNtyA==",
"dependencies": {
"axios": "^1.5.1",
"form-data": "^4.0.0",
@@ -19910,8 +19908,9 @@
},
"dependencies": {
"@aleph-front/core": {
- "version": "file:../front-core/aleph-front-core-1.29.1-beta-0.tgz",
- "integrity": "sha512-ZNhvd8r7lHwEPpEmtmkhS2Jc9VMwYN412kwoVb3SE1YD/2xQ9NQX3hXUdG80EjH6P2KtxyLypKsIS/dd2tzmfg==",
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/@aleph-front/core/-/core-1.30.1.tgz",
+ "integrity": "sha512-si2EjrUG2TwPp1MsD+sq1FA14f/+bz5UrMbAGNhkSKZlFpCVakbp9PdnbHXekgtBktwNJsMgLra2RjV1W74ljg==",
"requires": {
"@monaco-editor/react": "^4.4.6",
"react-infinite-scroll-hook": "^4.1.1",
@@ -20029,8 +20028,9 @@
}
},
"@aleph-sdk/message": {
- "version": "file:../aleph-sdk-ts/packages/message/aleph-sdk-message-1.6.2-beta-2.tgz",
- "integrity": "sha512-SXoYWMQxbkUg09eQ4RbSz29XDQuS8Ea1EuwSk5XBp/YTQZuTvteHAm6lEyDQas8Ew8ZDr3l3FwlIChLXMf2s2A==",
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@aleph-sdk/message/-/message-1.6.3.tgz",
+ "integrity": "sha512-UMs1TVf28Fj24jwdgtH8rDLdbVGRJyo/pAZmEfJl0dWAyRKzaJWB0EsLJVF07I3Ls4YOhACy9AHz7RVCszNtyA==",
"requires": {
"axios": "^1.5.1",
"form-data": "^4.0.0",
diff --git a/package.json b/package.json
index b7aed5070..faba57330 100644
--- a/package.json
+++ b/package.json
@@ -11,14 +11,14 @@
"lint:fix": "next lint --fix"
},
"dependencies": {
- "@aleph-front/core": "file:/../front-core/aleph-front-core-1.29.1-beta-0.tgz",
+ "@aleph-front/core": "^1.30.1",
"@aleph-sdk/account": "^1.2.0",
"@aleph-sdk/avalanche": "^1.5.0",
"@aleph-sdk/client": "^1.4.5",
"@aleph-sdk/core": "^1.6.2",
"@aleph-sdk/ethereum": "^1.5.0",
"@aleph-sdk/evm": "^1.6.2",
- "@aleph-sdk/message": "file:/../aleph-sdk-ts/packages/message/aleph-sdk-message-1.6.2-beta-2.tgz",
+ "@aleph-sdk/message": "^1.6.3",
"@aleph-sdk/solana": "^1.6.2",
"@aleph-sdk/superfluid": "^1.4.5",
"@fortawesome/fontawesome-svg-core": "^6.3.0",
From c3befa4e75ee298084e105baa0e83771cc242728 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Wed, 1 Oct 2025 19:05:40 +0200
Subject: [PATCH 18/41] refactor: constants urls
---
src/components/common/Header/cmp.tsx | 2 +-
src/helpers/constants.ts | 43 ++++++++++++++--------------
2 files changed, 23 insertions(+), 22 deletions(-)
diff --git a/src/components/common/Header/cmp.tsx b/src/components/common/Header/cmp.tsx
index c1c2a5995..967650a4c 100644
--- a/src/components/common/Header/cmp.tsx
+++ b/src/components/common/Header/cmp.tsx
@@ -68,7 +68,7 @@ export const Header = () => {
Link={CustomLinkMemo}
externalUrl={{
text: 'Legacy console',
- url: 'https://app.aleph.cloud/',
+ url: NAVIGATION_URLS.legacyConsole.home,
}}
/>
),
diff --git a/src/helpers/constants.ts b/src/helpers/constants.ts
index 1300d9e56..19d50eee5 100644
--- a/src/helpers/constants.ts
+++ b/src/helpers/constants.ts
@@ -197,51 +197,52 @@ export enum WebsiteFrameworkId {
export const EXTRA_WEI = 3600 / 10 ** 18
+const LEGACY_CONSOLE_DOMAIN = 'https://app.aleph.cloud/console'
export const NAVIGATION_URLS = {
legacyConsole: {
- home: 'https://app.aleph.cloud/console',
+ home: `${LEGACY_CONSOLE_DOMAIN}`,
settings: {
- home: 'https://app.aleph.cloud/console/settings',
+ home: `${LEGACY_CONSOLE_DOMAIN}/settings`,
ssh: {
- home: 'https://app.aleph.cloud/console/settings/ssh',
- new: 'https://app.aleph.cloud/console/settings/ssh/new',
+ home: `${LEGACY_CONSOLE_DOMAIN}/settings/ssh`,
+ new: `${LEGACY_CONSOLE_DOMAIN}/settings/ssh/new`,
},
domain: {
- home: 'https://app.aleph.cloud/console/settings/domain',
- new: 'https://app.aleph.cloud/console/settings/domain/new',
+ home: `${LEGACY_CONSOLE_DOMAIN}/settings/domain`,
+ new: `${LEGACY_CONSOLE_DOMAIN}/settings/domain/new`,
},
},
web3Hosting: {
- home: 'https://app.aleph.cloud/console/hosting/',
+ home: `${LEGACY_CONSOLE_DOMAIN}/hosting/`,
website: {
- home: 'https://app.aleph.cloud/console/hosting/website',
- new: 'https://app.aleph.cloud/console/hosting/website/new',
+ home: `${LEGACY_CONSOLE_DOMAIN}/hosting/website`,
+ new: `${LEGACY_CONSOLE_DOMAIN}/hosting/website/new`,
},
},
computing: {
- home: 'https://app.aleph.cloud/console/computing',
+ home: `${LEGACY_CONSOLE_DOMAIN}/computing`,
functions: {
- home: 'https://app.aleph.cloud/console/computing/function',
- new: 'https://app.aleph.cloud/console/computing/function/new',
+ home: `${LEGACY_CONSOLE_DOMAIN}/computing/function`,
+ new: `${LEGACY_CONSOLE_DOMAIN}/computing/function/new`,
},
instances: {
- home: 'https://app.aleph.cloud/console/computing/instance',
- new: 'https://app.aleph.cloud/console/computing/instance/new',
+ home: `${LEGACY_CONSOLE_DOMAIN}/computing/instance`,
+ new: `${LEGACY_CONSOLE_DOMAIN}/computing/instance/new`,
},
gpus: {
- home: 'https://app.aleph.cloud/console/computing/gpu-instance',
- new: 'https://app.aleph.cloud/console/computing/gpu-instance/new',
+ home: `${LEGACY_CONSOLE_DOMAIN}/computing/gpu-instance`,
+ new: `${LEGACY_CONSOLE_DOMAIN}/computing/gpu-instance/new`,
},
confidentials: {
- home: 'https://app.aleph.cloud/console/computing/confidential',
- new: 'https://app.aleph.cloud/console/computing/confidential/new',
+ home: `${LEGACY_CONSOLE_DOMAIN}/computing/confidential`,
+ new: `${LEGACY_CONSOLE_DOMAIN}/computing/confidential/new`,
},
},
storage: {
- home: 'https://app.aleph.cloud/console/storage',
+ home: `${LEGACY_CONSOLE_DOMAIN}/storage`,
volumes: {
- home: 'https://app.aleph.cloud/console/storage/volume',
- new: 'https://app.aleph.cloud/console/storage/volume/new',
+ home: `${LEGACY_CONSOLE_DOMAIN}/storage/volume`,
+ new: `${LEGACY_CONSOLE_DOMAIN}/storage/volume/new`,
},
},
},
From fcd8ad57c6d06404f1eaa3474a1d8b14776009df Mon Sep 17 00:00:00 2001
From: gmolki
Date: Wed, 1 Oct 2025 20:05:42 +0200
Subject: [PATCH 19/41] fix: show credits in account picker
---
src/components/common/Header/cmp.tsx | 2 ++
src/helpers/constants.ts | 3 ++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/components/common/Header/cmp.tsx b/src/components/common/Header/cmp.tsx
index 967650a4c..c15892640 100644
--- a/src/components/common/Header/cmp.tsx
+++ b/src/components/common/Header/cmp.tsx
@@ -55,6 +55,7 @@ export const Header = () => {
isMobile
accountAddress={accountAddress}
accountBalance={accountBalance}
+ showCredits
accountCredits={accountCreditBalance}
accountVouchers={accountVouchers}
blockchains={blockchains}
@@ -81,6 +82,7 @@ export const Header = () => {
Date: Wed, 1 Oct 2025 20:30:23 +0200
Subject: [PATCH 20/41] fix: comment purchases table
---
.../DashboardPage/CreditsDashboard/cmp.tsx | 16 +++++-----------
1 file changed, 5 insertions(+), 11 deletions(-)
diff --git a/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx b/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx
index 5dcb9d035..665cb4c57 100644
--- a/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx
+++ b/src/components/pages/console/DashboardPage/CreditsDashboard/cmp.tsx
@@ -1,11 +1,5 @@
import React from 'react'
-import {
- Button,
- Icon,
- NoisyContainer,
- ObjectImg,
- TextGradient,
-} from '@aleph-front/core'
+import { Button, Icon, NoisyContainer, ObjectImg } from '@aleph-front/core'
import { SectionTitle } from '@/components/common/CompositeTitle'
import ToggleDashboard from '@/components/common/ToggleDashboard'
@@ -101,7 +95,7 @@ export default function CreditsDashboard() {
-
+ {/*
Purchases
@@ -111,7 +105,7 @@ export default function CreditsDashboard() {
History
- {/*
+
+
*/}
From 32985901ec53c109d985febfe5b9e4cc1e557f23 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Thu, 2 Oct 2025 10:50:43 +0200
Subject: [PATCH 21/41] feat: remove vouchers
---
src/components/common/Header/cmp.tsx | 3 --
src/components/common/Header/hook.ts | 45 +---------------------------
2 files changed, 1 insertion(+), 47 deletions(-)
diff --git a/src/components/common/Header/cmp.tsx b/src/components/common/Header/cmp.tsx
index c15892640..a62555a09 100644
--- a/src/components/common/Header/cmp.tsx
+++ b/src/components/common/Header/cmp.tsx
@@ -26,7 +26,6 @@ export const Header = () => {
accountAddress,
accountBalance,
accountCreditBalance,
- accountVouchers,
rewards,
selectedNetwork,
handleToggle,
@@ -57,7 +56,6 @@ export const Header = () => {
accountBalance={accountBalance}
showCredits
accountCredits={accountCreditBalance}
- accountVouchers={accountVouchers}
blockchains={blockchains}
networks={networks}
selectedNetwork={selectedNetwork}
@@ -84,7 +82,6 @@ export const Header = () => {
accountBalance={accountBalance}
showCredits
accountCredits={accountCreditBalance}
- accountVouchers={accountVouchers}
blockchains={blockchains}
networks={networks}
selectedNetwork={selectedNetwork}
diff --git a/src/components/common/Header/hook.ts b/src/components/common/Header/hook.ts
index fe50be7ab..32898c364 100644
--- a/src/components/common/Header/hook.ts
+++ b/src/components/common/Header/hook.ts
@@ -1,5 +1,5 @@
import { useRouter } from 'next/router'
-import { useCallback, useState, useMemo, useEffect } from 'react'
+import { useCallback, useState, useMemo } from 'react'
import { useAppState } from '@/contexts/appState'
import {
AccountPickerProps,
@@ -21,7 +21,6 @@ export type UseHeaderReturn = UseRoutesReturn & {
accountAddress?: string
accountBalance?: number
accountCreditBalance?: number
- accountVouchers?: AccountPickerProps['accountVouchers'] | undefined
networks: Network[]
pathname: string
breadcrumbNames: UseBreadcrumbNamesReturn['names']
@@ -44,7 +43,6 @@ export function useHeader(): UseHeaderReturn {
balance: accountBalance,
creditBalance: accountCreditBalance,
} = state.connection
- const { voucherManager } = state.manager
const { handleConnect: connect, handleDisconnect: disconnect } =
useConnection({ triggerOnMount: true })
@@ -119,46 +117,6 @@ export function useHeader(): UseHeaderReturn {
[wallets],
)
- // --------------------
- const [accountVouchers, setAccountVouchers] = useState<
- AccountPickerProps['accountVouchers']
- >([])
-
- useEffect(() => {
- const fetchAndFormatVouchers = async () => {
- if (!account || !voucherManager) return
-
- const vouchers = await voucherManager.getAll()
- const groupedVouchers = vouchers.reduce(
- (grouped, voucher) => {
- const { metadataId } = voucher
- if (!grouped[metadataId]) grouped[metadataId] = []
- grouped[metadataId].push(voucher)
- return grouped
- },
- {} as Record,
- )
-
- const formattedVouchers = Object.values(groupedVouchers).flatMap(
- (vouchers) => {
- if (!vouchers.length) return []
-
- const { name, icon } = vouchers[0]
- return {
- name: name,
- image: icon,
- imageAlt: name,
- amount: vouchers.length,
- }
- },
- )
-
- setAccountVouchers(formattedVouchers)
- }
-
- fetchAndFormatVouchers()
- }, [account, voucherManager])
-
// --------------------
const handleConnect = useCallback(
@@ -230,7 +188,6 @@ export function useHeader(): UseHeaderReturn {
accountAddress: account?.address,
accountBalance,
accountCreditBalance,
- accountVouchers,
networks,
pathname,
routes,
From 5e54b6e41a5a1594c4b2f2a32801c44b5ae3441f Mon Sep 17 00:00:00 2001
From: gmolki
Date: Thu, 2 Oct 2025 12:15:39 +0200
Subject: [PATCH 22/41] disabled non-credit instances list
---
.../instance/InstancesTabContent/cmp.tsx | 73 ++++++++++++++++---
1 file changed, 62 insertions(+), 11 deletions(-)
diff --git a/src/components/pages/console/instance/InstancesTabContent/cmp.tsx b/src/components/pages/console/instance/InstancesTabContent/cmp.tsx
index bedbc9b32..415de3d0c 100644
--- a/src/components/pages/console/instance/InstancesTabContent/cmp.tsx
+++ b/src/components/pages/console/instance/InstancesTabContent/cmp.tsx
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, { useCallback } from 'react'
import tw from 'twin.macro'
import { InstancesTabContentProps } from './types'
import ButtonLink from '@/components/common/ButtonLink'
@@ -9,9 +9,31 @@ import {
} from '@/helpers/utils'
import EntityTable from '@/components/common/EntityTable'
import { Icon } from '@aleph-front/core'
+import { PaymentType } from '@aleph-sdk/message'
+import ExternalLink from '@/components/common/ExternalLink'
+import { NAVIGATION_URLS } from '@/helpers/constants'
+import { Instance } from '@/domain/instance'
export const InstancesTabContent = React.memo(
({ data }: InstancesTabContentProps) => {
+ const isCredit = useCallback((row: Instance) => {
+ return row.payment?.type === PaymentType.credit
+ }, [])
+
+ const isDisabled = useCallback(
+ (row: Instance) => {
+ return !isCredit(row)
+ },
+ [isCredit],
+ )
+
+ const defaultCellProps = useCallback(
+ (row: Instance) => {
+ return isDisabled(row) ? { css: tw`opacity-40!` } : {}
+ },
+ [isDisabled],
+ )
+
return (
<>
{data.length > 0 ? (
@@ -29,12 +51,14 @@ export const InstancesTabContent = React.memo(
sortable: true,
render: (row) =>
(row?.metadata?.name as string) || ellipseAddress(row.id),
+ cellProps: (row) => defaultCellProps(row),
},
{
label: 'Cores',
align: 'right',
sortable: true,
render: (row) => row?.resources?.vcpus || 0,
+ cellProps: (row) => defaultCellProps(row),
},
{
label: 'RAM',
@@ -46,31 +70,58 @@ export const InstancesTabContent = React.memo(
to: 'GiB',
displayUnit: true,
}),
+ cellProps: (row) => defaultCellProps(row),
},
{
- label: 'HDD',
+ label: 'date',
align: 'right',
sortable: true,
render: (row) => humanReadableSize(row.size, 'MiB'),
+ cellProps: (row) => defaultCellProps(row),
},
{
label: 'Date',
align: 'right',
sortable: true,
render: (row) => row.date,
+ cellProps: (row) => defaultCellProps(row),
},
{
label: '',
align: 'right',
- render: (row) => (
-
-
-
- ),
+ render: (row) => {
+ const disabled = isDisabled(row)
+
+ return (
+
+ To manage this instance, go to the{' '}
+
+
+ )
+ }
+ tooltipPosition={{
+ my: 'bottom-right',
+ at: 'bottom-center',
+ }}
+ >
+
+
+ )
+ },
cellProps: () => ({
css: tw`pl-3!`,
}),
From 60225ca0fb411ed4cabc8bb5cbdda1b7c311d9ca Mon Sep 17 00:00:00 2001
From: gmolki
Date: Thu, 2 Oct 2025 12:39:56 +0200
Subject: [PATCH 23/41] disable non-credit enities list
---
.../pages/console/DashboardPage/cmp.tsx | 6 +-
.../ConfidentialsTabContent/cmp.tsx | 55 ++++++++++++++----
.../GpuInstancesTabContent/cmp.tsx | 55 ++++++++++++++----
.../instance/InstancesTabContent/cmp.tsx | 26 ++-------
.../console/volume/VolumesTabContent/cmp.tsx | 43 ++++++++++----
.../website/WebsitesTabContent/cmp.tsx | 56 +++++++++++++++----
6 files changed, 175 insertions(+), 66 deletions(-)
diff --git a/src/components/pages/console/DashboardPage/cmp.tsx b/src/components/pages/console/DashboardPage/cmp.tsx
index 70a278b2b..7a9682833 100644
--- a/src/components/pages/console/DashboardPage/cmp.tsx
+++ b/src/components/pages/console/DashboardPage/cmp.tsx
@@ -128,7 +128,7 @@ export default function DashboardPage() {
To create a Function, navigate to the{' '}
To deploy a Website, navigate to the{' '}
To create a new volume, navigate to the{' '}
{
+ const isCredit = useCallback((row: Confidential) => {
+ return row.payment?.type === PaymentType.credit
+ }, [])
+
return (
<>
{data.length > 0 ? (
@@ -36,6 +44,9 @@ export const ConfidentialsTabContent = memo(
borderType="none"
rowNoise
rowKey={(row) => row.id}
+ rowProps={(row) => ({
+ css: isCredit(row) ? '' : tw`opacity-40`,
+ })}
data={data}
columns={[
{
@@ -77,15 +88,39 @@ export const ConfidentialsTabContent = memo(
{
label: '',
align: 'right',
- render: (row) => (
-
-
-
- ),
+ render: (row) => {
+ const disabled = !isCredit(row)
+
+ return (
+
+ To manage this confidential instance, go to the{' '}
+
+
+ )
+ }
+ tooltipPosition={{
+ my: 'bottom-right',
+ at: 'bottom-center',
+ }}
+ >
+
+
+ )
+ },
cellProps: () => ({
css: tw`pl-3!`,
}),
diff --git a/src/components/pages/console/gpuInstance/GpuInstancesTabContent/cmp.tsx b/src/components/pages/console/gpuInstance/GpuInstancesTabContent/cmp.tsx
index 1acbe1b74..3934347d6 100644
--- a/src/components/pages/console/gpuInstance/GpuInstancesTabContent/cmp.tsx
+++ b/src/components/pages/console/gpuInstance/GpuInstancesTabContent/cmp.tsx
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, { useCallback } from 'react'
import tw from 'twin.macro'
import { GpuInstancesTabContentProps } from './types'
import ButtonLink from '@/components/common/ButtonLink'
@@ -9,9 +9,17 @@ import {
} from '@/helpers/utils'
import EntityTable from '@/components/common/EntityTable'
import { Icon } from '@aleph-front/core'
+import { GpuInstance } from '@/domain/gpuInstance'
+import { PaymentType } from '@aleph-sdk/message'
+import ExternalLink from '@/components/common/ExternalLink'
+import { NAVIGATION_URLS } from '@/helpers/constants'
export const GpuInstancesTabContent = React.memo(
({ data }: GpuInstancesTabContentProps) => {
+ const isCredit = useCallback((row: GpuInstance) => {
+ return row.payment?.type === PaymentType.credit
+ }, [])
+
return (
<>
{data.length > 0 ? (
@@ -21,6 +29,9 @@ export const GpuInstancesTabContent = React.memo(
borderType="none"
rowNoise
rowKey={(row) => row.id}
+ rowProps={(row) => ({
+ css: isCredit(row) ? '' : tw`opacity-40`,
+ })}
data={data}
columns={[
{
@@ -62,15 +73,39 @@ export const GpuInstancesTabContent = React.memo(
{
label: '',
align: 'right',
- render: (row) => (
-
-
-
- ),
+ render: (row) => {
+ const disabled = !isCredit(row)
+
+ return (
+
+ To manage this GPU instance, go to the{' '}
+
+
+ )
+ }
+ tooltipPosition={{
+ my: 'bottom-right',
+ at: 'bottom-center',
+ }}
+ >
+
+
+ )
+ },
cellProps: () => ({
css: tw`pl-3!`,
}),
diff --git a/src/components/pages/console/instance/InstancesTabContent/cmp.tsx b/src/components/pages/console/instance/InstancesTabContent/cmp.tsx
index 415de3d0c..5b97edbea 100644
--- a/src/components/pages/console/instance/InstancesTabContent/cmp.tsx
+++ b/src/components/pages/console/instance/InstancesTabContent/cmp.tsx
@@ -20,20 +20,6 @@ export const InstancesTabContent = React.memo(
return row.payment?.type === PaymentType.credit
}, [])
- const isDisabled = useCallback(
- (row: Instance) => {
- return !isCredit(row)
- },
- [isCredit],
- )
-
- const defaultCellProps = useCallback(
- (row: Instance) => {
- return isDisabled(row) ? { css: tw`opacity-40!` } : {}
- },
- [isDisabled],
- )
-
return (
<>
{data.length > 0 ? (
@@ -43,6 +29,9 @@ export const InstancesTabContent = React.memo(
borderType="none"
rowNoise
rowKey={(row) => row.id}
+ rowProps={(row) => ({
+ css: isCredit(row) ? '' : tw`opacity-40`,
+ })}
data={data}
columns={[
{
@@ -51,14 +40,12 @@ export const InstancesTabContent = React.memo(
sortable: true,
render: (row) =>
(row?.metadata?.name as string) || ellipseAddress(row.id),
- cellProps: (row) => defaultCellProps(row),
},
{
label: 'Cores',
align: 'right',
sortable: true,
render: (row) => row?.resources?.vcpus || 0,
- cellProps: (row) => defaultCellProps(row),
},
{
label: 'RAM',
@@ -70,27 +57,24 @@ export const InstancesTabContent = React.memo(
to: 'GiB',
displayUnit: true,
}),
- cellProps: (row) => defaultCellProps(row),
},
{
label: 'date',
align: 'right',
sortable: true,
render: (row) => humanReadableSize(row.size, 'MiB'),
- cellProps: (row) => defaultCellProps(row),
},
{
label: 'Date',
align: 'right',
sortable: true,
render: (row) => row.date,
- cellProps: (row) => defaultCellProps(row),
},
{
label: '',
align: 'right',
render: (row) => {
- const disabled = isDisabled(row)
+ const disabled = !isCredit(row)
return (
To manage this instance, go to the{' '}
row.id}
data={data}
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
rowProps={(row) => ({
- css: row.confirmed ? '' : tw`opacity-60`,
+ // css: row.confirmed ? '' : tw`opacity-60`,
+ css: tw`opacity-40`,
})}
columns={[
{
@@ -44,15 +47,35 @@ export const VolumesTabContent = ({
{
label: '',
align: 'right',
- render: (row) => (
-
-
-
- ),
+ render: (row) => {
+ return (
+
+ To manage this volume, go to the{' '}
+
+
+ }
+ tooltipPosition={{
+ my: 'bottom-right',
+ at: 'bottom-center',
+ }}
+ >
+
+
+ )
+ },
cellProps: () => ({
css: tw`pl-3!`,
}),
diff --git a/src/components/pages/console/website/WebsitesTabContent/cmp.tsx b/src/components/pages/console/website/WebsitesTabContent/cmp.tsx
index 7b2850763..afb8d3cb5 100644
--- a/src/components/pages/console/website/WebsitesTabContent/cmp.tsx
+++ b/src/components/pages/console/website/WebsitesTabContent/cmp.tsx
@@ -1,13 +1,19 @@
-import React from 'react'
+import React, { useCallback } from 'react'
import tw from 'twin.macro'
import { WebsitesTabContentProps } from './types'
import ButtonLink from '@/components/common/ButtonLink'
import EntityTable from '@/components/common/EntityTable'
import { Icon } from '@aleph-front/core'
-import { NAVIGATION_URLS } from '@/helpers/constants'
+import { NAVIGATION_URLS, PaymentMethod } from '@/helpers/constants'
+import { Website } from '@/domain/website'
+import ExternalLink from '@/components/common/ExternalLink'
export const WebsitesTabContent = React.memo(
({ data }: WebsitesTabContentProps) => {
+ const isCredit = useCallback((row: Website) => {
+ return row.payment?.type === PaymentMethod.Credit
+ }, [])
+
return (
<>
{data.length > 0 ? (
@@ -18,8 +24,10 @@ export const WebsitesTabContent = React.memo(
rowNoise
rowKey={(row) => row.id}
data={data}
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
rowProps={(row) => ({
- css: row.confirmed ? '' : tw`opacity-60`,
+ // css: row.confirmed ? '' : tw`opacity-60`,
+ css: tw`opacity-40`,
})}
columns={[
{
@@ -52,15 +60,39 @@ export const WebsitesTabContent = React.memo(
{
label: '',
align: 'right',
- render: (row) => (
-
-
-
- ),
+ render: (row) => {
+ const disabled = !isCredit(row)
+
+ return (
+
+ To manage this website, go to the{' '}
+
+
+ )
+ }
+ tooltipPosition={{
+ my: 'bottom-right',
+ at: 'bottom-center',
+ }}
+ >
+
+
+ )
+ },
cellProps: () => ({
css: tw`pl-3!`,
}),
From 3cff83a6e2293cb332a6258fc78da1c27309e628 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Thu, 2 Oct 2025 15:19:03 +0200
Subject: [PATCH 24/41] disable functions
---
.../function/FunctionsTabContent/cmp.tsx | 23 ++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/src/components/pages/console/function/FunctionsTabContent/cmp.tsx b/src/components/pages/console/function/FunctionsTabContent/cmp.tsx
index 958cd2a03..26213cb80 100644
--- a/src/components/pages/console/function/FunctionsTabContent/cmp.tsx
+++ b/src/components/pages/console/function/FunctionsTabContent/cmp.tsx
@@ -6,6 +6,7 @@ import { convertByteUnits, ellipseAddress } from '@/helpers/utils'
import EntityTable from '@/components/common/EntityTable'
import { Icon } from '@aleph-front/core'
import { NAVIGATION_URLS } from '@/helpers/constants'
+import ExternalLink from '@/components/common/ExternalLink'
export const FunctionsTabContent = React.memo(
({ data }: FunctionsTabContentProps) => {
@@ -19,8 +20,10 @@ export const FunctionsTabContent = React.memo(
rowNoise
rowKey={(row) => row.id}
data={data}
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
rowProps={(row) => ({
- css: row.confirmed ? '' : tw`opacity-60`,
+ // css: row.confirmed ? '' : tw`opacity-60`,
+ css: tw`opacity-40`,
})}
columns={[
{
@@ -77,6 +80,24 @@ export const FunctionsTabContent = React.memo(
kind="functional"
variant="secondary"
href={`${NAVIGATION_URLS.console.computing.functions.home}/${row.id}`}
+ disabled={true}
+ disabledMessage={
+
+ To manage this function, go to the{' '}
+
+
+ }
+ tooltipPosition={{
+ my: 'bottom-right',
+ at: 'bottom-center',
+ }}
>
From d49a0fa579d5a0012469eca6daeb08b402677d5e Mon Sep 17 00:00:00 2001
From: gmolki
Date: Mon, 6 Oct 2025 10:54:16 +0200
Subject: [PATCH 25/41] fix: round credits
---
src/components/common/entityData/EntityPayment/hook.ts | 7 ++++---
src/components/common/entityData/EntityPayment/types.ts | 2 +-
src/components/form/CheckoutSummary/cmp.tsx | 4 +++-
3 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/src/components/common/entityData/EntityPayment/hook.ts b/src/components/common/entityData/EntityPayment/hook.ts
index e78fd4181..80402e567 100644
--- a/src/components/common/entityData/EntityPayment/hook.ts
+++ b/src/components/common/entityData/EntityPayment/hook.ts
@@ -61,7 +61,7 @@ export function useFormatPayment(
const { days, hours, minutes } = getTimeComponents(runningTime)
const runningTimeInHours = days * 24 + hours + minutes / 60
- return (cost * runningTimeInHours).toFixed(6)
+ return Math.round(cost * runningTimeInHours)
}, [cost, isStream, isCredit, runningTime])
// Format blockchain name
@@ -75,8 +75,9 @@ export function useFormatPayment(
if (!isStream && !isCredit) return
if (!cost) return
- const dailyRate = cost * 24
- return `~${dailyRate.toFixed(4)}/day`
+ const dailyRate = Math.round(cost * 24)
+
+ return `~${dailyRate}/day`
}, [cost, isCredit, isStream])
// Format start date
diff --git a/src/components/common/entityData/EntityPayment/types.ts b/src/components/common/entityData/EntityPayment/types.ts
index 53472b8d5..dd38a8ab8 100644
--- a/src/components/common/entityData/EntityPayment/types.ts
+++ b/src/components/common/entityData/EntityPayment/types.ts
@@ -42,7 +42,7 @@ export interface EntityPaymentProps {
export interface FormattedPaymentData {
isStream: boolean
isCredit: boolean
- totalSpent?: string
+ totalSpent?: string | number
formattedBlockchain?: string
formattedFlowRate?: string
formattedStartDate?: string
diff --git a/src/components/form/CheckoutSummary/cmp.tsx b/src/components/form/CheckoutSummary/cmp.tsx
index c51b5435f..3136d7008 100644
--- a/src/components/form/CheckoutSummary/cmp.tsx
+++ b/src/components/form/CheckoutSummary/cmp.tsx
@@ -223,7 +223,9 @@ export const CheckoutSummary = ({
{line.cost !== 0 ? (
- {line.cost}
+
+ {humanReadableCurrency(line.cost)}
+
/ h
) : (
From 3596fe9182ae8f223db9fb4d2f683cc523ba6850 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Mon, 6 Oct 2025 11:26:27 +0200
Subject: [PATCH 26/41] fix: disable top up on account picker
---
package-lock.json | 14 +++++++-------
package.json | 2 +-
src/components/common/Header/cmp.tsx | 2 ++
3 files changed, 10 insertions(+), 8 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 31e700c9b..59e323efe 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,7 @@
"name": "front-aleph-cloud",
"version": "0.34.3",
"dependencies": {
- "@aleph-front/core": "^1.30.1",
+ "@aleph-front/core": "^1.30.2",
"@aleph-sdk/account": "^1.2.0",
"@aleph-sdk/avalanche": "^1.5.0",
"@aleph-sdk/client": "^1.4.5",
@@ -61,9 +61,9 @@
}
},
"node_modules/@aleph-front/core": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/@aleph-front/core/-/core-1.30.1.tgz",
- "integrity": "sha512-si2EjrUG2TwPp1MsD+sq1FA14f/+bz5UrMbAGNhkSKZlFpCVakbp9PdnbHXekgtBktwNJsMgLra2RjV1W74ljg==",
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/@aleph-front/core/-/core-1.30.2.tgz",
+ "integrity": "sha512-18dSBQ8QVSGrwYaXaZ6t6NDTK6uuEnlxz8Kw5lagWniqn2iUbqaLKs2n/z5VsulCay/A8IZyWVUzYHtATPn8Gg==",
"dependencies": {
"@monaco-editor/react": "^4.4.6",
"react-infinite-scroll-hook": "^4.1.1",
@@ -19908,9 +19908,9 @@
},
"dependencies": {
"@aleph-front/core": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/@aleph-front/core/-/core-1.30.1.tgz",
- "integrity": "sha512-si2EjrUG2TwPp1MsD+sq1FA14f/+bz5UrMbAGNhkSKZlFpCVakbp9PdnbHXekgtBktwNJsMgLra2RjV1W74ljg==",
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/@aleph-front/core/-/core-1.30.2.tgz",
+ "integrity": "sha512-18dSBQ8QVSGrwYaXaZ6t6NDTK6uuEnlxz8Kw5lagWniqn2iUbqaLKs2n/z5VsulCay/A8IZyWVUzYHtATPn8Gg==",
"requires": {
"@monaco-editor/react": "^4.4.6",
"react-infinite-scroll-hook": "^4.1.1",
diff --git a/package.json b/package.json
index faba57330..2f111cbd3 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
"lint:fix": "next lint --fix"
},
"dependencies": {
- "@aleph-front/core": "^1.30.1",
+ "@aleph-front/core": "^1.30.2",
"@aleph-sdk/account": "^1.2.0",
"@aleph-sdk/avalanche": "^1.5.0",
"@aleph-sdk/client": "^1.4.5",
diff --git a/src/components/common/Header/cmp.tsx b/src/components/common/Header/cmp.tsx
index a62555a09..ba8d179dd 100644
--- a/src/components/common/Header/cmp.tsx
+++ b/src/components/common/Header/cmp.tsx
@@ -55,6 +55,7 @@ export const Header = () => {
accountAddress={accountAddress}
accountBalance={accountBalance}
showCredits
+ disabledTopUp
accountCredits={accountCreditBalance}
blockchains={blockchains}
networks={networks}
@@ -81,6 +82,7 @@ export const Header = () => {
accountAddress={accountAddress}
accountBalance={accountBalance}
showCredits
+ disabledTopUp
accountCredits={accountCreditBalance}
blockchains={blockchains}
networks={networks}
From 518d1dd9cf3921830dd046849d11427cdce218bf Mon Sep 17 00:00:00 2001
From: gmolki
Date: Mon, 6 Oct 2025 11:30:09 +0200
Subject: [PATCH 27/41] fix: executable actions
---
src/hooks/common/useExecutableActions.ts | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/src/hooks/common/useExecutableActions.ts b/src/hooks/common/useExecutableActions.ts
index 5c5d6a32d..baf6d1525 100644
--- a/src/hooks/common/useExecutableActions.ts
+++ b/src/hooks/common/useExecutableActions.ts
@@ -179,7 +179,6 @@ export function useExecutableActions({
)
const handleStart = useCallback(async () => {
- console.log('starting')
if (!manager) throw Err.ConnectYourWallet
if (!executable) throw Err.InstanceNotFound
@@ -254,7 +253,7 @@ export function useExecutableActions({
const isAllocated = !!status?.ipv6Parsed
const stopDisabled = useMemo(() => {
- if (isPAYG || !crn) return true
+ if (!crn) return true
switch (calculatedStatus) {
case 'v1':
@@ -264,10 +263,10 @@ export function useExecutableActions({
default:
return true
}
- }, [calculatedStatus, crn, isAllocated, isPAYG])
+ }, [calculatedStatus, crn, isAllocated])
const startDisabled = useMemo(() => {
- if (isPAYG || !crn) return true
+ if (!crn) return true
switch (calculatedStatus) {
case 'v1':
@@ -279,7 +278,7 @@ export function useExecutableActions({
default:
return true
}
- }, [calculatedStatus, crn, isAllocated, isPAYG])
+ }, [calculatedStatus, crn, isAllocated])
const rebootDisabled = useMemo(() => {
if (!crn) return true
From f2ed4639e0ea12dadf241c31afb624273280081c Mon Sep 17 00:00:00 2001
From: gmolki
Date: Mon, 6 Oct 2025 13:16:42 +0200
Subject: [PATCH 28/41] feat: ipv4 & ipv6 tab
---
.../EntityConnectionMethods/cmp.tsx | 81 +++++++++----
.../EntityConnectionMethods/hook.ts | 23 +++-
.../EntityConnectionMethods/types.ts | 7 +-
.../entityData/EntityPortForwarding/cmp.tsx | 5 +-
.../entityData/EntityPortForwarding/hook.ts | 108 +++++-------------
.../entityData/EntityPortForwarding/types.ts | 2 +
.../entityData/EntityPortForwarding/utils.ts | 11 ++
.../confidential/ManageConfidential/cmp.tsx | 8 ++
.../confidential/ManageConfidential/hook.ts | 36 ++++++
.../gpuInstance/ManageGpuInstance/cmp.tsx | 8 ++
.../gpuInstance/ManageGpuInstance/hook.ts | 36 ++++++
.../console/instance/ManageInstance/cmp.tsx | 8 ++
.../console/instance/ManageInstance/hook.ts | 36 ++++++
src/hooks/common/useForwardedPorts.ts | 106 +++++++++++++++++
14 files changed, 364 insertions(+), 111 deletions(-)
create mode 100644 src/hooks/common/useForwardedPorts.ts
diff --git a/src/components/common/entityData/EntityConnectionMethods/cmp.tsx b/src/components/common/entityData/EntityConnectionMethods/cmp.tsx
index 180b9f35b..1954b3887 100644
--- a/src/components/common/entityData/EntityConnectionMethods/cmp.tsx
+++ b/src/components/common/entityData/EntityConnectionMethods/cmp.tsx
@@ -1,5 +1,5 @@
import React, { memo } from 'react'
-import { NoisyContainer } from '@aleph-front/core'
+import { NoisyContainer, Tabs } from '@aleph-front/core'
import { EntityConnectionMethodsProps } from './types'
import Skeleton from '../../Skeleton'
import { Text } from '@/components/pages/console/common'
@@ -9,16 +9,21 @@ import InfoTitle from '../InfoTitle'
export const EntityConnectionMethods = ({
executableStatus,
+ sshForwardedPort,
}: EntityConnectionMethodsProps) => {
const {
isLoading,
formattedIPv4,
formattedIPv6,
- formattedSSHCommand,
+ formattedIpv4SSHCommand,
+ formattedIpv6SSHCommand,
handleCopyIpv4,
handleCopyIpv6,
- handleCopyCommand,
- } = useEntityConnectionMethods({ executableStatus })
+ handleCopyIpv4Command,
+ handleCopyIpv6Command,
+ } = useEntityConnectionMethods({ executableStatus, sshForwardedPort })
+
+ const [tabSelected, setTabSelected] = React.useState<'ipv4' | 'ipv6'>('ipv4')
return (
<>
@@ -27,39 +32,67 @@ export const EntityConnectionMethods = ({
+
{
+ setTabSelected(tabSelected === 'ipv4' ? 'ipv6' : 'ipv4')
+ }}
+ align="left"
+ tabs={[
+ {
+ id: 'ipv4',
+ name: 'ipv4',
+ },
+ {
+ id: 'ipv6',
+ name: 'ipv6',
+ },
+ ]}
+ tw="overflow-hidden -mt-3"
+ />
SSH COMMAND
{!isLoading ? (
-
- >_ {formattedSSHCommand}
+ {
+ if (tabSelected === 'ipv4') {
+ handleCopyIpv4Command()
+ } else {
+ handleCopyIpv6Command()
+ }
+ }}
+ >
+
+ >_{' '}
+ {tabSelected === 'ipv4'
+ ? formattedIpv4SSHCommand
+ : formattedIpv6SSHCommand}
+
) : (
)}
- {(isLoading || formattedIPv4 !== '') && (
-
-
IPV4
-
- {!isLoading ? (
-
- {formattedIPv4}
-
- ) : (
-
- )}
-
-
- )}
-
-
IPV6
+
{tabSelected === 'ipv4' ? 'IPV4' : 'IPV6'}
{!isLoading ? (
-
- {formattedIPv6}
+ {
+ if (tabSelected === 'ipv4') {
+ handleCopyIpv4()
+ } else {
+ handleCopyIpv6()
+ }
+ }}
+ >
+
+ {tabSelected === 'ipv4' ? formattedIPv4 : formattedIPv6}
+
) : (
diff --git a/src/components/common/entityData/EntityConnectionMethods/hook.ts b/src/components/common/entityData/EntityConnectionMethods/hook.ts
index 69ec93cff..2a11bbd87 100644
--- a/src/components/common/entityData/EntityConnectionMethods/hook.ts
+++ b/src/components/common/entityData/EntityConnectionMethods/hook.ts
@@ -11,6 +11,7 @@ import {
*/
export function useEntityConnectionMethods({
executableStatus,
+ sshForwardedPort = '????',
}: EntityConnectionMethodsProps): UseEntityConnectionMethodsReturn {
// Check if data is still loading
const isLoading = !executableStatus
@@ -25,23 +26,35 @@ export function useEntityConnectionMethods({
return executableStatus?.hostIpv4 || ''
}, [executableStatus?.hostIpv4])
- // Format the SSH command
- const formattedSSHCommand = useMemo(() => {
+ // Format the IPV6 SSH command
+ const formattedIpv4SSHCommand = useMemo(() => {
+ return `ssh root@${formattedIPv4} -p ${sshForwardedPort}`
+ }, [formattedIPv4, sshForwardedPort])
+
+ // Format the IPV4 SSH command
+ const formattedIpv6SSHCommand = useMemo(() => {
return `ssh root@${formattedIPv6}`
}, [formattedIPv6])
// Create clipboard handlers
const handleCopyIpv4 = useCopyToClipboardAndNotify(formattedIPv4)
const handleCopyIpv6 = useCopyToClipboardAndNotify(formattedIPv6)
- const handleCopyCommand = useCopyToClipboardAndNotify(formattedSSHCommand)
+ const handleCopyIpv4Command = useCopyToClipboardAndNotify(
+ formattedIpv4SSHCommand,
+ )
+ const handleCopyIpv6Command = useCopyToClipboardAndNotify(
+ formattedIpv6SSHCommand,
+ )
return {
isLoading,
formattedIPv6,
formattedIPv4,
- formattedSSHCommand,
+ formattedIpv4SSHCommand,
+ formattedIpv6SSHCommand,
handleCopyIpv6,
handleCopyIpv4,
- handleCopyCommand,
+ handleCopyIpv4Command,
+ handleCopyIpv6Command,
}
}
diff --git a/src/components/common/entityData/EntityConnectionMethods/types.ts b/src/components/common/entityData/EntityConnectionMethods/types.ts
index 345140686..49dd0594c 100644
--- a/src/components/common/entityData/EntityConnectionMethods/types.ts
+++ b/src/components/common/entityData/EntityConnectionMethods/types.ts
@@ -3,6 +3,7 @@ import { ExecutableStatus } from '@/domain/executable'
// Raw data input props
export type EntityConnectionMethodsProps = {
executableStatus?: ExecutableStatus
+ sshForwardedPort?: string
}
// Formatted data returned by the hook
@@ -10,8 +11,10 @@ export type UseEntityConnectionMethodsReturn = {
isLoading: boolean
formattedIPv6: string
formattedIPv4: string
- formattedSSHCommand: string
+ formattedIpv4SSHCommand: string
+ formattedIpv6SSHCommand: string
handleCopyIpv6: () => void
handleCopyIpv4: () => void
- handleCopyCommand: () => void
+ handleCopyIpv4Command: () => void
+ handleCopyIpv6Command: () => void
}
diff --git a/src/components/common/entityData/EntityPortForwarding/cmp.tsx b/src/components/common/entityData/EntityPortForwarding/cmp.tsx
index 3b4815edc..d07d419a7 100644
--- a/src/components/common/entityData/EntityPortForwarding/cmp.tsx
+++ b/src/components/common/entityData/EntityPortForwarding/cmp.tsx
@@ -17,11 +17,12 @@ export const EntityPortForwarding = ({
entityHash,
executableStatus,
executableManager,
+ ports,
+ onPortsChange,
}: EntityPortForwardingProps) => {
const {
// State
showPortForm,
- ports,
// Actions
handleAddPort,
handleCancelAddPort,
@@ -31,6 +32,8 @@ export const EntityPortForwarding = ({
entityHash,
executableStatus,
executableManager,
+ ports,
+ onPortsChange,
})
const tooltipContent = (
diff --git a/src/components/common/entityData/EntityPortForwarding/hook.ts b/src/components/common/entityData/EntityPortForwarding/hook.ts
index 4469accbc..485bce297 100644
--- a/src/components/common/entityData/EntityPortForwarding/hook.ts
+++ b/src/components/common/entityData/EntityPortForwarding/hook.ts
@@ -5,9 +5,6 @@ import { NodeManager } from '@/domain/node'
import { useForwardedPortsManager } from '@/hooks/common/useManager/useForwardedPortsManager'
import { useAppState } from '@/contexts/appState'
import {
- getSystemPorts,
- transformAPIPortsToUI,
- mergePortsWithMappings,
filterOutSystemPorts,
validatePortBatch,
isSystemPort,
@@ -16,9 +13,7 @@ import {
hasUnmappedPorts,
addPendingPorts,
transformPortsToAPIFormat,
- mergePendingPortsWithAggregate,
addPendingPortRemoval,
- applyPendingRemovals,
getPendingRemovalsForEntity,
} from './utils'
@@ -26,12 +21,13 @@ export type UseEntityPortForwardingProps = {
entityHash?: string
executableStatus?: ExecutableStatus
executableManager?: ExecutableManager
+ ports: ForwardedPort[]
+ onPortsChange?: (ports: ForwardedPort[]) => void
}
export type UseEntityPortForwardingReturn = {
// State
showPortForm: boolean
- ports: ForwardedPort[]
isLoading: boolean
error: string | null
@@ -319,100 +315,55 @@ export function useEntityPortForwarding({
entityHash,
executableStatus,
executableManager,
-}: UseEntityPortForwardingProps = {}): UseEntityPortForwardingReturn {
+ ports,
+ onPortsChange,
+}: UseEntityPortForwardingProps): UseEntityPortForwardingReturn {
// Get account address from app state
const [appState] = useAppState()
const { account } = appState.connection
const accountAddress = account?.address
// State
- const [ports, setPorts] = useState(getSystemPorts())
const [showPortForm, setShowPortForm] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState(null)
- const forwardedPortsManager = useForwardedPortsManager()
-
- // Load existing ports for the entity
- const loadPorts = useCallback(async () => {
- if (!entityHash || !accountAddress || !forwardedPortsManager) {
- setPorts(getSystemPorts())
- return
- }
-
- try {
- const existingPorts =
- await forwardedPortsManager.getByEntityHash(entityHash)
-
- const aggregatePorts = existingPorts?.ports || {}
-
- // Merge aggregate ports with cached pending additions
- const portsWithAdditions = mergePendingPortsWithAggregate(
- entityHash,
- accountAddress,
- aggregatePorts,
- )
-
- // Apply pending removals
- const finalPorts = applyPendingRemovals(
- entityHash,
- accountAddress,
- portsWithAdditions,
- )
- const userPorts: ForwardedPort[] = transformAPIPortsToUI(finalPorts)
-
- const allPorts = [...getSystemPorts(), ...userPorts]
- setPorts(allPorts)
- setError(null)
- } catch (error) {
- console.error('Failed to load ports:', error)
- setError('Failed to load ports')
- setPorts(getSystemPorts())
- }
- }, [entityHash, accountAddress, forwardedPortsManager])
-
- // Load ports when entityHash changes
- useEffect(() => {
- loadPorts()
- }, [loadPorts])
-
- // Update ports when executable status changes (mapped ports)
- useEffect(() => {
- if (executableStatus?.mappedPorts) {
- setPorts((currentPorts) =>
- mergePortsWithMappings(currentPorts, executableStatus.mappedPorts),
- )
- }
- }, [executableStatus?.mappedPorts])
-
// State management helpers
- const addPortsToState = useCallback((newPorts: ForwardedPort[]) => {
- setPorts((prev) => [...prev, ...newPorts])
- setShowPortForm(false)
- setError(null)
- }, [])
+ const addPortsToState = useCallback(
+ (newPorts: ForwardedPort[]) => {
+ const updatedPorts = [...ports, ...newPorts]
+ onPortsChange?.(updatedPorts)
+ setShowPortForm(false)
+ setError(null)
+ },
+ [ports, onPortsChange],
+ )
- const removePortFromState = useCallback((portSource: string) => {
- setPorts((prev) => prev.filter((port) => port.source !== portSource))
- setError(null)
- }, [])
+ const removePortFromState = useCallback(
+ (portSource: string) => {
+ const updatedPorts = ports.filter((port) => port.source !== portSource)
+ onPortsChange?.(updatedPorts)
+ setError(null)
+ },
+ [ports, onPortsChange],
+ )
const setPortRemovalState = useCallback(
(portSource: string, isRemoving: boolean) => {
- setPorts((prev) =>
- prev.map((port) =>
- port.source === portSource ? { ...port, isRemoving } : port,
- ),
+ const updatedPorts = ports.map((port) =>
+ port.source === portSource ? { ...port, isRemoving } : port,
)
+ onPortsChange?.(updatedPorts)
},
- [],
+ [ports, onPortsChange],
)
const updatePorts = useCallback(
(updater: (currentPorts: ForwardedPort[]) => ForwardedPort[]) => {
- setPorts(updater)
+ const updatedPorts = updater(ports)
+ onPortsChange?.(updatedPorts)
},
- [],
+ [ports, onPortsChange],
)
const toggleForm = useCallback((show?: boolean) => {
@@ -479,7 +430,6 @@ export function useEntityPortForwarding({
return {
// State
showPortForm,
- ports,
isLoading,
error,
diff --git a/src/components/common/entityData/EntityPortForwarding/types.ts b/src/components/common/entityData/EntityPortForwarding/types.ts
index 652f74347..34e7a5af0 100644
--- a/src/components/common/entityData/EntityPortForwarding/types.ts
+++ b/src/components/common/entityData/EntityPortForwarding/types.ts
@@ -27,4 +27,6 @@ export type EntityPortForwardingProps = {
entityHash?: string
executableStatus?: ExecutableStatus
executableManager?: ExecutableManager
+ ports: ForwardedPort[]
+ onPortsChange?: (ports: ForwardedPort[]) => void
}
diff --git a/src/components/common/entityData/EntityPortForwarding/utils.ts b/src/components/common/entityData/EntityPortForwarding/utils.ts
index d865a65ac..dfd59d989 100644
--- a/src/components/common/entityData/EntityPortForwarding/utils.ts
+++ b/src/components/common/entityData/EntityPortForwarding/utils.ts
@@ -374,3 +374,14 @@ export function applyPendingRemovals(
return filteredPorts
}
+
+/**
+ * Extracts the SSH forwarded port (port 22's destination) from the ports array
+ * Returns the destination port or undefined if not mapped yet
+ */
+export function getSSHForwardedPort(
+ ports: ForwardedPort[],
+): string | undefined {
+ const sshPort = ports.find((port) => port.source === '22')
+ return sshPort?.destination
+}
diff --git a/src/components/pages/console/confidential/ManageConfidential/cmp.tsx b/src/components/pages/console/confidential/ManageConfidential/cmp.tsx
index 417444c03..ea92d42cc 100644
--- a/src/components/pages/console/confidential/ManageConfidential/cmp.tsx
+++ b/src/components/pages/console/confidential/ManageConfidential/cmp.tsx
@@ -83,6 +83,11 @@ export default function ManageConfidential() {
// Navigation handlers
handleBack,
+
+ // Ports
+ ports,
+ sshForwardedPort,
+ handlePortsChange,
} = useManageGpuInstance()
return (
@@ -162,6 +167,7 @@ export default function ManageConfidential() {
,
immutableVolumes.length && (
,
]}
/>
diff --git a/src/components/pages/console/confidential/ManageConfidential/hook.ts b/src/components/pages/console/confidential/ManageConfidential/hook.ts
index eea4b76d6..75e0def76 100644
--- a/src/components/pages/console/confidential/ManageConfidential/hook.ts
+++ b/src/components/pages/console/confidential/ManageConfidential/hook.ts
@@ -1,4 +1,5 @@
import { useRouter } from 'next/router'
+import { useMemo, useState } from 'react'
import { Confidential, ConfidentialManager } from '@/domain/confidential'
import { useConfidentialManager } from '@/hooks/common/useManager/useConfidentialManager'
import { useRequestConfidentials } from '@/hooks/common/useRequestEntity/useRequestConfidentials'
@@ -6,10 +7,16 @@ import {
useManageInstanceEntity,
UseManageInstanceEntityReturn,
} from '@/hooks/common/useEntity/useManageInstanceEntity'
+import { useForwardedPorts } from '@/hooks/common/useForwardedPorts'
+import { getSSHForwardedPort } from '@/components/common/entityData/EntityPortForwarding/utils'
+import { ForwardedPort } from '@/components/common/entityData/EntityPortForwarding/types'
export type UseManageGpuInstanceReturn = UseManageInstanceEntityReturn & {
confidentialInstance?: Confidential
confidentialInstanceManager?: ConfidentialManager
+ ports: ForwardedPort[]
+ sshForwardedPort?: string
+ handlePortsChange: (ports: ForwardedPort[]) => void
}
export function useManageGpuInstance(): UseManageGpuInstanceReturn {
@@ -29,9 +36,38 @@ export function useManageGpuInstance(): UseManageGpuInstanceReturn {
entityManager: confidentialInstanceManager,
})
+ const { status } = manageInstanceEntityProps
+
+ // Fetch forwarded ports
+ const { ports: fetchedPorts } = useForwardedPorts({
+ entityHash: confidentialInstance?.id,
+ executableStatus: status,
+ })
+
+ // Local state for ports to allow updates
+ const [ports, setPorts] = useState(fetchedPorts)
+
+ // Update local ports state when fetched ports change
+ useMemo(() => {
+ setPorts(fetchedPorts)
+ }, [fetchedPorts])
+
+ // Extract SSH forwarded port
+ const sshForwardedPort = useMemo(() => {
+ return getSSHForwardedPort(ports)
+ }, [ports])
+
+ // Handler to update ports
+ const handlePortsChange = (updatedPorts: ForwardedPort[]) => {
+ setPorts(updatedPorts)
+ }
+
return {
confidentialInstance,
confidentialInstanceManager,
+ ports,
+ sshForwardedPort,
+ handlePortsChange,
...manageInstanceEntityProps,
}
}
diff --git a/src/components/pages/console/gpuInstance/ManageGpuInstance/cmp.tsx b/src/components/pages/console/gpuInstance/ManageGpuInstance/cmp.tsx
index 1ed389b73..888522d43 100644
--- a/src/components/pages/console/gpuInstance/ManageGpuInstance/cmp.tsx
+++ b/src/components/pages/console/gpuInstance/ManageGpuInstance/cmp.tsx
@@ -83,6 +83,11 @@ export default function ManageGpuInstance() {
// Navigation handlers
handleBack,
+
+ // Ports
+ ports,
+ sshForwardedPort,
+ handlePortsChange,
} = useManageGpuInstance()
return (
@@ -162,6 +167,7 @@ export default function ManageGpuInstance() {
,
immutableVolumes.length && (
,
]}
/>
diff --git a/src/components/pages/console/gpuInstance/ManageGpuInstance/hook.ts b/src/components/pages/console/gpuInstance/ManageGpuInstance/hook.ts
index 5417cc665..d4bd8ae98 100644
--- a/src/components/pages/console/gpuInstance/ManageGpuInstance/hook.ts
+++ b/src/components/pages/console/gpuInstance/ManageGpuInstance/hook.ts
@@ -1,4 +1,5 @@
import { useRouter } from 'next/router'
+import { useMemo, useState } from 'react'
import { GpuInstance, GpuInstanceManager } from '@/domain/gpuInstance'
import { useGpuInstanceManager } from '@/hooks/common/useManager/useGpuInstanceManager'
import { useRequestGpuInstances } from '@/hooks/common/useRequestEntity/useRequestGpuInstances'
@@ -6,10 +7,16 @@ import {
useManageInstanceEntity,
UseManageInstanceEntityReturn,
} from '@/hooks/common/useEntity/useManageInstanceEntity'
+import { useForwardedPorts } from '@/hooks/common/useForwardedPorts'
+import { getSSHForwardedPort } from '@/components/common/entityData/EntityPortForwarding/utils'
+import { ForwardedPort } from '@/components/common/entityData/EntityPortForwarding/types'
export type UseManageGpuInstanceReturn = UseManageInstanceEntityReturn & {
gpuInstance?: GpuInstance
gpuInstanceManager?: GpuInstanceManager
+ ports: ForwardedPort[]
+ sshForwardedPort?: string
+ handlePortsChange: (ports: ForwardedPort[]) => void
}
export function useManageGpuInstance(): UseManageGpuInstanceReturn {
@@ -29,9 +36,38 @@ export function useManageGpuInstance(): UseManageGpuInstanceReturn {
entityManager: gpuInstanceManager,
})
+ const { status } = manageInstanceEntityProps
+
+ // Fetch forwarded ports
+ const { ports: fetchedPorts } = useForwardedPorts({
+ entityHash: gpuInstance?.id,
+ executableStatus: status,
+ })
+
+ // Local state for ports to allow updates
+ const [ports, setPorts] = useState(fetchedPorts)
+
+ // Update local ports state when fetched ports change
+ useMemo(() => {
+ setPorts(fetchedPorts)
+ }, [fetchedPorts])
+
+ // Extract SSH forwarded port
+ const sshForwardedPort = useMemo(() => {
+ return getSSHForwardedPort(ports)
+ }, [ports])
+
+ // Handler to update ports
+ const handlePortsChange = (updatedPorts: ForwardedPort[]) => {
+ setPorts(updatedPorts)
+ }
+
return {
gpuInstance,
gpuInstanceManager,
+ ports,
+ sshForwardedPort,
+ handlePortsChange,
...manageInstanceEntityProps,
}
}
diff --git a/src/components/pages/console/instance/ManageInstance/cmp.tsx b/src/components/pages/console/instance/ManageInstance/cmp.tsx
index 31b5cd8ab..926daf55b 100644
--- a/src/components/pages/console/instance/ManageInstance/cmp.tsx
+++ b/src/components/pages/console/instance/ManageInstance/cmp.tsx
@@ -88,6 +88,11 @@ export default function ManageInstance() {
// Navigation handlers
handleBack,
+
+ // Ports
+ ports,
+ sshForwardedPort,
+ handlePortsChange,
} = useManageInstance()
return (
@@ -163,6 +168,7 @@ export default function ManageInstance() {
,
immutableVolumes.length && (
,
]}
/>
diff --git a/src/components/pages/console/instance/ManageInstance/hook.ts b/src/components/pages/console/instance/ManageInstance/hook.ts
index 3df5f98a6..bac07d288 100644
--- a/src/components/pages/console/instance/ManageInstance/hook.ts
+++ b/src/components/pages/console/instance/ManageInstance/hook.ts
@@ -1,4 +1,5 @@
import { useRouter } from 'next/router'
+import { useMemo, useState } from 'react'
import { Instance, InstanceManager } from '@/domain/instance'
import { useInstanceManager } from '@/hooks/common/useManager/useInstanceManager'
import { useRequestInstances } from '@/hooks/common/useRequestEntity/useRequestInstances'
@@ -6,10 +7,16 @@ import {
useManageInstanceEntity,
UseManageInstanceEntityReturn,
} from '@/hooks/common/useEntity/useManageInstanceEntity'
+import { useForwardedPorts } from '@/hooks/common/useForwardedPorts'
+import { getSSHForwardedPort } from '@/components/common/entityData/EntityPortForwarding/utils'
+import { ForwardedPort } from '@/components/common/entityData/EntityPortForwarding/types'
export type UseManageInstanceReturn = UseManageInstanceEntityReturn & {
instance?: Instance
instanceManager?: InstanceManager
+ ports: ForwardedPort[]
+ sshForwardedPort?: string
+ handlePortsChange: (ports: ForwardedPort[]) => void
}
export function useManageInstance(): UseManageInstanceReturn {
@@ -29,9 +36,38 @@ export function useManageInstance(): UseManageInstanceReturn {
entityManager: instanceManager,
})
+ const { status } = manageInstanceEntityProps
+
+ // Fetch forwarded ports
+ const { ports: fetchedPorts } = useForwardedPorts({
+ entityHash: instance?.id,
+ executableStatus: status,
+ })
+
+ // Local state for ports to allow updates
+ const [ports, setPorts] = useState(fetchedPorts)
+
+ // Update local ports state when fetched ports change
+ useMemo(() => {
+ setPorts(fetchedPorts)
+ }, [fetchedPorts])
+
+ // Extract SSH forwarded port
+ const sshForwardedPort = useMemo(() => {
+ return getSSHForwardedPort(ports)
+ }, [ports])
+
+ // Handler to update ports
+ const handlePortsChange = (updatedPorts: ForwardedPort[]) => {
+ setPorts(updatedPorts)
+ }
+
return {
instance,
instanceManager,
+ ports,
+ sshForwardedPort,
+ handlePortsChange,
...manageInstanceEntityProps,
}
}
diff --git a/src/hooks/common/useForwardedPorts.ts b/src/hooks/common/useForwardedPorts.ts
new file mode 100644
index 000000000..1cfa2e000
--- /dev/null
+++ b/src/hooks/common/useForwardedPorts.ts
@@ -0,0 +1,106 @@
+import { useState, useEffect, useCallback } from 'react'
+import { useForwardedPortsManager } from '@/hooks/common/useManager/useForwardedPortsManager'
+import { useAppState } from '@/contexts/appState'
+import { ExecutableStatus } from '@/domain/executable'
+import {
+ getSystemPorts,
+ transformAPIPortsToUI,
+ mergePortsWithMappings,
+ mergePendingPortsWithAggregate,
+ applyPendingRemovals,
+} from '@/components/common/entityData/EntityPortForwarding/utils'
+import { ForwardedPort } from '@/components/common/entityData/EntityPortForwarding/types'
+
+export type UseForwardedPortsProps = {
+ entityHash?: string
+ executableStatus?: ExecutableStatus
+}
+
+export type UseForwardedPortsReturn = {
+ ports: ForwardedPort[]
+ isLoading: boolean
+ error: string | null
+ reloadPorts: () => Promise
+}
+
+/**
+ * Hook for fetching and managing forwarded ports for an entity
+ * Extracts port fetching logic to be used at parent component level
+ */
+export function useForwardedPorts({
+ entityHash,
+ executableStatus,
+}: UseForwardedPortsProps = {}): UseForwardedPortsReturn {
+ const [appState] = useAppState()
+ const { account } = appState.connection
+ const accountAddress = account?.address
+
+ const [ports, setPorts] = useState(getSystemPorts())
+ const [isLoading, setIsLoading] = useState(false)
+ const [error, setError] = useState(null)
+
+ const forwardedPortsManager = useForwardedPortsManager()
+
+ // Load existing ports for the entity
+ const loadPorts = useCallback(async () => {
+ if (!entityHash || !accountAddress || !forwardedPortsManager) {
+ setPorts(getSystemPorts())
+ setIsLoading(false)
+ return
+ }
+
+ setIsLoading(true)
+ try {
+ const existingPorts =
+ await forwardedPortsManager.getByEntityHash(entityHash)
+
+ const aggregatePorts = existingPorts?.ports || {}
+
+ // Merge aggregate ports with cached pending additions
+ const portsWithAdditions = mergePendingPortsWithAggregate(
+ entityHash,
+ accountAddress,
+ aggregatePorts,
+ )
+
+ // Apply pending removals
+ const finalPorts = applyPendingRemovals(
+ entityHash,
+ accountAddress,
+ portsWithAdditions,
+ )
+ const userPorts: ForwardedPort[] = transformAPIPortsToUI(finalPorts)
+
+ const allPorts = [...getSystemPorts(), ...userPorts]
+ setPorts(allPorts)
+ setError(null)
+ } catch (err) {
+ console.error('Failed to load ports:', err)
+ setError('Failed to load ports')
+ setPorts(getSystemPorts())
+ } finally {
+ setIsLoading(false)
+ }
+ }, [entityHash, accountAddress, forwardedPortsManager])
+
+ // Load ports when dependencies change
+ useEffect(() => {
+ loadPorts()
+ }, [loadPorts])
+
+ // Update ports when executable status changes (mapped ports)
+ useEffect(() => {
+ if (executableStatus?.mappedPorts) {
+ setPorts((currentPorts) =>
+ mergePortsWithMappings(currentPorts, executableStatus.mappedPorts),
+ )
+ }
+ }, [executableStatus?.mappedPorts])
+
+ return {
+ ports,
+ isLoading,
+ error,
+ reloadPorts: loadPorts,
+ }
+}
From 01991432a5257bdad5f3644804da12207846f275 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Mon, 6 Oct 2025 15:36:26 +0200
Subject: [PATCH 29/41] fix: remove 150000 fallback
---
src/helpers/utils.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts
index 70664b23a..35f2eb2bd 100644
--- a/src/helpers/utils.ts
+++ b/src/helpers/utils.ts
@@ -71,7 +71,7 @@ export const getAddressBalance = async (address: string) => {
return {
balance: balance as number,
- creditBalance: (credit_balance as number) || 150000, // @todo: temporary hack to give free credit balance
+ creditBalance: credit_balance as number,
}
} catch (error) {
throw Err.RequestFailed(error)
From 066995d0dcca66f63336fe659db39ecd3813372a Mon Sep 17 00:00:00 2001
From: gmolki
Date: Mon, 6 Oct 2025 15:39:22 +0200
Subject: [PATCH 30/41] disable nav items
---
src/hooks/common/useRoutes.ts | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/hooks/common/useRoutes.ts b/src/hooks/common/useRoutes.ts
index caeab51d3..e5f84d4e0 100644
--- a/src/hooks/common/useRoutes.ts
+++ b/src/hooks/common/useRoutes.ts
@@ -71,6 +71,7 @@ export function useRoutes(): UseRoutesReturn {
name: 'Manage your website',
href: NAVIGATION_URLS.console.web3Hosting.website.home,
icon: 'manageWebsite',
+ disabled: true,
},
],
},
@@ -83,6 +84,7 @@ export function useRoutes(): UseRoutesReturn {
name: 'Functions',
href: NAVIGATION_URLS.console.computing.functions.home,
icon: 'functions',
+ disabled: true,
},
{
name: 'Instances',
@@ -94,12 +96,14 @@ export function useRoutes(): UseRoutesReturn {
icon: 'gpu',
label: '(BETA)',
href: NAVIGATION_URLS.console.computing.gpus.home,
+ disabled: true,
},
{
name: 'Confidentials',
href: NAVIGATION_URLS.console.computing.confidentials.home,
label: '(BETA)',
icon: 'confidential',
+ disabled: true,
},
],
},
@@ -112,6 +116,7 @@ export function useRoutes(): UseRoutesReturn {
name: 'Volumes',
href: NAVIGATION_URLS.console.storage.home,
icon: 'storageSolutions',
+ disabled: true,
},
],
},
From 3413c3f3b9f6c9716b827d8f85f58d39fe579df2 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Mon, 6 Oct 2025 15:43:46 +0200
Subject: [PATCH 31/41] fix: remove checkout margin
---
src/components/form/CheckoutSummary/cmp.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/components/form/CheckoutSummary/cmp.tsx b/src/components/form/CheckoutSummary/cmp.tsx
index 3136d7008..c88f54501 100644
--- a/src/components/form/CheckoutSummary/cmp.tsx
+++ b/src/components/form/CheckoutSummary/cmp.tsx
@@ -166,7 +166,6 @@ export const CheckoutSummary = ({
return (
<>
-
Date: Mon, 6 Oct 2025 15:44:47 +0200
Subject: [PATCH 32/41] fix: port forwarding tooltip postitio
---
src/components/common/entityData/EntityPortForwarding/cmp.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/common/entityData/EntityPortForwarding/cmp.tsx b/src/components/common/entityData/EntityPortForwarding/cmp.tsx
index d07d419a7..64330c36b 100644
--- a/src/components/common/entityData/EntityPortForwarding/cmp.tsx
+++ b/src/components/common/entityData/EntityPortForwarding/cmp.tsx
@@ -63,7 +63,7 @@ export const EntityPortForwarding = ({
Date: Tue, 7 Oct 2025 11:48:09 +0200
Subject: [PATCH 33/41] update copies
---
.../gpuInstance/NewGpuInstancePage/cmp.tsx | 10 ++++-----
.../console/instance/NewInstancePage/cmp.tsx | 22 +++++++++----------
2 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/src/components/pages/console/gpuInstance/NewGpuInstancePage/cmp.tsx b/src/components/pages/console/gpuInstance/NewGpuInstancePage/cmp.tsx
index 8ecc14a41..0a99ba1d3 100644
--- a/src/components/pages/console/gpuInstance/NewGpuInstancePage/cmp.tsx
+++ b/src/components/pages/console/gpuInstance/NewGpuInstancePage/cmp.tsx
@@ -420,11 +420,11 @@ export default function NewGpuInstancePage({ mainRef }: PageProps) {
mainRef={mainRef}
description={
<>
- You can either leverage the traditional method of holding tokens
- in your wallet for resource access, or opt for the Pay-As-You-Go
- (PAYG) system, which allows you to pay precisely for what you use,
- for the duration you need. The PAYG option includes a token stream
- feature, enabling real-time payment for resources as you use them.
+ Aleph Cloud runs on a credit-based system ,
+ designed for flexibility and transparency. You can top up credits
+ with fiat, USDC, or ALEPH . Your credits are
+ deducted only as you consume resources, ensuring you pay exactly
+ for what you use.
>
}
// Duplicate buttons to have different references for the tooltip on each one
diff --git a/src/components/pages/console/instance/NewInstancePage/cmp.tsx b/src/components/pages/console/instance/NewInstancePage/cmp.tsx
index 78d9cda88..f3f9ed79f 100644
--- a/src/components/pages/console/instance/NewInstancePage/cmp.tsx
+++ b/src/components/pages/console/instance/NewInstancePage/cmp.tsx
@@ -261,12 +261,12 @@ export default function NewInstancePage({ mainRef }: PageProps) {
Your instance is set up with your manually selected Compute
- Resource Node (CRN), operating under the{' '}
- Pay-as-you-go payment method on{' '}
- {blockchainName} . This setup gives you direct
- control over your resource allocation and costs, requiring active
- management of your instance. To adjust your CRN or explore
- different payment options, you can modify your selection below.
+ Resource Node (CRN) and powered by{' '}
+ Aleph Cloud Credits . Credits are pre-purchased
+ using fiat, USDC, or ALEPH and automatically deducted as your
+ instance consumes resources. This setup gives you full control
+ over your node selection, cost, and balance. To adjust your CRN or
+ add more credits, you can update your configuration below.
@@ -430,11 +430,11 @@ export default function NewInstancePage({ mainRef }: PageProps) {
mainRef={mainRef}
description={
<>
- You can either leverage the traditional method of holding tokens
- in your wallet for resource access, or opt for the Pay-As-You-Go
- (PAYG) system, which allows you to pay precisely for what you use,
- for the duration you need. The PAYG option includes a token stream
- feature, enabling real-time payment for resources as you use them.
+ Aleph Cloud runs on a credit-based system ,
+ designed for flexibility and transparency. You can top up credits
+ with fiat, USDC, or ALEPH . Your credits are
+ deducted only as you consume resources, ensuring you pay exactly
+ for what you use.
>
}
button={
From be391b3bc831d2a8538bc312bb61d3352582b400 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Tue, 7 Oct 2025 15:45:39 +0200
Subject: [PATCH 34/41] remove unused const
---
.../pages/console/instance/NewInstancePage/cmp.tsx | 1 -
.../pages/console/instance/NewInstancePage/hook.ts | 8 +-------
2 files changed, 1 insertion(+), 8 deletions(-)
diff --git a/src/components/pages/console/instance/NewInstancePage/cmp.tsx b/src/components/pages/console/instance/NewInstancePage/cmp.tsx
index f3f9ed79f..7e80522a1 100644
--- a/src/components/pages/console/instance/NewInstancePage/cmp.tsx
+++ b/src/components/pages/console/instance/NewInstancePage/cmp.tsx
@@ -41,7 +41,6 @@ export default function NewInstancePage({ mainRef }: PageProps) {
const {
address,
accountCreditBalance,
- blockchainName,
manuallySelectCRNDisabled,
manuallySelectCRNDisabledMessage,
createInstanceDisabled,
diff --git a/src/components/pages/console/instance/NewInstancePage/hook.ts b/src/components/pages/console/instance/NewInstancePage/hook.ts
index 3919e5817..aa931c825 100644
--- a/src/components/pages/console/instance/NewInstancePage/hook.ts
+++ b/src/components/pages/console/instance/NewInstancePage/hook.ts
@@ -43,7 +43,7 @@ import {
import { EntityAddAction } from '@/store/entity'
import { useConnection } from '@/hooks/common/useConnection'
import Err from '@/helpers/errors'
-import { BlockchainId, blockchains } from '@/domain/connect/base'
+import { BlockchainId } from '@/domain/connect/base'
import {
CreditPaymentConfiguration,
PaymentConfiguration,
@@ -75,7 +75,6 @@ export type Modal = 'node-list' | 'terms-and-conditions'
export type UseNewInstancePageReturn = {
address: string
accountCreditBalance: number
- blockchainName: string
manuallySelectCRNDisabled: boolean
manuallySelectCRNDisabledMessage?: TooltipProps['content']
createInstanceDisabled: boolean
@@ -309,10 +308,6 @@ export function useNewInstancePage(): UseNewInstancePageReturn {
return !!node?.terms_and_conditions
}, [node])
- const blockchainName = useMemo(() => {
- return blockchain ? blockchains[blockchain]?.name : 'Current network'
- }, [blockchain])
-
const address = useMemo(() => account?.address || '', [account])
const manuallySelectCRNDisabledMessage: UseNewInstancePageReturn['manuallySelectCRNDisabledMessage'] =
@@ -437,7 +432,6 @@ export function useNewInstancePage(): UseNewInstancePageReturn {
return {
address,
accountCreditBalance,
- blockchainName,
createInstanceDisabled,
createInstanceButtonTitle,
manuallySelectCRNDisabled,
From 070fedbe18d39a97c53a2890e55d6393220a986e Mon Sep 17 00:00:00 2001
From: gmolki
Date: Tue, 7 Oct 2025 15:59:06 +0200
Subject: [PATCH 35/41] use price cmp
---
src/components/common/Price/cmp.tsx | 5 ++-
src/components/common/Price/types.ts | 3 ++
src/components/form/CheckoutSummary/cmp.tsx | 35 +++++++------------
.../form/CheckoutSummaryFooter/cmp.tsx | 4 +--
4 files changed, 21 insertions(+), 26 deletions(-)
diff --git a/src/components/common/Price/cmp.tsx b/src/components/common/Price/cmp.tsx
index 8a4573fa8..901b42d8e 100644
--- a/src/components/common/Price/cmp.tsx
+++ b/src/components/common/Price/cmp.tsx
@@ -6,6 +6,7 @@ import { humanReadableCurrency } from '@/helpers/utils'
export const Price = ({
value,
+ type = 'token',
duration,
iconSize = '0.75em',
...rest
@@ -13,7 +14,9 @@ export const Price = ({
return (
{humanReadableCurrency(value)}
-
+ {type === 'token' && (
+
+ )}
{duration && / {duration} }
)
diff --git a/src/components/common/Price/types.ts b/src/components/common/Price/types.ts
index 4c47eb3c6..9b9b2fce0 100644
--- a/src/components/common/Price/types.ts
+++ b/src/components/common/Price/types.ts
@@ -1,8 +1,11 @@
import { StreamDurationUnit } from '@/hooks/form/useSelectStreamDuration'
import { HTMLAttributes } from 'react'
+export type PriceType = 'token' | 'credit'
+
export type PriceProps = HTMLAttributes & {
value: number | undefined
+ type?: PriceType
duration?: StreamDurationUnit
iconSize?: string
}
diff --git a/src/components/form/CheckoutSummary/cmp.tsx b/src/components/form/CheckoutSummary/cmp.tsx
index c88f54501..db6ee8552 100644
--- a/src/components/form/CheckoutSummary/cmp.tsx
+++ b/src/components/form/CheckoutSummary/cmp.tsx
@@ -9,6 +9,7 @@ import CheckoutSummaryFooter from '../CheckoutSummaryFooter'
import { useConnection } from '@/hooks/common/useConnection'
import { Blockchain } from '@aleph-sdk/core'
import { useNFTVoucherBalance } from '@/hooks/common/useNFTVoucherBalance'
+import Price from '@/components/common/Price'
// const CheckoutSummaryVolumeLine = ({
// volume,
@@ -221,21 +222,13 @@ export const CheckoutSummary = ({
{line.cost !== 0 ? (
-
-
- {humanReadableCurrency(line.cost)}
-
- / h
-
+
) : (
- //
'-'
)}
@@ -278,11 +271,7 @@ export const CheckoutSummary = ({
Total credits / h
- {/* */}
-
- {humanReadableCurrency(cost?.cost)}
- / h
-
+
@@ -291,10 +280,10 @@ export const CheckoutSummary = ({
Min. required
- {/* */}
-
- {humanReadableCurrency(cost?.cost * 4)}
-
+
diff --git a/src/components/form/CheckoutSummaryFooter/cmp.tsx b/src/components/form/CheckoutSummaryFooter/cmp.tsx
index 6fe260765..68c12daf9 100644
--- a/src/components/form/CheckoutSummaryFooter/cmp.tsx
+++ b/src/components/form/CheckoutSummaryFooter/cmp.tsx
@@ -3,7 +3,7 @@ import { Button, ButtonProps } from '@aleph-front/core'
import { StyledSeparator } from './styles'
import { CheckoutSummaryFooterProps } from './types'
import FloatingFooter from '../FloatingFooter'
-import { humanReadableCurrency } from '@/helpers/utils'
+import Price from '@/components/common/Price'
// ------------------------------------------
@@ -37,7 +37,7 @@ export const CheckoutSummaryFooter = ({
- {humanReadableCurrency(totalCost)}
+
Credits / h
From de8b7777aa7b9683e9982e20de1333c8950267f6 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Tue, 7 Oct 2025 17:05:44 +0200
Subject: [PATCH 36/41] price estimation loading stte
---
src/components/common/Price/cmp.tsx | 10 +++
src/components/common/Price/types.ts | 1 +
src/components/form/CheckoutSummary/cmp.tsx | 18 ++++-
.../form/CheckoutSummaryFooter/cmp.tsx | 3 +-
.../form/CheckoutSummaryFooter/types.ts | 1 +
src/hooks/common/useCanAfford.ts | 7 +-
src/hooks/common/useEntityCost.ts | 81 ++++++++++++-------
7 files changed, 86 insertions(+), 35 deletions(-)
diff --git a/src/components/common/Price/cmp.tsx b/src/components/common/Price/cmp.tsx
index 901b42d8e..904b20dcd 100644
--- a/src/components/common/Price/cmp.tsx
+++ b/src/components/common/Price/cmp.tsx
@@ -3,14 +3,24 @@ import { StyledPrice } from './styles'
import { PriceProps } from './types'
import { Logo } from '@aleph-front/core'
import { humanReadableCurrency } from '@/helpers/utils'
+import Skeleton from '@/components/common/Skeleton'
export const Price = ({
value,
type = 'token',
duration,
iconSize = '0.75em',
+ loading = false,
...rest
}: PriceProps) => {
+ if (loading) {
+ return (
+
+
+
+ )
+ }
+
return (
{humanReadableCurrency(value)}
diff --git a/src/components/common/Price/types.ts b/src/components/common/Price/types.ts
index 9b9b2fce0..316230a28 100644
--- a/src/components/common/Price/types.ts
+++ b/src/components/common/Price/types.ts
@@ -8,4 +8,5 @@ export type PriceProps = HTMLAttributes & {
type?: PriceType
duration?: StreamDurationUnit
iconSize?: string
+ loading?: boolean
}
diff --git a/src/components/form/CheckoutSummary/cmp.tsx b/src/components/form/CheckoutSummary/cmp.tsx
index db6ee8552..8084c4176 100644
--- a/src/components/form/CheckoutSummary/cmp.tsx
+++ b/src/components/form/CheckoutSummary/cmp.tsx
@@ -171,7 +171,8 @@ export const CheckoutSummary = ({
{...{
submitButton: footerButton,
mainRef,
- totalCost: cost?.cost,
+ totalCost: cost?.cost?.cost,
+ loading: cost.loading,
}}
/>
)}
- {cost?.lines?.map((line) => (
+ {cost?.cost?.lines?.map((line) => (
{line.name}
@@ -227,6 +228,7 @@ export const CheckoutSummary = ({
value={line.cost}
duration="h"
className="tp-body3"
+ loading={cost.loading}
/>
) : (
'-'
@@ -271,7 +273,12 @@ export const CheckoutSummary = ({
Total credits / h
@@ -282,7 +289,10 @@ export const CheckoutSummary = ({
diff --git a/src/components/form/CheckoutSummaryFooter/cmp.tsx b/src/components/form/CheckoutSummaryFooter/cmp.tsx
index 68c12daf9..4aae30e31 100644
--- a/src/components/form/CheckoutSummaryFooter/cmp.tsx
+++ b/src/components/form/CheckoutSummaryFooter/cmp.tsx
@@ -11,6 +11,7 @@ export const CheckoutSummaryFooter = ({
submitButton: submitButtonNode,
mainRef: containerRef,
totalCost,
+ loading = false,
shouldHide = true,
thresholdOffset = 600,
...rest
@@ -37,7 +38,7 @@ export const CheckoutSummaryFooter = ({
-
+
Credits / h
diff --git a/src/components/form/CheckoutSummaryFooter/types.ts b/src/components/form/CheckoutSummaryFooter/types.ts
index 12f7ae73b..6d9d49913 100644
--- a/src/components/form/CheckoutSummaryFooter/types.ts
+++ b/src/components/form/CheckoutSummaryFooter/types.ts
@@ -8,4 +8,5 @@ export type CheckoutSummaryFooterProps = Pick<
submitButton?: ReactNode
mainRef?: RefObject
totalCost?: number
+ loading?: boolean
}
diff --git a/src/hooks/common/useCanAfford.ts b/src/hooks/common/useCanAfford.ts
index c44152092..6bf11c686 100644
--- a/src/hooks/common/useCanAfford.ts
+++ b/src/hooks/common/useCanAfford.ts
@@ -1,8 +1,8 @@
-import { CostSummary } from '@/domain/cost'
+import { UseEntityCostReturn } from './useEntityCost'
export type UseCanAffordProps = {
accountCreditBalance: number
- cost?: CostSummary
+ cost: UseEntityCostReturn
}
export type UseCanAffordReturn = {
@@ -15,7 +15,8 @@ export function useCanAfford({
cost,
}: UseCanAffordProps): UseCanAffordReturn {
const canAfford =
- accountCreditBalance >= (cost ? cost.cost : Number.MAX_SAFE_INTEGER)
+ accountCreditBalance >=
+ (cost.cost ? cost.cost.cost : Number.MAX_SAFE_INTEGER)
const isCreateButtonDisabled =
process.env.NEXT_PUBLIC_OVERRIDE_ALEPH_BALANCE === 'true'
diff --git a/src/hooks/common/useEntityCost.ts b/src/hooks/common/useEntityCost.ts
index 18e1de309..aaad7fb27 100644
--- a/src/hooks/common/useEntityCost.ts
+++ b/src/hooks/common/useEntityCost.ts
@@ -46,7 +46,10 @@ export type UseEntityCostProps =
| UseProgramCostProps
| UseWebsiteCostProps
-export type UseEntityCostReturn = CostSummary
+export type UseEntityCostReturn = {
+ cost: CostSummary
+ loading: boolean
+}
export function useEntityCost(props: UseEntityCostProps): UseEntityCostReturn {
// Use useMemo to prevent the object from being recreated on every render
@@ -59,7 +62,8 @@ export function useEntityCost(props: UseEntityCostProps): UseEntityCostReturn {
[],
)
- const [cost, setCost] = useState(emptyCost)
+ const [cost, setCost] = useState(emptyCost)
+ const [loading, setLoading] = useState(false)
const volumeManager = useVolumeManager()
const instanceManager = useInstanceManager()
@@ -76,6 +80,12 @@ export function useEntityCost(props: UseEntityCostProps): UseEntityCostReturn {
// Store previous debounced string to detect changes
const prevDebouncedPropsString = usePrevious(debouncedPropsString)
+ // Track if we're waiting for debounce or actively fetching
+ const isDebouncing = useMemo(
+ () => propsString !== debouncedPropsString,
+ [debouncedPropsString, propsString],
+ )
+
// Return the original props only when the debounced string changes
const debouncedProps = useMemo(() => {
// Check if the debounced string has changed
@@ -90,34 +100,42 @@ export function useEntityCost(props: UseEntityCostProps): UseEntityCostReturn {
// Only make the API call when the debounced props change
useEffect(() => {
async function load() {
+ console.log('Loading entity cost with props:', debouncedProps)
// Skip if debouncedProps is undefined (no change detected)
if (!debouncedProps) return
- let result: CostSummary = emptyCost
- const { entityType, props } = debouncedProps
-
- switch (entityType) {
- case EntityType.Volume:
- if (volumeManager) result = await volumeManager.getCost(props)
- break
- case EntityType.Instance:
- if (instanceManager) result = await instanceManager.getCost(props)
- break
- case EntityType.GpuInstance:
- if (gpuInstanceManager)
- result = await gpuInstanceManager.getCost(props)
- break
- case EntityType.Program:
- console.log('Fetching program cost with props:', props)
- if (programManager) result = await programManager.getCost(props)
- break
- case EntityType.Website:
- console.log('Fetching website cost with props:', props)
- if (websiteManager) result = await websiteManager.getCost(props)
- break
+ try {
+ setLoading(true)
+ let result: CostSummary = emptyCost
+ const { entityType, props } = debouncedProps
+
+ switch (entityType) {
+ case EntityType.Volume:
+ if (volumeManager) result = await volumeManager.getCost(props)
+ break
+ case EntityType.Instance:
+ if (instanceManager) result = await instanceManager.getCost(props)
+ break
+ case EntityType.GpuInstance:
+ if (gpuInstanceManager)
+ result = await gpuInstanceManager.getCost(props)
+ break
+ case EntityType.Program:
+ console.log('Fetching program cost with props:', props)
+ if (programManager) result = await programManager.getCost(props)
+ break
+ case EntityType.Website:
+ console.log('Fetching website cost with props:', props)
+ if (websiteManager) result = await websiteManager.getCost(props)
+ break
+ }
+
+ setCost(result)
+ } catch (e) {
+ console.error('Error fetching entity cost:', e)
+ } finally {
+ setLoading(false)
}
-
- setCost(result)
}
load()
@@ -131,5 +149,14 @@ export function useEntityCost(props: UseEntityCostProps): UseEntityCostReturn {
websiteManager,
])
- return cost
+ const isLoading = loading || isDebouncing
+
+ console.log('loading', {
+ cost,
+ loading: isLoading,
+ isDebouncing,
+ fetchingData: loading,
+ })
+
+ return { cost, loading: isLoading }
}
From fef0a916050da17116a9b8bf3d0c3c1ced81d495 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Tue, 7 Oct 2025 17:07:39 +0200
Subject: [PATCH 37/41] remove console logs
---
src/components/common/entityData/EntityPayment/cmp.tsx | 1 -
src/components/common/entityData/EntityPayment/hook.ts | 2 --
src/domain/executable.ts | 1 -
src/domain/program.ts | 2 --
src/hooks/common/useEntityCost.ts | 10 ----------
src/hooks/common/useExecutableActions.ts | 1 -
src/hooks/common/useSyncPaymentMethod.ts | 2 --
7 files changed, 19 deletions(-)
diff --git a/src/components/common/entityData/EntityPayment/cmp.tsx b/src/components/common/entityData/EntityPayment/cmp.tsx
index ad284d612..74da686c3 100644
--- a/src/components/common/entityData/EntityPayment/cmp.tsx
+++ b/src/components/common/entityData/EntityPayment/cmp.tsx
@@ -136,7 +136,6 @@ const PaymentCard = ({ paymentData }: { paymentData: PaymentData }) => {
* Takes an array of payment data objects and renders a card for each one
*/
export const EntityPayment = ({ payments }: EntityPaymentProps) => {
- console.log('Rendering EntityPayment with payments:', payments)
return (
<>
diff --git a/src/components/common/entityData/EntityPayment/hook.ts b/src/components/common/entityData/EntityPayment/hook.ts
index 80402e567..4f153fb74 100644
--- a/src/components/common/entityData/EntityPayment/hook.ts
+++ b/src/components/common/entityData/EntityPayment/hook.ts
@@ -116,8 +116,6 @@ export function useFormatPayment(
const receiverType = useMemo(() => {
if (!receiver) return undefined
- console.log('Receiver address:', receiver)
- console.log('communityWalletAddress:', communityWalletAddress)
if (receiver == (communityWalletAddress as string)) {
return 'Community Wallet (20%)'
}
diff --git a/src/domain/executable.ts b/src/domain/executable.ts
index 797b5a6d4..e0d62c6f8 100644
--- a/src/domain/executable.ts
+++ b/src/domain/executable.ts
@@ -876,7 +876,6 @@ export abstract class ExecutableManager
{
receiver: payment.receiver,
}
} else {
- console.log('Parsing payment for cost estimation:', payment)
return {
chain: payment?.chain || BlockchainId.ETH,
type:
diff --git a/src/domain/program.ts b/src/domain/program.ts
index efa8034ed..14fd0453f 100644
--- a/src/domain/program.ts
+++ b/src/domain/program.ts
@@ -276,7 +276,6 @@ export class ProgramManager
const parsedProgram: ProgramPublishConfiguration =
await this.parseProgramForCostEstimation(newProgram)
- console.log('Parsed program for cost estimation:', parsedProgram)
const costs =
await this.sdkClient.programClient.getEstimatedCost(parsedProgram)
@@ -354,7 +353,6 @@ export class ProgramManager
): Promise {
const { account = mockAccount, channel } = this
const { isPersistent, specs } = newProgram
- console.log('Parsing program for cost estimation:', newProgram)
const parsedSpecs = this.parseSpecs(specs)
const memory = parsedSpecs?.memory
diff --git a/src/hooks/common/useEntityCost.ts b/src/hooks/common/useEntityCost.ts
index aaad7fb27..64651c8dc 100644
--- a/src/hooks/common/useEntityCost.ts
+++ b/src/hooks/common/useEntityCost.ts
@@ -100,7 +100,6 @@ export function useEntityCost(props: UseEntityCostProps): UseEntityCostReturn {
// Only make the API call when the debounced props change
useEffect(() => {
async function load() {
- console.log('Loading entity cost with props:', debouncedProps)
// Skip if debouncedProps is undefined (no change detected)
if (!debouncedProps) return
@@ -121,11 +120,9 @@ export function useEntityCost(props: UseEntityCostProps): UseEntityCostReturn {
result = await gpuInstanceManager.getCost(props)
break
case EntityType.Program:
- console.log('Fetching program cost with props:', props)
if (programManager) result = await programManager.getCost(props)
break
case EntityType.Website:
- console.log('Fetching website cost with props:', props)
if (websiteManager) result = await websiteManager.getCost(props)
break
}
@@ -151,12 +148,5 @@ export function useEntityCost(props: UseEntityCostProps): UseEntityCostReturn {
const isLoading = loading || isDebouncing
- console.log('loading', {
- cost,
- loading: isLoading,
- isDebouncing,
- fetchingData: loading,
- })
-
return { cost, loading: isLoading }
}
diff --git a/src/hooks/common/useExecutableActions.ts b/src/hooks/common/useExecutableActions.ts
index baf6d1525..abaea16e5 100644
--- a/src/hooks/common/useExecutableActions.ts
+++ b/src/hooks/common/useExecutableActions.ts
@@ -121,7 +121,6 @@ export function useExecutableActions({
}, [executable, manager])
const nodeDetails = useMemo(() => {
- console.log('crn', crn)
if (!crn) return
return {
name: crn.name || crn.hash,
diff --git a/src/hooks/common/useSyncPaymentMethod.ts b/src/hooks/common/useSyncPaymentMethod.ts
index 381feec9b..b5d310748 100644
--- a/src/hooks/common/useSyncPaymentMethod.ts
+++ b/src/hooks/common/useSyncPaymentMethod.ts
@@ -40,7 +40,6 @@ export function useSyncPaymentMethod({
if (isUpdatingRef.current) return
isUpdatingRef.current = true
- console.log('Update global state when form changes', formPaymentMethod)
setPaymentMethod(formPaymentMethod)
setTimeout(() => (isUpdatingRef.current = false), 0)
@@ -50,7 +49,6 @@ export function useSyncPaymentMethod({
useEffect(() => {
if (isUpdatingRef.current) return
- console.log('Update form when global state changes', globalPaymentMethod)
setValue(fieldName, globalPaymentMethod)
}, [fieldName, globalPaymentMethod, setValue])
}
From ca95d0e3e7e325faaabd9328bf74e33985b12626 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Wed, 8 Oct 2025 10:11:12 +0200
Subject: [PATCH 38/41] filter nodes by version
---
src/domain/cost.ts | 1 +
src/helpers/utils.ts | 28 +++++++++++++++++++++
src/hooks/common/useCRNList.ts | 46 ++++++++++++++++++++++++++++------
3 files changed, 68 insertions(+), 7 deletions(-)
diff --git a/src/domain/cost.ts b/src/domain/cost.ts
index 6f79b9854..5a6ea37c0 100644
--- a/src/domain/cost.ts
+++ b/src/domain/cost.ts
@@ -83,6 +83,7 @@ export type PricingAggregate = Record
export type SettingsAggregate = {
compatibleGpus: GPUDevice[]
+ lastCrnVersion: string
communityWalletAddress: string
communityWalletTimestamp: number
}
diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts
index 35f2eb2bd..54f9b8987 100644
--- a/src/helpers/utils.ts
+++ b/src/helpers/utils.ts
@@ -657,6 +657,34 @@ export function getVersionNumber(version: string): number {
}
}
+/**
+ * Compares two semantic version strings to determine if the first version
+ * is greater than or equal to the second version.
+ *
+ * @param version - The version to check (e.g., "1.8.5")
+ * @param minVersion - The minimum required version (e.g., "1.7.2")
+ * @returns true if version >= minVersion, false otherwise
+ *
+ * @example
+ * compareVersion("1.8.5", "1.7.2") // true
+ * compareVersion("1.7.0", "1.7.2") // false
+ * compareVersion("2.0.0", "1.7.2") // true
+ */
+export function compareVersion(version: string, minVersion: string): boolean {
+ const v1Parts = version.split('.').map(Number)
+ const v2Parts = minVersion.split('.').map(Number)
+
+ for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
+ const v1 = v1Parts[i] || 0
+ const v2 = v2Parts[i] || 0
+
+ if (v1 > v2) return true
+ if (v1 < v2) return false
+ }
+
+ return true
+}
+
/**
* An util for controlling concurrency (lock / unlock) forcing sequential access
* to some region of the code
diff --git a/src/hooks/common/useCRNList.ts b/src/hooks/common/useCRNList.ts
index 0a8ccea5a..3d7f6f3c8 100644
--- a/src/hooks/common/useCRNList.ts
+++ b/src/hooks/common/useCRNList.ts
@@ -9,6 +9,7 @@ import { CRNSpecs, StreamNotSupportedIssue } from '@/domain/node'
import {
DropdownProps,
useDebounceState,
+ useLocalRequest,
usePaginatedList,
} from '@aleph-front/core'
import {
@@ -18,6 +19,8 @@ import {
import { useDefaultTiers } from './pricing/useDefaultTiers'
import { useRequestCRNLastVersion } from './useRequestEntity/useRequestCRNLastVersion'
import { EntityType } from '@/helpers/constants'
+import { useCostManager } from './useManager/useCostManager'
+import { compareVersion } from '@/helpers/utils'
export type StreamSupportedIssues = Record
@@ -59,17 +62,36 @@ export function useCRNList(props: UseCRNListProps): UseCRNListReturn {
const { enableGpu } = props
const nodeManager = useNodeManager()
+ const costManager = useCostManager()
const { specs: crnSpecs, loading: isLoadingSpecs } = useRequestCRNSpecs()
const { lastVersion, loading: isLoadingLastVersion } =
useRequestCRNLastVersion()
const [isLoadingList, setIsLoadingList] = useState(true)
+ const { data: settingsAggregate, loading: isLoadingSettings } =
+ useLocalRequest({
+ doRequest: () => {
+ return costManager
+ ? costManager.getSettingsAggregate()
+ : Promise.resolve(undefined)
+ },
+ onSuccess: () => null,
+ onError: () => null,
+ flushData: true,
+ triggerOnMount: true,
+ triggerDeps: [costManager],
+ })
+
// -----------------------------
// Loading CRN List
const loading = useMemo(
- () => isLoadingSpecs || isLoadingLastVersion || isLoadingList,
- [isLoadingLastVersion, isLoadingList, isLoadingSpecs],
+ () =>
+ isLoadingSpecs ||
+ isLoadingLastVersion ||
+ isLoadingList ||
+ isLoadingSettings,
+ [isLoadingLastVersion, isLoadingList, isLoadingSpecs, isLoadingSettings],
)
// -----------------------------
@@ -228,16 +250,26 @@ export function useCRNList(props: UseCRNListProps): UseCRNListReturn {
// -----------------------------
- const validPAYGNodes = useMemo(() => {
+ const validCreditNodes = useMemo(() => {
if (!baseFilteredNodes) return
if (!nodesIssues) return baseFilteredNodes
+ if (!settingsAggregate?.lastCrnVersion) return baseFilteredNodes
+
+ const minVersion = settingsAggregate.lastCrnVersion
- return baseFilteredNodes.filter((node) => !nodesIssues[node.hash])
- }, [baseFilteredNodes, nodesIssues])
+ return baseFilteredNodes.filter((node) => {
+ if (nodesIssues[node.hash]) return false
+
+ const nodeVersion = node.version || ''
+ if (!nodeVersion) return false
+
+ return compareVersion(nodeVersion, minVersion)
+ })
+ }, [baseFilteredNodes, nodesIssues, settingsAggregate])
const filteredNodes = useMemo(() => {
try {
- return validPAYGNodes?.filter((node) => {
+ return validCreditNodes?.filter((node) => {
if (gpuFilter) {
if (node.selectedGpu?.model !== gpuFilter) return false
}
@@ -261,7 +293,7 @@ export function useCRNList(props: UseCRNListProps): UseCRNListReturn {
} finally {
setIsLoadingList(false)
}
- }, [gpuFilter, cpuFilter, hddFilter, ramFilter, validPAYGNodes])
+ }, [gpuFilter, cpuFilter, hddFilter, ramFilter, validCreditNodes])
const sortedNodes = useMemo(() => {
if (!filteredNodes) return
From a7500086e113f82ea72d0a02c18b69e3cbb369ad Mon Sep 17 00:00:00 2001
From: gmolki
Date: Wed, 8 Oct 2025 10:45:07 +0200
Subject: [PATCH 39/41] feat: handle cannot start due insufficient credits
---
src/components/common/BorderBox/styles.tsx | 3 +
src/components/common/ExternalLink/cmp.tsx | 4 +-
src/components/common/ExternalLink/styles.ts | 10 +-
src/components/common/ExternalLink/types.ts | 1 +
.../entityData/ManageEntityHeader/cmp.tsx | 328 ++++++++++--------
.../entityData/ManageEntityHeader/types.ts | 7 +
.../console/instance/ManageInstance/cmp.tsx | 5 +
.../console/instance/ManageInstance/hook.ts | 6 +
8 files changed, 222 insertions(+), 142 deletions(-)
diff --git a/src/components/common/BorderBox/styles.tsx b/src/components/common/BorderBox/styles.tsx
index d4334ba33..cc2055719 100644
--- a/src/components/common/BorderBox/styles.tsx
+++ b/src/components/common/BorderBox/styles.tsx
@@ -13,6 +13,8 @@ export const BorderBox = styled.div<{
backdrop-filter: blur(50px);
color: ${theme.color.text}b3;
+ background: linear-gradient(90deg, ${g0}1a 0%, ${g1}1a 100%);
+
&::before {
content: '';
position: absolute;
@@ -33,6 +35,7 @@ export const BorderBox = styled.div<{
-webkit-mask-composite: exclude;
mask-composite: exclude;
-webkit-mask-composite: xor;
+
background-image: linear-gradient(90deg, ${g0} 0%, ${g1} 100%);
}
`
diff --git a/src/components/common/ExternalLink/cmp.tsx b/src/components/common/ExternalLink/cmp.tsx
index a8d15a540..eaf0cf9f5 100644
--- a/src/components/common/ExternalLink/cmp.tsx
+++ b/src/components/common/ExternalLink/cmp.tsx
@@ -9,6 +9,7 @@ export const ExternalLink = ({
color,
typo,
underline,
+ disabled = false,
...props
}: ExternalLinkProps) => {
return (
@@ -19,10 +20,11 @@ export const ExternalLink = ({
$color={color}
$typo={typo}
$underline={underline}
+ $disabled={disabled}
{...props}
>
{text ? text : href}
-
+
>
)
diff --git a/src/components/common/ExternalLink/styles.ts b/src/components/common/ExternalLink/styles.ts
index 3ee278683..f3208001f 100644
--- a/src/components/common/ExternalLink/styles.ts
+++ b/src/components/common/ExternalLink/styles.ts
@@ -6,12 +6,20 @@ export type StyledExternalLinkProps = {
$color?: keyof CoreTheme['color']
$typo?: keyof CoreTheme['typo']
$underline?: boolean
+ $disabled: boolean
}
export const StyledExternalLink = styled.a`
- ${({ theme, $color = 'white', $typo, $underline = false }) => css`
+ ${({ theme, $color = 'white', $typo, $underline = false, $disabled }) => css`
color: ${theme.color[$color]};
text-decoration: ${$underline ? 'underline' : 'none'};
${$typo ? getTypoCss($typo) : ''}
+
+ ${$disabled &&
+ css`
+ // pointer-events: none;
+ cursor: not-allowed;
+ color: ${theme.color.disabled};
+ `};
`}
`
diff --git a/src/components/common/ExternalLink/types.ts b/src/components/common/ExternalLink/types.ts
index 5c3802da7..6607c9cac 100644
--- a/src/components/common/ExternalLink/types.ts
+++ b/src/components/common/ExternalLink/types.ts
@@ -7,4 +7,5 @@ export type ExternalLinkProps = AnchorHTMLAttributes & {
color?: keyof CoreTheme['color']
typo?: keyof CoreTheme['typo']
underline?: boolean
+ disabled?: boolean
}
diff --git a/src/components/common/entityData/ManageEntityHeader/cmp.tsx b/src/components/common/entityData/ManageEntityHeader/cmp.tsx
index 4cdc011a1..2b0c22dc9 100644
--- a/src/components/common/entityData/ManageEntityHeader/cmp.tsx
+++ b/src/components/common/entityData/ManageEntityHeader/cmp.tsx
@@ -1,4 +1,4 @@
-import React, { memo } from 'react'
+import React, { memo, useMemo } from 'react'
import { Button, Icon, Tooltip } from '@aleph-front/core'
import { ManageEntityHeaderProps } from './types'
import BackButton from '../../BackButton'
@@ -6,6 +6,9 @@ import { useTheme } from 'styled-components'
import Skeleton from '../../Skeleton'
import EntityStatus from '../EntityStatus'
import { RotatingLines } from 'react-loader-spinner'
+import { PaymentType } from '@aleph-sdk/message'
+import BorderBox from '../../BorderBox'
+import ExternalLink from '../../ExternalLink'
export const ManageEntityHeader = ({
// Basic data
@@ -17,6 +20,12 @@ export const ManageEntityHeader = ({
// Status
calculatedStatus,
+ // Credit balance
+ creditBalance,
+
+ // Payment data
+ paymentData,
+
// Stop action
showStop = false,
stopDisabled,
@@ -47,153 +56,192 @@ export const ManageEntityHeader = ({
}: ManageEntityHeaderProps) => {
const theme = useTheme()
+ const cannotStart = useMemo(() => {
+ const isStoppedOrNotAllocated =
+ !isAllocated || calculatedStatus === 'stopped'
+
+ if (!isStoppedOrNotAllocated) return false
+
+ // Find credit payment to get the hourly cost
+ const creditPayment = paymentData?.find(
+ (payment) => payment.paymentType === PaymentType.credit,
+ )
+
+ if (!creditPayment?.cost) return false
+
+ const hourlyCost = creditPayment.cost
+ const minimumHours = 4
+ const requiredCredits = hourlyCost * minimumHours
+
+ const hasInsufficientCredits =
+ !creditBalance || creditBalance < requiredCredits
+
+ return hasInsufficientCredits
+ }, [isAllocated, calculatedStatus, paymentData, creditBalance])
+
return (
-
-
-
-
-
-
-
-
- {entity ? name :
}
+ <>
+
+
-
- {showStop && (
-
-
+
+
+ {entity ? name : }
+
+
+
+ {showStop && (
+
- {stopLoading ? (
-
- ) : (
-
- )}
-
-
- )}
-
- {showStart && (
-
-
+ {stopLoading ? (
+
+ ) : (
+
+ )}
+
+
+ )}
+
+ {showStart && (
+
- {startLoading ? (
-
- ) : (
-
- )}
-
-
- )}
-
- {showReboot && (
-
-
+ {startLoading ? (
+
+ ) : (
+
+ )}
+
+
+ )}
+
+ {showReboot && (
+
- {rebootLoading ? (
-
- ) : (
-
- )}
-
-
- )}
-
- {showDownload && (
-
-
+ {rebootLoading ? (
+
+ ) : (
+
+ )}
+
+
+ )}
+
+ {showDownload && (
+
- {downloadLoading ? (
-
- ) : (
-
- )}
-
-
- )}
-
- {showDelete && (
-
-
+ {downloadLoading ? (
+
+ ) : (
+
+ )}
+
+
+ )}
+
+ {showDelete && (
+
- {deleteLoading ? (
-
- ) : (
-
- )}
-
-
- )}
+
+ {deleteLoading ? (
+
+ ) : (
+
+ )}
+
+
+ )}
+
-
-
+
+ {cannotStart && (
+
+
+
+ ⛔️ This resource can't be started due to
+ insufficiency of credits.
+
+
+ {' '}
+ your wallet to reactivate your instance again.
+
+
+
+ )}
+ >
)
}
ManageEntityHeader.displayName = 'ManageEntityHeader'
diff --git a/src/components/common/entityData/ManageEntityHeader/types.ts b/src/components/common/entityData/ManageEntityHeader/types.ts
index d175c557c..4567e9727 100644
--- a/src/components/common/entityData/ManageEntityHeader/types.ts
+++ b/src/components/common/entityData/ManageEntityHeader/types.ts
@@ -1,4 +1,5 @@
import { Executable, ExecutableCalculatedStatus } from '@/domain/executable'
+import { PaymentData } from '@/components/common/entityData/EntityPayment/types'
export type ManageEntityHeaderProps = {
name: string
@@ -8,6 +9,12 @@ export type ManageEntityHeaderProps = {
calculatedStatus: ExecutableCalculatedStatus
+ // Credit balance
+ creditBalance?: number
+
+ // Payment data
+ paymentData?: PaymentData[]
+
// Stop action
showStop?: boolean
stopDisabled?: boolean
diff --git a/src/components/pages/console/instance/ManageInstance/cmp.tsx b/src/components/pages/console/instance/ManageInstance/cmp.tsx
index 926daf55b..4f644694b 100644
--- a/src/components/pages/console/instance/ManageInstance/cmp.tsx
+++ b/src/components/pages/console/instance/ManageInstance/cmp.tsx
@@ -93,6 +93,9 @@ export default function ManageInstance() {
ports,
sshForwardedPort,
handlePortsChange,
+
+ // Credit balance
+ creditBalance,
} = useManageInstance()
return (
@@ -110,6 +113,8 @@ export default function ManageInstance() {
type={EntityType.Instance}
isAllocated={isAllocated}
calculatedStatus={calculatedStatus}
+ creditBalance={creditBalance}
+ paymentData={paymentData}
// Start action
showStart
startDisabled={startDisabled}
diff --git a/src/components/pages/console/instance/ManageInstance/hook.ts b/src/components/pages/console/instance/ManageInstance/hook.ts
index bac07d288..617b8760b 100644
--- a/src/components/pages/console/instance/ManageInstance/hook.ts
+++ b/src/components/pages/console/instance/ManageInstance/hook.ts
@@ -10,6 +10,7 @@ import {
import { useForwardedPorts } from '@/hooks/common/useForwardedPorts'
import { getSSHForwardedPort } from '@/components/common/entityData/EntityPortForwarding/utils'
import { ForwardedPort } from '@/components/common/entityData/EntityPortForwarding/types'
+import { useAppState } from '@/contexts/appState'
export type UseManageInstanceReturn = UseManageInstanceEntityReturn & {
instance?: Instance
@@ -17,12 +18,16 @@ export type UseManageInstanceReturn = UseManageInstanceEntityReturn & {
ports: ForwardedPort[]
sshForwardedPort?: string
handlePortsChange: (ports: ForwardedPort[]) => void
+ creditBalance?: number
}
export function useManageInstance(): UseManageInstanceReturn {
const router = useRouter()
const { hash } = router.query
+ const [state] = useAppState()
+ const { creditBalance } = state.connection
+
const { entities } = useRequestInstances({ ids: hash as string })
const [instance] = entities || []
@@ -68,6 +73,7 @@ export function useManageInstance(): UseManageInstanceReturn {
ports,
sshForwardedPort,
handlePortsChange,
+ creditBalance,
...manageInstanceEntityProps,
}
}
From 1b8d96ff8a893a4d1f1a3cd6e380c6e385a61464 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Fri, 10 Oct 2025 14:59:29 +0200
Subject: [PATCH 40/41] refactor: show stopped on cannot start
---
.../common/entityData/EntityStatus/cmp.tsx | 25 +++++++++++++++----
.../common/entityData/EntityStatus/types.ts | 2 ++
.../entityData/ManageEntityHeader/cmp.tsx | 5 ++--
3 files changed, 24 insertions(+), 8 deletions(-)
diff --git a/src/components/common/entityData/EntityStatus/cmp.tsx b/src/components/common/entityData/EntityStatus/cmp.tsx
index 190ccc89e..df540e64e 100644
--- a/src/components/common/entityData/EntityStatus/cmp.tsx
+++ b/src/components/common/entityData/EntityStatus/cmp.tsx
@@ -7,8 +7,14 @@ import {
} from './types'
import { RotatingLines } from 'react-loader-spinner'
-const EntityStatusV2 = ({ theme, calculatedStatus }: EntityStatusPropsV2) => {
+const EntityStatusV2 = ({
+ theme,
+ calculatedStatus,
+ cannotStart,
+}: EntityStatusPropsV2) => {
const labelVariant = useMemo(() => {
+ if (cannotStart) return 'error'
+
switch (calculatedStatus) {
case 'not-allocated':
return 'warning'
@@ -21,9 +27,11 @@ const EntityStatusV2 = ({ theme, calculatedStatus }: EntityStatusPropsV2) => {
case 'preparing':
return 'warning'
}
- }, [calculatedStatus])
+ }, [calculatedStatus, cannotStart])
const text = useMemo(() => {
+ if (cannotStart) return 'STOPPED'
+
switch (calculatedStatus) {
case 'not-allocated':
return 'NOT ALLLOCATED'
@@ -36,9 +44,11 @@ const EntityStatusV2 = ({ theme, calculatedStatus }: EntityStatusPropsV2) => {
case 'preparing':
return 'PREPARING'
}
- }, [calculatedStatus])
+ }, [calculatedStatus, cannotStart])
const showSpinner = useMemo(() => {
+ if (cannotStart) return false
+
switch (calculatedStatus) {
case 'not-allocated':
return false
@@ -51,7 +61,7 @@ const EntityStatusV2 = ({ theme, calculatedStatus }: EntityStatusPropsV2) => {
case 'preparing':
return true
}
- }, [calculatedStatus])
+ }, [calculatedStatus, cannotStart])
return (
@@ -100,6 +110,7 @@ export const EntityStatus = ({
entity,
isAllocated,
calculatedStatus,
+ cannotStart,
theme,
}: EntityStatusProps) => {
if (calculatedStatus === 'loading' || !entity) {
@@ -116,7 +127,11 @@ export const EntityStatus = ({
return calculatedStatus === 'v1' ? (
) : (
-
+
)
}
EntityStatus.displayName = 'EntityStatus'
diff --git a/src/components/common/entityData/EntityStatus/types.ts b/src/components/common/entityData/EntityStatus/types.ts
index f481ad8ba..f7eeb37a8 100644
--- a/src/components/common/entityData/EntityStatus/types.ts
+++ b/src/components/common/entityData/EntityStatus/types.ts
@@ -9,11 +9,13 @@ export type EntityStatusPropsV1 = {
export type EntityStatusPropsV2 = {
calculatedStatus: ExecutableCalculatedStatus
+ cannotStart?: boolean
theme: DefaultTheme
}
export type EntityStatusProps = {
calculatedStatus: ExecutableCalculatedStatus
+ cannotStart?: boolean
entity?: Executable
isAllocated: boolean
theme: DefaultTheme
diff --git a/src/components/common/entityData/ManageEntityHeader/cmp.tsx b/src/components/common/entityData/ManageEntityHeader/cmp.tsx
index 2b0c22dc9..a8a905ed5 100644
--- a/src/components/common/entityData/ManageEntityHeader/cmp.tsx
+++ b/src/components/common/entityData/ManageEntityHeader/cmp.tsx
@@ -92,6 +92,7 @@ export const ManageEntityHeader = ({
entity={entity}
isAllocated={isAllocated}
theme={theme}
+ cannotStart={cannotStart}
/>
{entity ? name :
}
@@ -232,9 +233,7 @@ export const ManageEntityHeader = ({
⛔️ This resource can't be started due to
- insufficiency of credits.
-
-
+ insufficiency of credits.{' '}
{' '}
your wallet to reactivate your instance again.
From 4da16b277f5a8fe39c841cbb60e49d71455fac43 Mon Sep 17 00:00:00 2001
From: gmolki
Date: Fri, 10 Oct 2025 17:30:15 +0200
Subject: [PATCH 41/41] production variables
---
src/domain/cost.ts | 3 +--
src/helpers/constants.ts | 7 +++----
2 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/src/domain/cost.ts b/src/domain/cost.ts
index 5a6ea37c0..6952c7288 100644
--- a/src/domain/cost.ts
+++ b/src/domain/cost.ts
@@ -94,8 +94,7 @@ export class CostManager {
) {}
protected static readonly aggregateSourceAddress =
- // '0xFba561a84A537fCaa567bb7A2257e7142701ae2A'
- '0xA07B1214bAe0D5ccAA25449C3149c0aC83658874'
+ '0xFba561a84A537fCaa567bb7A2257e7142701ae2A'
async getSettingsAggregate(): Promise {
const response = await this.sdkClient.fetchAggregate(
diff --git a/src/helpers/constants.ts b/src/helpers/constants.ts
index bae6334e1..b1ea333bc 100644
--- a/src/helpers/constants.ts
+++ b/src/helpers/constants.ts
@@ -9,8 +9,8 @@ export const channel = 'FOUNDATION'
export const tags = ['mainnet']
export const postType = 'corechan-operation'
-export const apiServer = 'http://51.159.106.166:4024'
-export const wsServer = 'ws://51.159.106.166:4024'
+export const apiServer = 'https://api.aleph.im'
+export const wsServer = 'wss://api.aleph.im'
export const mbPerAleph = 3
export const communityWalletAddress =
@@ -197,8 +197,7 @@ export enum WebsiteFrameworkId {
export const EXTRA_WEI = 3600 / 10 ** 18
-const LEGACY_CONSOLE_DOMAIN =
- 'https://aleph-cloud-explorer-test.gerardmolina.com/console'
+const LEGACY_CONSOLE_DOMAIN = 'https://app.aleph.cloud/console'
export const NAVIGATION_URLS = {
legacyConsole: {
home: `${LEGACY_CONSOLE_DOMAIN}`,