Skip to content

Commit

Permalink
feat: add a toaster for notifications and other snacks (storacha#91)
Browse files Browse the repository at this point in the history
Use `react-hot-toast` to introduce "toaster" style notifications:

https://react-hot-toast.com/

These should be refined over time, but as an experiment I've introduced
this on the "plan change" page and am using it to let the user know when
something goes wrong:



https://github.com/web3-storage/console/assets/1113/ffe3c1cf-5a42-4471-920c-649bfecb36a4

or when something goes right:



https://github.com/web3-storage/console/assets/1113/af7f5bd2-cf20-482c-803c-22d5804ed86a

If this seems like a good pattern to everyone I think we should start
using it more broadly - no longer will our users be forced to watch a
whole file upload just to get feedback about its success or failure!
  • Loading branch information
Travis Vachon authored Apr 18, 2024
1 parent a6d75ae commit 1906d39
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 3 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"next": "^13.5.4",
"react": "latest",
"react-dom": "latest",
"react-hot-toast": "^2.4.1",
"swr": "^2.2.4"
},
"devDependencies": {
Expand Down
25 changes: 25 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import './globals.css'
import type { Metadata } from 'next'
import Provider from '@/components/W3UIProvider'
import Toaster from '@/components/Toaster'

export const metadata: Metadata = {
title: 'w3up console',
Expand All @@ -18,6 +19,7 @@ export default function RootLayout ({
<Provider>
<>{children}</>
</Provider>
<Toaster />
</body>
</html>
)
Expand Down
7 changes: 6 additions & 1 deletion src/app/plans/change/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { CheckCircleIcon } from '@heroicons/react/24/outline'
import DefaultLoader from "@/components/Loader"
import { useState } from "react"
import SidebarLayout from "@/components/SidebarLayout"
import { ucantoast } from "@/toaster"

interface PlanSectionProps {
planID: DID
Expand Down Expand Up @@ -38,7 +39,11 @@ function PlanSection ({ planID, planName, flatFee, flatFeeAllotment, perGbFee }:
async function selectPlan (selectedPlanID: DID) {
try {
setIsUpdatingPlan(true)
await setPlan(selectedPlanID)
await ucantoast(setPlan(selectedPlanID), {
loading: "Updating plan...",
success: "Plan updated!",
error: "Failed to update plan, check the console for more details."
})
} finally {
setIsUpdatingPlan(false)
}
Expand Down
27 changes: 27 additions & 0 deletions src/components/Toaster.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use client'

import { Toaster, ToastIcon, resolveValue } from "react-hot-toast"
import { Transition } from "@headlessui/react"

export default function TailwindToaster () {
return (
<Toaster position="top-right">
{(t) => (
<Transition
appear
show={t.visible}
className="transform p-4 flex bg-white rounded shadow-lg"
enter="transition-all duration-150"
enterFrom="opacity-0 scale-50"
enterTo="opacity-100 scale-100"
leave="transition-all duration-150"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-75"
>
<ToastIcon toast={t} />
<p className="px-2">{resolveValue(t.message, t)}</p>
</Transition>
)}
</Toaster>
)
}
4 changes: 2 additions & 2 deletions src/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ type UsePlanResult = SWRResponse<PlanGetSuccess | undefined> & {
}

export const usePlan = (account: Account) => {
const { mutate } = useSWRConfig()
const result = useSWR<PlanGetSuccess | undefined>(planKey(account), {
fetcher: async () => {
if (!account) return
Expand All @@ -25,8 +24,9 @@ export const usePlan = (account: Account) => {
// to avoid calling the getters in SWRResponse when copying values over -
// I can't think of a cleaner way to do this but open to refactoring
result.setPlan = async (plan: DID) => {
await account.plan.set(plan)
const setResult = await account.plan.set(plan)
await result.mutate()
return setResult
}
return result as UsePlanResult
}
13 changes: 13 additions & 0 deletions src/toaster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Result } from '@ucanto/interface'
import { toast } from 'react-hot-toast'

export const ucantoast = async (promise: Promise<Result>, options: any) => {
return toast.promise((async () => {
const result = await promise
if (result.ok) {
return result.ok
} else {
throw result.error
}
})(), options)
}

0 comments on commit 1906d39

Please sign in to comment.