Skip to content

Commit

Permalink
feat: hostd onboarding
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfreska committed Oct 16, 2023
1 parent ccf0bdc commit 5d60356
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/chatty-panthers-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'hostd': minor
---

New users will now see an onboarding wizard that prompts the user to complete the necessary setup steps - it also shows the status and progress of each.
264 changes: 264 additions & 0 deletions apps/hostd/components/OnboardingBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
import {
Button,
Link,
Logo,
Panel,
ScrollArea,
Text,
Tooltip,
} from '@siafoundation/design-system'
import {
RadioButton16,
CheckmarkFilled16,
Launch16,
PendingFilled16,
Subtract24,
} from '@siafoundation/react-icons'
import { useState } from 'react'
import { useSyncStatus } from '../hooks/useSyncStatus'
import { routes } from '../config/routes'
import { useDialog } from '../contexts/dialog'
import { useSettings, useWallet } from '@siafoundation/react-hostd'
import BigNumber from 'bignumber.js'
import { humanSiacoin, toHastings } from '@siafoundation/sia-js'
import { useAppSettings } from '@siafoundation/react-core'
import { useVolumes } from '../contexts/volumes'

export function OnboardingBar() {
const { isUnlocked } = useAppSettings()
const { openDialog } = useDialog()
const { dataset: volumes } = useVolumes()
const settings = useSettings()
const wallet = useWallet()
const [maximized, setMaximized] = useState<boolean>(true)
const syncStatus = useSyncStatus()

if (!isUnlocked) {
return null
}

const walletBalance = new BigNumber(wallet.data?.confirmed || 0)
const minimumBalance = toHastings(5_000)

const step1Funded = wallet.data && walletBalance.gte(minimumBalance)
const step2Volumes = volumes?.length > 0
const step3Configured = settings.data?.acceptingContracts
const step4Synced = syncStatus.isSynced
// const step5Announced = false
const steps = [
step1Funded,
step2Volumes,
step3Configured,
step4Synced,
//step5Announced
]
const totalSteps = steps.length
const completedSteps = steps.filter((step) => step).length

if (totalSteps === completedSteps) {
return null
}

if (maximized) {
return (
<div className="z-20 fixed bottom-5 left-1/2 -translate-x-1/2 flex justify-center">
<Panel className="w-[400px] flex flex-col max-h-[600px]">
<ScrollArea>
<div className="flex justify-between items-center px-3 py-2 border-b border-gray-200 dark:border-graydark-300">
<div className="flex gap-2 items-center">
<Logo />
<Text size="20" weight="semibold">
Welcome to Sia
</Text>
</div>
<Button variant="ghost" onClick={() => setMaximized(false)}>
<Subtract24 />
</Button>
</div>
<div className="flex justify-between items-center px-3 py-2 border-b border-gray-200 dark:border-graydark-300">
<Text size="14">
Get set up by completing the following steps. Once they are
complete, your host is ready to store data.
</Text>
</div>
<Section
title={
<Link
href={routes.wallet.view}
onClick={() => openDialog('addressDetails')}
ellipsis
size="14"
underline="hover"
>
Step 1: Fund your wallet
</Link>
}
description={`Fund your wallet with at least ${humanSiacoin(
minimumBalance
)} siacoin to cover required contract collateral.${
syncStatus.isWalletSynced
? ''
: ' Balance will not be accurate until wallet is finished scanning.'
}`}
action={
step1Funded ? (
<Text color="green">
<CheckmarkFilled16 />
</Text>
) : (
<>
{!syncStatus.isWalletSynced && (
<Tooltip
content={`Wallet scanning progress ${syncStatus.walletScanPercent}%`}
>
<Text size="14">{syncStatus.walletScanPercent}%</Text>
</Tooltip>
)}
<Link
href={routes.wallet.view}
onClick={() => openDialog('addressDetails')}
>
<Launch16 />
</Link>
<Text color="amber">
<RadioButton16 />
</Text>
</>
)
}
/>
<Section
title={
<Link
href={routes.volumes.index}
ellipsis
size="14"
underline="hover"
>
Step 2: Add a volume
</Link>
}
description={
'Add a system volume that will be used to store data.'
}
action={
step2Volumes ? (
<Text color="green">
<CheckmarkFilled16 />
</Text>
) : (
<>
<Link href={routes.volumes.index}>
<Launch16 />
</Link>
<Text color="amber">
<RadioButton16 />
</Text>
</>
)
}
/>
<Section
title={
<Link
href={routes.config.index}
ellipsis
size="14"
underline="hover"
>
Step 3: Configure pricing and settings
</Link>
}
description={`Configure your host's pricing and settings and start accepting contracts.`}
action={
step3Configured ? (
<Text color="green">
<CheckmarkFilled16 />
</Text>
) : (
<>
<Link href={routes.config.index}>
<Launch16 />
</Link>
<Text color="amber">
<RadioButton16 />
</Text>
</>
)
}
/>
<Section
title={
<Link
href={routes.node.index}
underline="hover"
ellipsis
size="14"
>
Step 4: Wait for the blockchain to sync
</Link>
}
description={
'The blockchain will sync in the background, this takes some time. No user action required.'
}
action={
step4Synced ? (
<Text color="green">
<CheckmarkFilled16 />
</Text>
) : (
<>
<Text ellipsis size="14">
{syncStatus.syncPercent}%
</Text>
<Text color="amber">
<PendingFilled16 />
</Text>
</>
)
}
/>
</ScrollArea>
</Panel>
</div>
)
}
return (
<div className="z-30 fixed bottom-5 left-1/2 -translate-x-1/2 flex justify-center">
<Button
onClick={() => setMaximized(true)}
size="large"
className="flex gap-3 !px-3"
>
<Text className="flex items-center gap-1">
<Logo />
Setup: {completedSteps}/{totalSteps} steps complete
</Text>
</Button>
</div>
)
}

type SectionProps = {
title: React.ReactNode
action: React.ReactNode
description: React.ReactNode
}

function Section({ title, action, description }: SectionProps) {
return (
<div className="border-t first:border-t-0 border-gray-200 dark:border-graydark-300 px-3 py-2">
<div className="flex flex-col gap-1">
<div className="flex gap-2 items-center">
<div className="flex-1 flex items-center">{title}</div>
{action}
</div>
<div className="pr-5">
<Text size="12" color="subtle">
{description}
</Text>
</div>
</div>
</div>
)
}
2 changes: 2 additions & 0 deletions apps/hostd/config/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { MetricsProvider } from '../contexts/metrics'
import { DialogProvider, Dialogs } from '../contexts/dialog'
import { VolumesProvider } from '../contexts/volumes'
import { ConfigProvider } from '../contexts/config'
import { OnboardingBar } from '../components/OnboardingBar'

type Props = {
children: React.ReactNode
Expand All @@ -18,6 +19,7 @@ export function Providers({ children }: Props) {
{/* this is here so that dialogs can use all the other providers,
and the other providers can trigger dialogs */}
<Dialogs />
<OnboardingBar />
{children}
</MetricsProvider>
</ContractsProvider>
Expand Down
4 changes: 4 additions & 0 deletions apps/renterd/config/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { HostsProvider } from '../contexts/hosts'
import React from 'react'
import { AppProvider } from '../contexts/app'
import { ConfigProvider } from '../contexts/config'
import { OnboardingBar } from '../components/OnboardingBar'
import { TransfersBar } from '../components/TransfersBar'

type Props = {
children: React.ReactNode
Expand All @@ -20,6 +22,8 @@ export function Providers({ children }: Props) {
<FilesProvider>
{/* this is here so that dialogs can use all the other providers,
and the other providers can trigger dialogs */}
<OnboardingBar />
<TransfersBar />
<Dialogs />
{children}
</FilesProvider>
Expand Down
10 changes: 1 addition & 9 deletions apps/renterd/contexts/files/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
} from '@siafoundation/design-system'
import { useRouter } from 'next/router'
import { createContext, useCallback, useContext, useMemo } from 'react'
import { TransfersBar } from '../../components/TransfersBar'
import { columns } from './columns'
import {
defaultSortField,
Expand All @@ -18,7 +17,6 @@ import { FullPath, FullPathSegments, pathSegmentsToPath } from './paths'
import { useUploads } from './uploads'
import { useDownloads } from './downloads'
import { useDataset } from './dataset'
import { OnboardingBar } from '../../components/OnboardingBar'

function useFilesMain() {
const router = useRouter()
Expand Down Expand Up @@ -184,11 +182,5 @@ type Props = {

export function FilesProvider({ children }: Props) {
const state = useFilesMain()
return (
<FilesContext.Provider value={state}>
{children}
<OnboardingBar />
<TransfersBar />
</FilesContext.Provider>
)
return <FilesContext.Provider value={state}>{children}</FilesContext.Provider>
}

0 comments on commit 5d60356

Please sign in to comment.