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>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </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>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>SubscriptionPlan.tsx</strong><dd><code>Update
subscription plan UI and add free org info</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </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>&nbsp;
</td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>TextLink.tsx</strong><dd><code>Enhance TextLink
component with optional icon</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </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>&nbsp;
&nbsp; &nbsp; </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>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </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>&nbsp;
&nbsp; &nbsp; </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:
robertkasza
2025-05-07 13:31:01 +02:00
committed by GitHub
parent d2a9a9ae1d
commit c0635ae1c7
6 changed files with 50 additions and 33 deletions

View File

@@ -0,0 +1,5 @@
---
'@nhost/dashboard': minor
---
feat (dashboard): Add information about that free organization cannot be upgraded.

View File

@@ -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:

View File

@@ -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(

View File

@@ -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}

View File

@@ -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>

View File

@@ -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>
);
}