Compare commits

..

7 Commits

Author SHA1 Message Date
Szilárd Dóró
757c888656 Merge pull request #1910 from nhost/changeset-release/main
chore: update versions
2023-05-09 11:40:16 +02:00
github-actions[bot]
7c13eb5f9b chore: update versions 2023-05-09 09:17:43 +00:00
Szilárd Dóró
a84608e086 Merge pull request #1907 from nhost/fix/upgrade
fix(dashboard): unpause after upgrading a paused project to pro
2023-05-09 11:13:44 +02:00
Szilárd Dóró
e43c079b9c feat: poll project state after unpausing with upgrade 2023-05-09 10:50:34 +02:00
Szilárd Dóró
3f396a9ebb chore: add changesets 2023-05-08 19:28:42 +02:00
Szilárd Dóró
6ed605beb8 fix: update desiredState on plan change 2023-05-08 17:58:06 +02:00
Szilárd Dóró
edd223d29c fix: don't go to 404 page unnecessarily 2023-05-08 17:47:04 +02:00
6 changed files with 125 additions and 64 deletions

View File

@@ -1,5 +1,12 @@
# @nhost/dashboard
## 0.16.4
### Patch Changes
- 3f396a9e: fix(projects): unpause after upgrading a paused project to pro
- 3f396a9e: fix(projects): don't redirect to 404 page after project creation
## 0.16.3
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "0.16.3",
"version": "0.16.4",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",

View File

@@ -33,7 +33,8 @@ export default function ApplicationPaused() {
} = useCurrentWorkspaceAndProject();
const user = useUserData();
const isOwner = currentWorkspace.workspaceMembers.some(
({ id, type }) => id === user?.id && type === 'owner',
({ type, user: workspaceUser }) =>
workspaceUser.id === user?.id && type === 'owner',
);
const [showDeletingModal, setShowDeletingModal] = useState(false);
const [unpauseApplication, { loading: changingApplicationStateLoading }] =
@@ -120,20 +121,22 @@ export default function ApplicationPaused() {
</Box>
<Box className="grid grid-flow-row gap-2">
<Button
className="mx-auto w-full max-w-[280px]"
onClick={() => {
openDialog({
component: <ChangePlanModal />,
props: {
PaperProps: { className: 'p-0' },
maxWidth: 'lg',
},
});
}}
>
Upgrade to Pro
</Button>
{isOwner && (
<Button
className="mx-auto w-full max-w-[280px]"
onClick={() => {
openDialog({
component: <ChangePlanModal />,
props: {
PaperProps: { className: 'p-0' },
maxWidth: 'lg',
},
});
}}
>
Upgrade to Pro
</Button>
)}
<div className="grid grid-flow-row gap-2">
<Button

View File

@@ -6,7 +6,9 @@ import {
useGetPaymentMethodsQuery,
useUpdateApplicationMutation,
} from '@/generated/graphql';
import useApplicationState from '@/hooks/useApplicationState';
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
import { ApplicationStatus } from '@/types/application';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button';
@@ -15,11 +17,11 @@ import { BaseDialog } from '@/ui/v2/Dialog';
import Link from '@/ui/v2/Link';
import Text from '@/ui/v2/Text';
import { planDescriptions } from '@/utils/planDescriptions';
import getServerError from '@/utils/settings/getServerError/getServerError';
import getServerError from '@/utils/settings/getServerError';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { toast } from 'react-hot-toast';
function Plan({ planName, price, setPlan, planId, selectedPlanId }: any) {
@@ -53,7 +55,7 @@ function Plan({ planName, price, setPlan, planId, selectedPlanId }: any) {
</div>
<Text variant="h3" component="p">
$ {price}/mo
${price}/mo
</Text>
</button>
);
@@ -62,12 +64,14 @@ function Plan({ planName, price, setPlan, planId, selectedPlanId }: any) {
export function ChangePlanModalWithData({ app, plans, close }: any) {
const [selectedPlanId, setSelectedPlanId] = useState('');
const { closeAlertDialog } = useDialog();
const [pollingCurrentProject, setPollingCurrentProject] = useState(false);
const {
currentWorkspace,
currentProject,
refetch: refetchWorkspaceAndProject,
} = useCurrentWorkspaceAndProject();
const { state } = useApplicationState();
const { data } = useGetPaymentMethodsQuery({
variables: {
@@ -82,6 +86,29 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
const currentPlan = plans.find((plan) => plan.id === app.plan.id);
const selectedPlan = plans.find((plan) => plan.id === selectedPlanId);
useEffect(() => {
if (!pollingCurrentProject || state === ApplicationStatus.Paused) {
return;
}
close?.();
closeAlertDialog();
setShowPaymentModal(false);
setPollingCurrentProject(false);
}, [state, pollingCurrentProject, close, closeAlertDialog]);
useEffect(() => {
if (!pollingCurrentProject) {
return () => {};
}
const interval = setInterval(() => {
refetchWorkspaceAndProject();
}, 1000);
return () => clearInterval(interval);
}, [pollingCurrentProject, refetchWorkspaceAndProject, currentProject]);
const [updateApp] = useUpdateApplicationMutation({
refetchQueries: [
refetchGetApplicationPlanQuery({
@@ -99,6 +126,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
appId: app.id,
app: {
planId: selectedPlan.id,
desiredState: 5,
},
},
}),
@@ -112,11 +140,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
getToastStyleProps(),
);
await refetchWorkspaceAndProject();
close?.();
closeAlertDialog();
setShowPaymentModal(false);
setPollingCurrentProject(true);
} catch (error) {
// Note: Error is handled by the toast.
}
@@ -134,12 +158,49 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
}
await handleUpdateAppPlan();
setShowPaymentModal(false);
close?.();
closeAlertDialog();
};
if (pollingCurrentProject) {
return (
<Box className="mx-auto w-full max-w-xl rounded-lg p-6 text-left">
<div className="flex flex-col">
<div className="mx-auto">
<Image
src="/assets/upgrade.svg"
alt="Nhost Logo"
width={72}
height={72}
/>
</div>
<Text variant="h3" component="h2" className="mt-2 text-center">
Successfully upgraded to {currentPlan.name}
</Text>
<ActivityIndicator
label="We are unpausing your project. This may take some time..."
className="mx-auto mt-2"
/>
<Button
variant="outlined"
color="secondary"
className="mx-auto mt-4 w-full max-w-sm"
onClick={() => {
if (close) {
close();
}
closeAlertDialog();
}}
>
Cancel
</Button>
</div>
</Box>
);
}
if (app.plan.id !== plans.find((plan) => plan.isFree)?.id) {
return (
<Box className="mx-auto w-full max-w-xl rounded-lg p-6 text-left">
@@ -215,7 +276,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
You&apos;re currently on the <strong>{app.plan.name}</strong> plan.
</Text>
<div className="mt-5">
<div className="mt-2">
{plans
.filter((plan) => plan.id !== app.plan.id)
.map((plan) => (
@@ -233,8 +294,12 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
))}
</div>
<div className="mt-6 grid grid-flow-row gap-2">
<Button onClick={handleChangePlanClick} disabled={!selectedPlan}>
<div className="mt-2 grid grid-flow-row gap-2">
<Button
onClick={handleChangePlanClick}
disabled={!selectedPlan}
loading={pollingCurrentProject}
>
Upgrade
</Button>

View File

@@ -56,7 +56,9 @@ export default function ResetDatabasePasswordSettings() {
const handleGenerateRandomPassword = () => {
const newRandomDatabasePassword = generateRandomDatabasePassword();
triggerToast('New random database password generated.');
setValue('databasePassword', newRandomDatabasePassword);
setValue('databasePassword', newRandomDatabasePassword, {
shouldDirty: true,
});
};
const handleChangeDatabasePassword = async (

View File

@@ -6,52 +6,36 @@ import { useEffect } from 'react';
* Redirects to 404 page if either currentWorkspace/currentProject resolves to undefined.
*/
export default function useNotFoundRedirect() {
const { currentProject, currentWorkspace, loading } =
useCurrentWorkspaceAndProject();
const router = useRouter();
const {
query: { workspaceSlug, appSlug, updating },
isReady,
} = router;
const notIn404Already = router.pathname !== '/404';
const noResolvedWorkspace =
isReady && !loading && workspaceSlug && currentWorkspace === undefined;
const noResolvedApplication =
isReady &&
!loading &&
workspaceSlug &&
appSlug &&
currentProject === undefined;
const inSettingsDatabasePage = router.pathname.includes('/settings/database');
const { currentProject, currentWorkspace, loading } =
useCurrentWorkspaceAndProject();
useEffect(() => {
// This code is checking if the URL has a query of the form `?updating=true`
// If it does (`updating` is true) this useEffect will immediately exit without executing
// any further statements (e.g. the page will show a loader until `updating` is false).
// This is to prevent the user from being redirected to the 404 page while we are updating
// either the workspace slug or application slug.
if (updating) {
if (
updating ||
!isReady ||
loading ||
router.pathname === '/404' ||
(workspaceSlug && currentWorkspace && appSlug && currentProject) ||
(workspaceSlug && currentWorkspace)
) {
return;
}
if (noResolvedWorkspace && notIn404Already) {
router.replace('/404');
}
if (noResolvedApplication && notIn404Already) {
router.replace('/404');
}
router.replace('/404');
}, [
isReady,
updating,
currentProject,
currentWorkspace,
noResolvedApplication,
noResolvedWorkspace,
notIn404Already,
isReady,
loading,
appSlug,
router,
inSettingsDatabasePage,
updating,
workspaceSlug,
]);
}