console: add neon banner for onboarding wizard

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6003
GitOrigin-RevId: f3556e3b6c9987b7dc139d6185fd909bb87d3fe8
This commit is contained in:
Abhijeet Khangarot 2022-09-22 12:16:34 +05:30 committed by hasura-bot
parent 969cb05bdf
commit a81c028fe2
6 changed files with 250 additions and 2 deletions

View File

@ -1,4 +1,5 @@
import React from 'react';
import { MdRefresh } from 'react-icons/md';
import { ComponentMeta, Story } from '@storybook/react';
import { within } from '@storybook/testing-library';
import { expect } from '@storybook/jest';
@ -64,6 +65,7 @@ export const Error: Story = () => (
status={{
status: 'error',
buttonText: 'Try Again',
buttonIcon: <MdRefresh />,
errorTitle: 'Error creating database',
errorDescription: 'You have exceeded the free project limit on Neon.',
}}

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { ReactElement } from 'react';
import { Button } from '@/new-components/Button';
import { IndicatorCard } from '@/new-components/IndicatorCard';
@ -12,6 +12,7 @@ export type Props = {
| {
status: 'error';
buttonText: string;
buttonIcon: ReactElement;
errorTitle: string;
errorDescription: string;
}
@ -54,10 +55,11 @@ export function NeonBanner(props: Props) {
<Button
data-trackid="neon-connect-db-button"
data-testid="neon-connect-db-button"
mode="primary"
mode={status.status === 'loading' ? 'default' : 'primary'}
isLoading={status.status === 'loading'}
loadingText={status.buttonText}
size="md"
icon={status.status === 'error' ? status.buttonIcon : undefined}
onClick={() => {
if (!isButtonDisabled) {
onClickConnect();

View File

@ -1,5 +1,6 @@
import * as React from 'react';
import { useDispatch } from 'react-redux';
import { MdRefresh } from 'react-icons/md';
import { useNeonOAuth } from './useNeonOAuth';
import { useNeonDatabase } from './useNeonDatabase';
import { useCreateHasuraDatasource } from './useCreateHasuraDatasource';
@ -116,6 +117,7 @@ export function Neon(props: {
status: {
status: 'error',
buttonText: 'Try again',
buttonIcon: <MdRefresh />,
errorTitle: 'Creating Neon Database failed',
errorDescription: `Error creating database: ${neonDBCreationStatus.error}`,
},
@ -169,6 +171,7 @@ export function Neon(props: {
status: {
status: 'error',
buttonText: 'Try again',
buttonIcon: <MdRefresh />,
errorTitle: 'Error authenticating with Neon',
errorDescription: neonOauthStatus.error.message,
},

View File

@ -0,0 +1,85 @@
import React from 'react';
import { MdRefresh } from 'react-icons/md';
import { ComponentMeta, Story } from '@storybook/react';
import { within } from '@storybook/testing-library';
import { expect } from '@storybook/jest';
import { NeonBanner } from './NeonBanner';
export default {
title: 'features/Onboarding Wizard/NeonConnectBanner',
component: NeonBanner,
} as ComponentMeta<typeof NeonBanner>;
export const Base: Story = () => (
<NeonBanner
onButtonClick={() => window.alert('clicked connect button')}
status={{ status: 'default', buttonText: 'Create a Neon Database' }}
/>
);
Base.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Expect element renders successfully
expect(canvas.getByText('Create a Neon Database')).toBeVisible();
expect(
canvas.getByTestId('onboarding-wizard-neon-connect-db-button')
).toBeVisible();
expect(
canvas.getByTestId('onboarding-wizard-neon-connect-db-button')
).not.toBeDisabled();
};
export const Creating: Story = () => (
<NeonBanner
onButtonClick={() => window.alert('clicked connect button')}
status={{ status: 'loading', buttonText: 'Creating Neon Database' }}
/>
);
Creating.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Expect element renders successfully
expect(canvas.getByText('Creating Neon Database')).toBeVisible();
// Expect button disabled state to be as expected
expect(
canvas.getByTestId('onboarding-wizard-neon-connect-db-button')
).toBeVisible();
expect(
canvas.getByTestId('onboarding-wizard-neon-connect-db-button')
).toBeDisabled();
};
export const Error: Story = () => (
<NeonBanner
onButtonClick={() => window.alert('clicked connect button')}
status={{
status: 'error',
buttonText: 'Try Again',
buttonIcon: <MdRefresh />,
errorTitle: 'Your Neon Database connection failed',
errorDescription: 'You have exceeded the free project limit on Neon.',
}}
/>
);
Error.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Expect element renders successfully
expect(canvas.getByText('Try Again')).toBeVisible();
expect(canvas.getByText('Try Again')).not.toBeDisabled();
// Expect element rend
expect(
canvas.getByText('Your Neon Database connection failed')
).toBeVisible();
expect(
canvas.getByText('You have exceeded the free project limit on Neon.')
).toBeVisible();
expect(
canvas.getByTestId('onboarding-wizard-neon-connect-db-button')
).toBeVisible();
expect(
canvas.getByTestId('onboarding-wizard-neon-connect-db-button')
).not.toBeDisabled();
};

View File

@ -0,0 +1,76 @@
import React, { ReactElement } from 'react';
import { Button } from '@/new-components/Button';
import { IndicatorCard } from '@/new-components/IndicatorCard';
import { NeonIcon } from './NeonIcon';
export type Props = {
onButtonClick: VoidFunction;
status:
| {
status: 'loading';
buttonText: string;
}
| {
status: 'error';
buttonText: string;
buttonIcon: ReactElement;
errorTitle: string;
errorDescription: string;
}
| {
status: 'default';
buttonText: string;
};
};
export function NeonBanner(props: Props) {
const { status, onButtonClick } = props;
const isButtonDisabled = status.status === 'loading';
return (
<div className="border border-gray-300 border-l-4 border-l-[#297393] shadow-md rounded bg-white p-md">
<div className="flex items-center">
<div className="flex w-3/4 items-center">
<div className="mr-sm">
<NeonIcon />
</div>
<div className="text-lg text-gray-700 ml-sm">
<b>Need a new database?</b> Hasura has partnered with Neon to help
you seamlessly create your database with their serverless Postgres
platform.
</div>
</div>
<div className="flex w-1/4 justify-end">
<Button
data-trackid="onboarding-wizard-neon-connect-db-button"
data-testid="onboarding-wizard-neon-connect-db-button"
mode={status.status === 'loading' ? 'default' : 'primary'}
isLoading={status.status === 'loading'}
loadingText={status.buttonText}
size="md"
icon={status.status === 'error' ? status.buttonIcon : undefined}
onClick={() => {
if (!isButtonDisabled) {
onButtonClick();
}
}}
disabled={isButtonDisabled}
>
{status.buttonText}
</Button>
</div>
</div>
{status.status === 'error' && (
<div className="mt-md">
<IndicatorCard
status="negative"
headline={status.errorTitle}
showIcon
>
{status.errorDescription}
</IndicatorCard>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,80 @@
import React from 'react';
type NeonIconProps = {
className?: string;
};
export function NeonIcon(props: NeonIconProps) {
const { className } = props;
return (
<svg
width="84"
height="25"
viewBox="0 0 84 25"
fill="none"
className={className}
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_138_19342)">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0 4.79524C0 3.71972 0.429155 2.68826 1.19305 1.92775C1.95695 1.16725 2.99303 0.739998 4.07334 0.739998L19.5517 0.739998C20.632 0.739998 21.668 1.16725 22.4319 1.92775C23.1958 2.68826 23.625 3.71972 23.625 4.79524V17.9011C23.625 20.2178 20.6798 21.2233 19.2518 19.3946L14.786 13.6753V20.6105C14.786 21.5784 14.3998 22.5067 13.7123 23.1911C13.0248 23.8755 12.0924 24.26 11.1202 24.26H4.07334C2.99303 24.26 1.95695 23.8328 1.19305 23.0722C0.429155 22.3117 0 21.2803 0 20.2048L0 4.79524ZM4.07334 3.98445C3.62316 3.98445 3.25894 4.34705 3.25894 4.79458V20.2048C3.25894 20.6529 3.62316 21.0162 4.07269 21.0162H11.2422C11.4673 21.0162 11.527 20.8346 11.527 20.6105V11.3109C11.527 8.99356 14.4723 7.98808 15.9009 9.81741L20.3667 15.536V4.79524C20.3667 4.34705 20.4087 3.98445 19.9592 3.98445H4.07334Z"
fill="#12FFF7"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0 4.79524C0 3.71972 0.429155 2.68826 1.19305 1.92775C1.95695 1.16725 2.99303 0.739998 4.07334 0.739998L19.5517 0.739998C20.632 0.739998 21.668 1.16725 22.4319 1.92775C23.1958 2.68826 23.625 3.71972 23.625 4.79524V17.9011C23.625 20.2178 20.6798 21.2233 19.2518 19.3946L14.786 13.6753V20.6105C14.786 21.5784 14.3998 22.5067 13.7123 23.1911C13.0248 23.8755 12.0924 24.26 11.1202 24.26H4.07334C2.99303 24.26 1.95695 23.8328 1.19305 23.0722C0.429155 22.3117 0 21.2803 0 20.2048L0 4.79524ZM4.07334 3.98445C3.62316 3.98445 3.25894 4.34705 3.25894 4.79458V20.2048C3.25894 20.6529 3.62316 21.0162 4.07269 21.0162H11.2422C11.4673 21.0162 11.527 20.8346 11.527 20.6105V11.3109C11.527 8.99356 14.4723 7.98808 15.9009 9.81741L20.3667 15.536V4.79524C20.3667 4.34705 20.4087 3.98445 19.9592 3.98445H4.07334Z"
fill="url(#paint0_linear_138_19342)"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0 4.79524C0 3.71972 0.429155 2.68826 1.19305 1.92775C1.95695 1.16725 2.99303 0.739998 4.07334 0.739998L19.5517 0.739998C20.632 0.739998 21.668 1.16725 22.4319 1.92775C23.1958 2.68826 23.625 3.71972 23.625 4.79524V17.9011C23.625 20.2178 20.6798 21.2233 19.2518 19.3946L14.786 13.6753V20.6105C14.786 21.5784 14.3998 22.5067 13.7123 23.1911C13.0248 23.8755 12.0924 24.26 11.1202 24.26H4.07334C2.99303 24.26 1.95695 23.8328 1.19305 23.0722C0.429155 22.3117 0 21.2803 0 20.2048L0 4.79524ZM4.07334 3.98445C3.62316 3.98445 3.25894 4.34705 3.25894 4.79458V20.2048C3.25894 20.6529 3.62316 21.0162 4.07269 21.0162H11.2422C11.4673 21.0162 11.527 20.8346 11.527 20.6105V11.3109C11.527 8.99356 14.4723 7.98808 15.9009 9.81741L20.3667 15.536V4.79524C20.3667 4.34705 20.4087 3.98445 19.9592 3.98445H4.07334Z"
fill="url(#paint1_linear_138_19342)"
/>
<path
d="M19.5516 0.739998C20.6319 0.739998 21.668 1.16725 22.4319 1.92775C23.1958 2.68826 23.625 3.71972 23.625 4.79524V17.9011C23.625 20.2178 20.6797 21.2233 19.2517 19.3946L14.7859 13.6753V20.6105C14.7859 21.5784 14.3997 22.5067 13.7122 23.1911C13.0248 23.8755 12.0924 24.26 11.1201 24.26C11.1735 24.26 11.2265 24.2495 11.2758 24.2292C11.3252 24.2088 11.37 24.179 11.4078 24.1414C11.4456 24.1037 11.4756 24.0591 11.496 24.0099C11.5165 23.9608 11.527 23.9081 11.527 23.8549V11.3109C11.527 8.99356 14.4722 7.98808 15.9009 9.81741L20.3667 15.536V1.55078C20.3667 1.10325 20.0018 0.739998 19.5516 0.739998Z"
fill="#B9FFB3"
/>
<path
d="M39.8251 7.66533V13.8171L33.8336 7.66533H30.7151V17.7267H33.5593V11.1149L40.085 17.7267H42.6693V7.66533H39.8251ZM47.9416 15.4844V13.7021H54.3086V11.5605H47.9416V9.90757H55.6657V7.66533H45.0397V17.7267H55.8245V15.4844H47.9416ZM63.3826 18.0573C67.3817 18.0573 69.9805 16.1025 69.9805 12.696C69.9805 9.28952 67.3817 7.33475 63.3826 7.33475C59.3834 7.33475 56.7991 9.28952 56.7991 12.696C56.7991 16.1025 59.3834 18.0573 63.3826 18.0573ZM63.3826 15.6713C61.1592 15.6713 59.7876 14.5933 59.7876 12.696C59.7876 10.7987 61.1736 9.72072 63.3826 9.72072C65.6059 9.72072 66.9775 10.7987 66.9775 12.696C66.9775 14.5933 65.6059 15.6713 63.3826 15.6713ZM80.8821 7.66533V13.8171L74.8906 7.66533H71.772V17.7267H74.6162V11.1149L81.142 17.7267H83.7263V7.66533H80.8821Z"
fill="black"
/>
</g>
<defs>
<linearGradient
id="paint0_linear_138_19342"
x1="23.625"
y1="24.26"
x2="2.95571"
y2="0.648685"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#B9FFB3" />
<stop offset="1" stopColor="#B9FFB3" stopOpacity="0" />
</linearGradient>
<linearGradient
id="paint1_linear_138_19342"
x1="23.625"
y1="24.26"
x2="9.60889"
y2="18.784"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#1A1A1A" stopOpacity="0.9" />
<stop offset="1" stopColor="#1A1A1A" stopOpacity="0" />
</linearGradient>
<clipPath id="clip0_138_19342">
<rect
width="84"
height="23.52"
fill="white"
transform="translate(0 0.739998)"
/>
</clipPath>
</defs>
</svg>
);
}