Compare commits
7 Commits
@nhost/nho
...
@nhost/das
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
757c888656 | ||
|
|
7c13eb5f9b | ||
|
|
a84608e086 | ||
|
|
e43c079b9c | ||
|
|
3f396a9ebb | ||
|
|
6ed605beb8 | ||
|
|
edd223d29c |
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/dashboard",
|
||||
"version": "0.16.3",
|
||||
"version": "0.16.4",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'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>
|
||||
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user