Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hostd onboarding #389

Merged
merged 1 commit into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
}