Skip to content

Commit

Permalink
feat(console): App storage package modal (#2836)
Browse files Browse the repository at this point in the history
  • Loading branch information
Cosmin-Parvulescu committed Feb 12, 2024
1 parent 91ae0e6 commit ea8f515
Show file tree
Hide file tree
Showing 7 changed files with 338 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { Modal } from '@proofzero/design-system/src/molecules/modal/Modal'
import { Button, Text } from '@proofzero/design-system'
import { FetcherWithComponents } from '@remix-run/react'

import ExternalAppDataPackages from '@proofzero/utils/externalAppDataPackages'
import { ExternalAppDataPackageType } from '@proofzero/types/billing'
import { useEffect, useState } from 'react'
import { HiOutlineX } from 'react-icons/hi'
import { InputToggle } from '@proofzero/design-system/src/atoms/form/InputToggle'
import { FaCheck, FaTimes } from 'react-icons/fa'

type AppDataStorageModalProps = {
isOpen: boolean
onClose: () => void
subscriptionFetcher: FetcherWithComponents<any>
currentPackage?: ExternalAppDataPackageType
topUp?: boolean
clientID: string
}

const AppDataStorageModal: React.FC<AppDataStorageModalProps> = ({
isOpen,
onClose,
subscriptionFetcher,
currentPackage,
topUp = false,
clientID,
}) => {
const [selectedPackage, setSelectedPackage] =
useState<ExternalAppDataPackageType>(
currentPackage ?? ExternalAppDataPackageType.STARTER
)
const [autoTopUp, setAutoTopUp] = useState(topUp)

useEffect(() => {
if (currentPackage) {
setSelectedPackage(currentPackage)
}
}, [currentPackage])
return (
<>
<Modal isOpen={isOpen} handleClose={() => onClose()}>
<subscriptionFetcher.Form
method="post"
action={`/apps/${clientID}/storage/ostrich`}
className="flex flex-col gap-4 p-5"
>
{selectedPackage && <input type="hidden" name="op" value="enable" />}
{selectedPackage && (
<input type="hidden" name="package" value={selectedPackage} />
)}
<header className="flex flex-row justify-between w-full mb-3 items-center">
<Text
size="lg"
weight="semibold"
className="text-left text-gray-800"
>
Purchase Entitlement(s)
</Text>
<button
className="bg-white p-2 rounded-lg text-xl cursor-pointer hover:bg-[#F3F4F6]"
onClick={() => onClose()}
>
<HiOutlineX />
</button>
</header>

<section className="flex flex-col gap-4">
<div>
<Text
size="xs"
className="text-gray-500 uppercase text-left mb-2"
>
Choose Package
</Text>
<div className="text-left flex flex-row items-center gap-1.5">
<Button
disabled={subscriptionFetcher.state !== 'idle'}
btnType={
selectedPackage === ExternalAppDataPackageType.STARTER
? 'secondary'
: 'secondary-alt'
}
onClick={() => {
setSelectedPackage(ExternalAppDataPackageType.STARTER)
}}
>
{
ExternalAppDataPackages[ExternalAppDataPackageType.STARTER]
.title
}
</Button>
<Button
disabled={subscriptionFetcher.state !== 'idle'}
btnType={
selectedPackage === ExternalAppDataPackageType.SCALE
? 'secondary'
: 'secondary-alt'
}
onClick={() => {
setSelectedPackage(ExternalAppDataPackageType.SCALE)
}}
>
{
ExternalAppDataPackages[ExternalAppDataPackageType.SCALE]
.title
}
</Button>
</div>
</div>

<div className="flex flex-col border rounded">
<div className="flex flex-row items-center justify-between px-4 py-2">
<Text size="sm" weight="medium" className="text-gray-800">
Reads:
</Text>
<Text size="sm" weight="medium" className="text-gray-500">
{ExternalAppDataPackages[selectedPackage].reads}
</Text>
</div>
<div className="w-full h-px bg-gray-200"></div>
<div className="flex flex-row items-center justify-between px-4 py-2">
<Text size="sm" weight="medium" className="text-gray-800">
Writes:
</Text>
<Text size="sm" weight="medium" className="text-gray-500">
{ExternalAppDataPackages[selectedPackage].writes}
</Text>
</div>
</div>
</section>

<section className="flex flex-col">
<Text size="xs" className="text-gray-500 uppercase text-left mb-2">
Auto Top-Up
</Text>

<div className="flex flex-row items-start gap-2 border rounded px-4 py-2">
<Text
className="flex-1 text-gray-500 max-w-[638px] text-left"
size="sm"
>
When enabled it allows for automatic package purchasing to
prevent stopping services from running if you ever ran out of
units. The unused value of the top-up carries over to the next
billing cycle.
</Text>

<InputToggle
id="top-up"
checked={autoTopUp}
onToggle={(val) => setAutoTopUp(val)}
disabled={subscriptionFetcher.state !== 'idle'}
/>
</div>
</section>

<section className="flex flex-row border rounded px-4 py-2 justify-between items-center">
<Text size="sm" className="text-gray-800">
Changes to your subscription
</Text>

<div className="flex flex-col gap-2">
<div className="flex flex-row justify-between items-center">
<span>
{autoTopUp ? (
<FaCheck className="text-green-500" />
) : (
<FaTimes className="text-red-500" />
)}
</span>
<Text type="span" size="sm" className="text-gray-800">
Auto top-up
</Text>
</div>
</div>
</section>

<footer className="flex flex-row items-center justify-end gap-2">
<Button btnType="secondary-alt" onClick={() => onClose()}>
Close
</Button>
<Button
btnType="primary-alt"
type="submit"
disabled={subscriptionFetcher.state !== 'idle'}
>
Change Subscription
</Button>
</footer>
</subscriptionFetcher.Form>
</Modal>
</>
)
}

export default AppDataStorageModal
145 changes: 122 additions & 23 deletions apps/console/app/routes/apps/$clientId/storage.ostrich.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { Form, useOutletContext, useTransition } from '@remix-run/react'
import {
Form,
useFetcher,
useOutletContext,
useTransition,
} from '@remix-run/react'
import { Button, Text } from '@proofzero/design-system'
import { DocumentationBadge } from '~/components/DocumentationBadge'
import { getRollupReqFunctionErrorWrapper } from '@proofzero/utils/errors'
Expand All @@ -12,16 +17,21 @@ import classNames from 'classnames'
import { appDetailsProps } from '~/types'
import { ExternalAppDataPackageType } from '@proofzero/types/billing'
import {
HiDotsVertical,
HiOutlinePencilAlt,
HiOutlineShoppingCart,
HiOutlineTrash,
HiOutlineX,
} from 'react-icons/hi'
import { ExternalAppDataPackageStatus } from '@proofzero/platform.starbase/src/jsonrpc/validators/externalAppDataPackageDefinition'
import { Spinner } from '@proofzero/design-system/src/atoms/spinner/Spinner'
import { useState } from 'react'
import { Fragment, useEffect, useState } from 'react'
import { Modal } from '@proofzero/design-system/src/molecules/modal/Modal'
import dangerVector from '~/images/danger.svg'
import { Input } from '@proofzero/design-system/src/atoms/form/Input'
import AppDataStorageModal from '~/components/AppDataStorageModal/AppDataStorageModal'
import { Menu, Transition } from '@headlessui/react'
import { Loader } from '@proofzero/design-system/src/molecules/loader/Loader'

export const action: ActionFunction = getRollupReqFunctionErrorWrapper(
async ({ request, context, params }) => {
Expand All @@ -42,9 +52,12 @@ export const action: ActionFunction = getRollupReqFunctionErrorWrapper(
const fd = await request.formData()
switch (fd.get('op')) {
case 'enable':
const packageType = fd.get('package') as ExternalAppDataPackageType
const autoTopUp = fd.get('top-up') !== '0'
await coreClient.starbase.setExternalAppDataPackage.mutate({
clientId,
packageType: ExternalAppDataPackageType.STARTER,
packageType,
autoTopUp,
})
break
case 'disable':
Expand Down Expand Up @@ -157,13 +170,39 @@ export default () => {
appDetails: appDetailsProps
}>()

const [isModalOpen, setIsModalOpen] = useState(false)
const [isCancelModalOpen, setIsCancelModalOpen] = useState(false)
const [isSubscriptionModalOpen, setIsSubscriptionModalOpen] = useState(false)

const fetcher = useFetcher()
useEffect(() => {
if (fetcher.state === 'idle' && fetcher.type === 'done') {
setIsSubscriptionModalOpen(false)
}
}, [fetcher])

return (
<>
{isModalOpen && (
<ConfirmCancelModal isOpen={isModalOpen} setIsOpen={setIsModalOpen} />
{fetcher.state !== 'idle' && <Loader />}
{isCancelModalOpen && (
<ConfirmCancelModal
isOpen={isCancelModalOpen}
setIsOpen={setIsCancelModalOpen}
/>
)}
{isSubscriptionModalOpen && (
<AppDataStorageModal
isOpen={isSubscriptionModalOpen}
onClose={() => setIsSubscriptionModalOpen(false)}
subscriptionFetcher={fetcher}
clientID={appDetails.clientId!}
currentPackage={
appDetails.externalAppDataPackageDefinition?.packageDetails
.packageType
}
topUp={appDetails.externalAppDataPackageDefinition?.autoTopUp}
/>
)}

<section className="flex flex-col space-y-5">
<div className="flex flex-row items-center space-x-3">
<Text size="2xl" weight="semibold" className="text-gray-900">
Expand Down Expand Up @@ -213,31 +252,91 @@ export default () => {
ExternalAppDataPackageStatus.Deleting && (
<>
{!Boolean(appDetails.externalAppDataPackageDefinition) && (
<Form method="post">
<input type="hidden" name="op" value="enable" />
<Button
btnType="primary-alt"
className="flex flex-row items-center gap-3"
type="submit"
>
<HiOutlineShoppingCart className="w-3.5 h-3.5" />
<Text>Purchase Package</Text>
</Button>
</Form>
)}
{Boolean(appDetails.externalAppDataPackageDefinition) && (
<Button
btnType="dangerous-alt"
btnType="primary-alt"
className="flex flex-row items-center gap-3"
type="submit"
onClick={() => {
setIsModalOpen(true)
setIsSubscriptionModalOpen(true)
}}
>
<HiOutlineTrash className="w-3.5 h-3.5" />
<Text>Cancel Service</Text>
<HiOutlineShoppingCart className="w-3.5 h-3.5" />
<Text>Purchase Package</Text>
</Button>
)}
{Boolean(appDetails.externalAppDataPackageDefinition) && (
<Menu>
<Menu.Button>
<div
className="w-8 h-8 flex justify-center items-center cursor-pointer
hover:bg-gray-100 hover:rounded-[6px]"
>
<HiDotsVertical className="text-lg text-gray-400" />
</div>
</Menu.Button>

<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
className="absolute z-10 right-0 mt-2 w-56 origin-top-right divide-y divide-gray-100
rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none divide-y
divide-gray-100"
>
<div className="p-1 ">
<div
onClick={() => {
setIsSubscriptionModalOpen(true)
}}
className="cursor-pointer"
>
<Menu.Item
as="div"
className="py-2 px-4 flex items-center space-x-3 cursor-pointer
hover:rounded-[6px] hover:bg-gray-100"
>
<HiOutlinePencilAlt className="text-xl font-normal text-gray-400" />
<Text
size="sm"
weight="normal"
className="text-gray-700"
>
Edit Package
</Text>
</Menu.Item>
</div>
</div>

<div className="p-1">
<Menu.Item
as="div"
className="py-2 px-4 flex items-center space-x-3 cursor-pointer
hover:rounded-[6px] hover:bg-gray-100 "
onClick={() => {
setIsCancelModalOpen(true)
}}
>
<HiOutlineTrash className="text-xl font-normal text-red-500" />

<Text
size="sm"
weight="normal"
className="text-red-500"
>
Cancel Service
</Text>
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
)}
</>
)}
</section>
Expand Down
Loading

0 comments on commit ea8f515

Please sign in to comment.