feat (dashboard): Add information about that free organization cannot be upgraded (#3316)
### **PR Type** Enhancement ___ ### **Description** - Add info about free org upgrade limitations - Introduce new 'NewOrgButton' component - Update UI for subscription plan section - Improve text link component functionality ___ ### **Changes walkthrough** 📝 <table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table> <tr> <td> <details> <summary><strong>InfoAlert.tsx</strong><dd><code>Enhance AlertTitle styling</code> </dd></summary> <hr> dashboard/src/features/orgs/components/InfoAlert/InfoAlert.tsx - Added 'font-semibold' class to AlertTitle </details> </td> <td><a href="https://github.com/nhost/nhost/pull/3316/files#diff-4b78a2f0e1c6c2f6b37e430bc6cad016e884bb34735bd6aaebac906743748d7b">+1/-1</a> </td> </tr> <tr> <td> <details> <summary><strong>SubscriptionPlan.tsx</strong><dd><code>Update subscription plan UI and add free org info</code> </dd></summary> <hr> dashboard/src/features/orgs/components/billing/components/SubscriptionPlan/SubscriptionPlan.tsx <li>Added InfoAlert for free organizations<br> <li> Introduced NewOrgButton component<br> <li> Updated layout and styling of subscription plan section<br> <li> Replaced Link component with TextLink </details> </td> <td><a href="https://github.com/nhost/nhost/pull/3316/files#diff-2a5f070869055286b669e382b18d656935752803b9a1ef13390ac028c2a48ac4">+32/-30</a> </td> </tr> <tr> <td> <details> <summary><strong>TextLink.tsx</strong><dd><code>Enhance TextLink component with optional icon</code> </dd></summary> <hr> dashboard/src/features/orgs/projects/common/components/TextLink/TextLink.tsx <li>Added optional icon to TextLink component<br> <li> Introduced withIcon prop for flexibility </details> </td> <td><a href="https://github.com/nhost/nhost/pull/3316/files#diff-2f49ce51c83fab712173974ec09621f291ef56a7ad056df587c1bfd525ae6983">+4/-1</a> </td> </tr> </table></td></tr><tr><td><strong>Documentation</strong></td><td><table> <tr> <td> <details> <summary><strong>good-frogs-share.md</strong><dd><code>Add changeset for dashboard feature</code> </dd></summary> <hr> .changeset/good-frogs-share.md - Added changeset file for version bump </details> </td> <td><a href="https://github.com/nhost/nhost/pull/3316/files#diff-ff1c12916da9254a5d59fef39d5220a0ccdd20a7e66e1436a860da9a014d31ee">+5/-0</a> </td> </tr> </table></td></tr></tr></tbody></table> ___ > <details> <summary> Need help?</summary><li>Type <code>/help how to ...</code> in the comments thread for any questions about PR-Agent usage.</li><li>Check out the <a href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a> for more information.</li></details>
This commit is contained in:
5
.changeset/good-frogs-share.md
Normal file
5
.changeset/good-frogs-share.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@nhost/dashboard': minor
|
||||
---
|
||||
|
||||
feat (dashboard): Add information about that free organization cannot be upgraded.
|
||||
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -29,7 +29,7 @@ env:
|
||||
NHOST_PRO_TEST_PROJECT_NAME: ${{ vars.NHOST_PRO_TEST_PROJECT_NAME }}
|
||||
NHOST_TEST_USER_EMAIL: ${{ secrets.NHOST_TEST_USER_EMAIL }}
|
||||
NHOST_TEST_USER_PASSWORD: ${{ secrets.NHOST_TEST_USER_PASSWORD }}
|
||||
NHOST_TEST_PROJECT_ADMIN_SECRET: ${{ secrets.NHOST_TEST_PROJECT_ADMIN_SECRET }}
|
||||
NHOST_TEST_PROJECT_ADMIN_SECRET: '${{ secrets.NHOST_TEST_PROJECT_ADMIN_SECRET }}'
|
||||
NHOST_TEST_FREE_USER_EMAILS: ${{ secrets.NHOST_TEST_FREE_USER_EMAILS }}
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/* eslint-disable no-console */
|
||||
import { TEST_PROJECT_ADMIN_SECRET, TEST_PROJECT_SUBDOMAIN } from '@/e2e/env';
|
||||
import { test as setup } from '@playwright/test';
|
||||
|
||||
setup('refresh metadata', async () => {
|
||||
try {
|
||||
await fetch(
|
||||
const response = await fetch(
|
||||
`https://${TEST_PROJECT_SUBDOMAIN}.hasura.eu-central-1.staging.nhost.run/v1/metadata`,
|
||||
{
|
||||
method: 'POST',
|
||||
@@ -29,6 +30,14 @@ setup('refresh metadata', async () => {
|
||||
}),
|
||||
},
|
||||
);
|
||||
const body = await response.json();
|
||||
if (!response.ok) {
|
||||
const message = `[${body.code}]:${body.error}`;
|
||||
console.log(message);
|
||||
throw new Error(message);
|
||||
} else {
|
||||
console.log('Metadata is consistent.');
|
||||
}
|
||||
} catch (error) {
|
||||
// Log safe error information
|
||||
console.error(
|
||||
|
||||
@@ -26,7 +26,7 @@ function InfoAlert({
|
||||
<Alert className={alertClassNames}>
|
||||
{icon && <div>{icon}</div>}
|
||||
<div>
|
||||
{title && <AlertTitle>{title}</AlertTitle>}
|
||||
{title && <AlertTitle className="font-semibold">{title}</AlertTitle>}
|
||||
{children && (
|
||||
<AlertDescription className={descClassNames}>
|
||||
{children}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { ArrowSquareOutIcon } from '@/components/ui/v2/icons/ArrowSquareOutIcon';
|
||||
import { Link } from '@/components/ui/v2/Link';
|
||||
import { Button } from '@/components/ui/v3/button';
|
||||
import {
|
||||
Dialog,
|
||||
@@ -21,6 +20,8 @@ import {
|
||||
FormMessage,
|
||||
} from '@/components/ui/v3/form';
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/v3/radio-group';
|
||||
import { InfoAlert } from '@/features/orgs/components/InfoAlert';
|
||||
import TextLink from '@/features/orgs/projects/common/components/TextLink/TextLink';
|
||||
import { planDescriptions } from '@/features/orgs/projects/common/utils/planDescriptions';
|
||||
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
|
||||
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
|
||||
@@ -35,6 +36,14 @@ import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
function NewOrgButton() {
|
||||
return (
|
||||
<strong className="inline-flex items-center justify-center gap-2 px-1">
|
||||
<span>+ New Organization</span>
|
||||
</strong>
|
||||
);
|
||||
}
|
||||
|
||||
const changeOrgPlanForm = z.object({
|
||||
plan: z.string(),
|
||||
});
|
||||
@@ -48,6 +57,8 @@ export default function SubscriptionPlan() {
|
||||
const [fetchOrganizationCustomePortalLink, { loading }] =
|
||||
useBillingOrganizationCustomePortalLazyQuery();
|
||||
|
||||
const isFreeOrg = org?.plan.isFree;
|
||||
|
||||
const form = useForm<z.infer<typeof changeOrgPlanForm>>({
|
||||
resolver: zodResolver(changeOrgPlanForm),
|
||||
defaultValues: {
|
||||
@@ -125,7 +136,7 @@ export default function SubscriptionPlan() {
|
||||
<div className="flex w-full flex-col gap-1 border-b p-4">
|
||||
<h4 className="font-medium">Subscription plan</h4>
|
||||
</div>
|
||||
<div className="flex w-full flex-col justify-between gap-8 border-b p-4 md:flex-row">
|
||||
<div className="flex w-full flex-col justify-between gap-8 p-4 md:flex-row">
|
||||
<div className="flex basis-1/2 flex-col gap-4">
|
||||
<span className="font-medium">Organization name</span>
|
||||
<span className="font-medium">{org?.name}</span>
|
||||
@@ -152,31 +163,26 @@ export default function SubscriptionPlan() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full flex-col-reverse items-end justify-between gap-2 p-4 md:flex-row md:items-center md:gap-0">
|
||||
{isFreeOrg && (
|
||||
<div className="flex w-full flex-col justify-between gap-8 p-4 md:flex-row">
|
||||
<InfoAlert title="Personal Organizations can not be upgraded.">
|
||||
You may create a new organization with premium features by
|
||||
clicking on the <NewOrgButton /> button in the left sidebar.
|
||||
</InfoAlert>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex w-full flex-col-reverse items-end justify-between gap-2 border-t p-4 md:flex-row md:items-center md:gap-0">
|
||||
<div>
|
||||
<span>For a complete list of features, visit our </span>
|
||||
<Link
|
||||
href="https://nhost.io/pricing"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
underline="hover"
|
||||
className="font-medium"
|
||||
>
|
||||
<TextLink href="https://nhost.io/pricing">
|
||||
pricing
|
||||
<ArrowSquareOutIcon className="mb-[2px] ml-1 h-4 w-4" />
|
||||
</Link>
|
||||
</TextLink>
|
||||
<span> You can also visit our </span>
|
||||
<Link
|
||||
href="https://docs.nhost.io/platform/cloud/billing"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
underline="hover"
|
||||
className="font-medium"
|
||||
>
|
||||
<TextLink href="https://docs.nhost.io/platform/cloud/billing">
|
||||
documentation
|
||||
<ArrowSquareOutIcon className="mb-[2px] ml-1 h-4 w-4" />
|
||||
</Link>
|
||||
</TextLink>
|
||||
<span> for billing information</span>
|
||||
</div>
|
||||
<div className="flex w-full flex-row items-center justify-end gap-2">
|
||||
@@ -245,7 +251,7 @@ export default function SubscriptionPlan() {
|
||||
</div>
|
||||
|
||||
<div className="mt-0 flex h-full items-center text-xl font-semibold">
|
||||
{plan.isFree ? 'Free' : `${plan.price}/mo`}
|
||||
{isFreeOrg ? 'Free' : `${plan.price}/mo`}
|
||||
</div>
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
@@ -264,16 +270,10 @@ export default function SubscriptionPlan() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
href="mailto:hello@nhost.io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
underline="hover"
|
||||
className="font-medium"
|
||||
>
|
||||
<TextLink href="mailto:hello@nhost.io">
|
||||
Contact us
|
||||
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
|
||||
</Link>
|
||||
</TextLink>
|
||||
</div>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { SquareArrowUpRightIcon } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
@@ -5,7 +6,8 @@ function TextLink({
|
||||
href,
|
||||
children,
|
||||
target = '_blank',
|
||||
}: PropsWithChildren<{ href: string; target?: string }>) {
|
||||
withIcon = false,
|
||||
}: PropsWithChildren<{ href: string; target?: string; withIcon?: boolean }>) {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
@@ -14,6 +16,7 @@ function TextLink({
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{children}
|
||||
{withIcon && <SquareArrowUpRightIcon className="h-4 w-4" />}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user