Compare commits

...

7 Commits

Author SHA1 Message Date
Szilárd Dóró
c625317342 Merge pull request #1841 from nhost/changeset-release/main
chore: update versions
2023-04-17 08:36:12 +02:00
github-actions[bot]
8ab75a4146 chore: update versions 2023-04-14 09:54:44 +00:00
Szilárd Dóró
607f465616 Merge pull request #1840 from nhost/chore/use-dialog-hook
chore(dashboard): unify payment dialog management
2023-04-14 11:50:15 +02:00
Szilárd Dóró
668c877130 chore: add changeset 2023-04-14 11:17:32 +02:00
Szilárd Dóró
4bd870eb96 chore: relocate BillingPaymentMethodForm 2023-04-14 11:15:58 +02:00
Szilárd Dóró
39b3161d91 fix: use up-to-date card information 2023-04-14 10:31:27 +02:00
Szilárd Dóró
ae090a6585 chore: unify modal management for payments 2023-04-13 16:53:30 +02:00
17 changed files with 131 additions and 169 deletions

View File

@@ -1,5 +1,11 @@
# @nhost/dashboard # @nhost/dashboard
## 0.14.8
### Patch Changes
- 668c8771: chore(dialogs): unify dialog management of payment dialogs
## 0.14.7 ## 0.14.7
### Patch Changes ### Patch Changes

View File

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

View File

@@ -124,13 +124,9 @@ export default function ApplicationPaused() {
className="mx-auto w-full max-w-[280px]" className="mx-auto w-full max-w-[280px]"
onClick={() => { onClick={() => {
openDialog({ openDialog({
title: 'Upgrade your plan.',
component: <ChangePlanModal />, component: <ChangePlanModal />,
props: { props: {
PaperProps: { className: 'p-0' }, PaperProps: { className: 'p-0' },
hidePrimaryAction: true,
hideSecondaryAction: true,
hideTitle: true,
maxWidth: 'lg', maxWidth: 'lg',
}, },
}); });

View File

@@ -1,6 +1,5 @@
import { BillingPaymentMethodForm } from '@/components/billing-payment-method/BillingPaymentMethodForm';
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/context/UIContext'; import { BillingPaymentMethodForm } from '@/components/workspace/BillingPaymentMethodForm';
import { import {
refetchGetApplicationPlanQuery, refetchGetApplicationPlanQuery,
useGetAppPlanAndGlobalPlansQuery, useGetAppPlanAndGlobalPlansQuery,
@@ -8,18 +7,19 @@ import {
useUpdateApplicationMutation, useUpdateApplicationMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
import { Modal } from '@/ui/Modal';
import ActivityIndicator from '@/ui/v2/ActivityIndicator'; import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import Box from '@/ui/v2/Box'; import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import Checkbox from '@/ui/v2/Checkbox'; import Checkbox from '@/ui/v2/Checkbox';
import { BaseDialog } from '@/ui/v2/Dialog';
import Text from '@/ui/v2/Text'; import Text from '@/ui/v2/Text';
import { planDescriptions } from '@/utils/planDescriptions'; import { planDescriptions } from '@/utils/planDescriptions';
import { triggerToast } from '@/utils/toast'; import getServerError from '@/utils/settings/getServerError/getServerError';
import { useTheme } from '@mui/material'; import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import Image from 'next/image'; import Image from 'next/image';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useState } from 'react'; import { useState } from 'react';
import { toast } from 'react-hot-toast';
function Plan({ function Plan({
planName, planName,
@@ -66,13 +66,15 @@ function Plan({
} }
export function ChangePlanModalWithData({ app, plans, close }: any) { export function ChangePlanModalWithData({ app, plans, close }: any) {
const theme = useTheme();
const [selectedPlanId, setSelectedPlanId] = useState(''); const [selectedPlanId, setSelectedPlanId] = useState('');
const { closeAlertDialog } = useDialog(); const { closeAlertDialog } = useDialog();
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject(); const {
currentWorkspace,
currentProject,
refetch: refetchWorkspaceAndProject,
} = useCurrentWorkspaceAndProject();
// get workspace payment methods
const { data } = useGetPaymentMethodsQuery({ const { data } = useGetPaymentMethodsQuery({
variables: { variables: {
workspaceId: currentWorkspace?.id, workspaceId: currentWorkspace?.id,
@@ -80,7 +82,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
skip: !currentWorkspace, skip: !currentWorkspace,
}); });
const { openPaymentModal, closePaymentModal, paymentModal } = useUI(); const [showPaymentModal, setShowPaymentModal] = useState(false);
const paymentMethodAvailable = data?.paymentMethods.length > 0; const paymentMethodAvailable = data?.paymentMethods.length > 0;
const currentPlan = plans.find((plan) => plan.id === app.plan.id); const currentPlan = plans.find((plan) => plan.id === app.plan.id);
@@ -88,7 +90,6 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
const isDowngrade = currentPlan.price > selectedPlan?.price; const isDowngrade = currentPlan.price > selectedPlan?.price;
// graphql mutations
const [updateApp] = useUpdateApplicationMutation({ const [updateApp] = useUpdateApplicationMutation({
refetchQueries: [ refetchQueries: [
refetchGetApplicationPlanQuery({ refetchGetApplicationPlanQuery({
@@ -98,28 +99,35 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
], ],
}); });
// function handlers
const handleUpdateAppPlan = async () => { const handleUpdateAppPlan = async () => {
await updateApp({ try {
variables: { await toast.promise(
appId: app.id, updateApp({
app: { variables: {
planId: selectedPlan.id, appId: app.id,
app: {
planId: selectedPlan.id,
},
},
}),
{
loading: 'Updating plan...',
success: `Plan has been updated successfully to ${selectedPlan.name}.`,
error: getServerError(
'An error occurred while updating the plan. Please try again.',
),
}, },
}, getToastStyleProps(),
}); );
if (isDowngrade) { await refetchWorkspaceAndProject();
if (close) {
close();
}
close?.();
closeAlertDialog(); closeAlertDialog();
setShowPaymentModal(false);
} catch (error) {
// Note: Error is handled by the toast.
} }
triggerToast(
`${currentProject.name} plan changed to ${selectedPlan.name}.`,
);
}; };
const handleChangePlanClick = async () => { const handleChangePlanClick = async () => {
@@ -128,33 +136,30 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
} }
if (!paymentMethodAvailable) { if (!paymentMethodAvailable) {
openPaymentModal(); setShowPaymentModal(true);
return; return;
} }
await handleUpdateAppPlan(); await handleUpdateAppPlan();
if (close) { setShowPaymentModal(false);
close(); close?.();
}
closeAlertDialog(); closeAlertDialog();
}; };
return ( return (
<Box className="w-full max-w-xl rounded-lg p-6 text-left"> <Box className="w-full max-w-xl rounded-lg p-6 text-left">
<Modal <BaseDialog
showModal={paymentModal} open={showPaymentModal}
close={closePaymentModal} onClose={() => setShowPaymentModal(false)}
dialogStyle={{ zIndex: theme.zIndex.modal + 1 }}
> >
<BillingPaymentMethodForm <BillingPaymentMethodForm
close={closePaymentModal}
onPaymentMethodAdded={handleUpdateAppPlan} onPaymentMethodAdded={handleUpdateAppPlan}
workspaceId={currentWorkspace.id} workspaceId={currentWorkspace.id}
/> />
</Modal> </BaseDialog>
<div className="flex flex-col"> <div className="flex flex-col">
<div className="mx-auto"> <div className="mx-auto">
<Image <Image
@@ -217,14 +222,12 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
export interface ChangePlanModalProps { export interface ChangePlanModalProps {
/** /**
* Function to close the modal if mounted on parent component. * Function to close the modal.
*
* @deprecated Implement modal by using `openAlertDialog` hook instead.
*/ */
close?: () => void; onCancel?: () => void;
} }
export function ChangePlanModal({ close }: ChangePlanModalProps) { export function ChangePlanModal({ onCancel }: ChangePlanModalProps) {
const { const {
query: { workspaceSlug, appSlug }, query: { workspaceSlug, appSlug },
} = useRouter(); } = useRouter();
@@ -250,5 +253,5 @@ export function ChangePlanModal({ close }: ChangePlanModalProps) {
const { apps, plans } = data; const { apps, plans } = data;
const app = apps[0]; const app = apps[0];
return <ChangePlanModalWithData app={app} plans={plans} close={close} />; return <ChangePlanModalWithData app={app} plans={plans} close={onCancel} />;
} }

View File

@@ -30,13 +30,9 @@ export function UnlockFeatureByUpgrading({
variant="borderless" variant="borderless"
onClick={() => { onClick={() => {
openDialog({ openDialog({
title: 'Upgrade your plan.',
component: <ChangePlanModal />, component: <ChangePlanModal />,
props: { props: {
PaperProps: { className: 'p-0 max-w-xl w-full' }, PaperProps: { className: 'p-0 max-w-xl w-full' },
hidePrimaryAction: true,
hideSecondaryAction: true,
hideTitle: true,
}, },
}); });
}} }}

View File

@@ -22,7 +22,7 @@ export interface OpenDialogOptions {
/** /**
* Title of the dialog. * Title of the dialog.
*/ */
title: ReactNode; title?: ReactNode;
/** /**
* Component to render inside the dialog skeleton. * Component to render inside the dialog skeleton.
*/ */

View File

@@ -22,6 +22,13 @@ export function CountrySelector({ value, onChange }: CountrySelectorProps) {
value={value || null} value={value || null}
onChange={(_event, inputValue) => onChange(inputValue as string)} onChange={(_event, inputValue) => onChange(inputValue as string)}
placeholder="Select Country" placeholder="Select Country"
slotProps={{
listbox: { className: 'min-w-0 w-full' },
popper: {
disablePortal: false,
className: 'z-[10000] w-[270px] w-full',
},
}}
> >
{countries?.map((country) => ( {countries?.map((country) => (
<Option key={country.name} value={country.code}> <Option key={country.name} value={country.code}>

View File

@@ -93,13 +93,9 @@ export default function OverviewTopBar() {
className="mr-2" className="mr-2"
onClick={() => { onClick={() => {
openDialog({ openDialog({
title: 'Upgrade your plan.',
component: <ChangePlanModal />, component: <ChangePlanModal />,
props: { props: {
PaperProps: { className: 'p-0 max-w-xl w-full' }, PaperProps: { className: 'p-0 max-w-xl w-full' },
hidePrimaryAction: true,
hideSecondaryAction: true,
hideTitle: true,
}, },
}); });
}} }}

View File

@@ -42,7 +42,7 @@ function AlertDialog({
}} }}
{...props} {...props}
> >
{!hideTitle && ( {!hideTitle && !!title && (
<Dialog.Title {...titleProps} id="alert-dialog-title"> <Dialog.Title {...titleProps} id="alert-dialog-title">
{title} {title}
</Dialog.Title> </Dialog.Title>

View File

@@ -22,7 +22,7 @@ function Dialog({
aria-describedby="dialog-description" aria-describedby="dialog-description"
{...props} {...props}
> >
{!hideTitle && ( {!hideTitle && !!title && (
<DialogTitle <DialogTitle
sx={{ sx={{
padding: (theme) => theme.spacing(3, 3, 1.5, 3), padding: (theme) => theme.spacing(3, 3, 1.5, 3),

View File

@@ -16,7 +16,7 @@ export interface CommonDialogProps
/** /**
* The title of the dialog. * The title of the dialog.
*/ */
title: ReactNode; title?: ReactNode;
/** /**
* The message to display in the dialog. * The message to display in the dialog.
*/ */

View File

@@ -27,13 +27,11 @@ const stripePromise = process.env.NEXT_PUBLIC_STRIPE_PK
: null; : null;
type AddPaymentMethodFormProps = { type AddPaymentMethodFormProps = {
close: () => void; onPaymentMethodAdded?: () => void;
onPaymentMethodAdded?: () => Promise<void>;
workspaceId: string; workspaceId: string;
}; };
function AddPaymentMethodForm({ function AddPaymentMethodForm({
close,
onPaymentMethodAdded, onPaymentMethodAdded,
workspaceId, workspaceId,
}: AddPaymentMethodFormProps) { }: AddPaymentMethodFormProps) {
@@ -141,9 +139,7 @@ function AddPaymentMethodForm({
// payment method added successfylly // payment method added successfylly
triggerToast(`New payment method added`); triggerToast('New payment method has been added to the workspace.');
close();
discordAnnounce( discordAnnounce(
`(${user.email}) added a new credit card to workspace id: ${workspaceId}.`, `(${user.email}) added a new credit card to workspace id: ${workspaceId}.`,
@@ -205,26 +201,27 @@ function AddPaymentMethodForm({
); );
} }
type BillingPaymentMethodFormProps = { export interface BillingPaymentMethodFormProps {
close: () => void; /**
onPaymentMethodAdded?: (e?: any) => Promise<void>; * Callback function to run after a payment method is added.
*/
onPaymentMethodAdded?: (e?: any) => void;
/**
* Workspace identifier.
*/
workspaceId: string; workspaceId: string;
}; }
export function BillingPaymentMethodForm({ export default function BillingPaymentMethodForm({
close,
onPaymentMethodAdded, onPaymentMethodAdded,
workspaceId, workspaceId,
}: BillingPaymentMethodFormProps) { }: BillingPaymentMethodFormProps) {
return ( return (
<Elements stripe={stripePromise}> <Elements stripe={stripePromise}>
<AddPaymentMethodForm <AddPaymentMethodForm
close={close}
onPaymentMethodAdded={onPaymentMethodAdded} onPaymentMethodAdded={onPaymentMethodAdded}
workspaceId={workspaceId} workspaceId={workspaceId}
/> />
</Elements> </Elements>
); );
} }
export default BillingPaymentMethodForm;

View File

@@ -0,0 +1,2 @@
export * from './BillingPaymentMethodForm';
export { default as BillingPaymentMethodForm } from './BillingPaymentMethodForm';

View File

@@ -1,5 +1,5 @@
import { BillingPaymentMethodForm } from '@/components/billing-payment-method/BillingPaymentMethodForm';
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import { BillingPaymentMethodForm } from '@/components/workspace/BillingPaymentMethodForm';
import type { GetPaymentMethodsFragment } from '@/generated/graphql'; import type { GetPaymentMethodsFragment } from '@/generated/graphql';
import { import {
refetchGetPaymentMethodsQuery, refetchGetPaymentMethodsQuery,
@@ -8,7 +8,6 @@ import {
useSetNewDefaultPaymentMethodMutation, useSetNewDefaultPaymentMethodMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
import { Modal } from '@/ui/Modal';
import ActivityIndicator from '@/ui/v2/ActivityIndicator'; import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
import Table from '@/ui/v2/Table'; import Table from '@/ui/v2/Table';
@@ -21,8 +20,6 @@ import Text from '@/ui/v2/Text';
import { triggerToast } from '@/utils/toast'; import { triggerToast } from '@/utils/toast';
import { useTheme } from '@mui/material'; import { useTheme } from '@mui/material';
import { formatDistanceToNowStrict } from 'date-fns'; import { formatDistanceToNowStrict } from 'date-fns';
import { useRouter } from 'next/router';
import { useState } from 'react';
function CheckCircle() { function CheckCircle() {
const theme = useTheme(); const theme = useTheme();
@@ -44,15 +41,8 @@ function CheckCircle() {
} }
export default function WorkspacePaymentMethods() { export default function WorkspacePaymentMethods() {
const router = useRouter();
const { action } = router.query;
const { currentWorkspace } = useCurrentWorkspaceAndProject(); const { currentWorkspace } = useCurrentWorkspaceAndProject();
const { openAlertDialog } = useDialog(); const { openAlertDialog, openDialog, closeDialog } = useDialog();
const [showAddPaymentMethodModal, setShowAddPaymentMethodModal] = useState(
action === 'add-payment-method',
);
const { loading, error, data } = useGetPaymentMethodsQuery({ const { loading, error, data } = useGetPaymentMethodsQuery({
variables: { variables: {
@@ -230,7 +220,14 @@ export default function WorkspacePaymentMethods() {
<Button <Button
variant="outlined" variant="outlined"
onClick={() => { onClick={() => {
setShowAddPaymentMethodModal(true); openDialog({
component: (
<BillingPaymentMethodForm
workspaceId={currentWorkspace.id}
onPaymentMethodAdded={closeDialog}
/>
),
});
}} }}
disabled={maxPaymentMethodsReached} disabled={maxPaymentMethodsReached}
> >
@@ -244,19 +241,6 @@ export default function WorkspacePaymentMethods() {
payment methods. payment methods.
</Text> </Text>
)} )}
{showAddPaymentMethodModal && (
<Modal
showModal={showAddPaymentMethodModal}
close={() =>
setShowAddPaymentMethodModal(!showAddPaymentMethodModal)
}
>
<BillingPaymentMethodForm
workspaceId={currentWorkspace.id}
close={() => setShowAddPaymentMethodModal(false)}
/>
</Modal>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,9 +1,8 @@
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import type { PropsWithChildren } from 'react'; import type { PropsWithChildren } from 'react';
import { createContext, useContext, useMemo, useReducer } from 'react'; import { createContext, useContext, useMemo } from 'react';
export interface UIContextState { export interface UIContextState {
paymentModal: boolean;
/** /**
* Determines whether or not the dashboard is in maintenance mode. * Determines whether or not the dashboard is in maintenance mode.
*/ */
@@ -12,42 +11,18 @@ export interface UIContextState {
* The date and time when maintenance mode will end. * The date and time when maintenance mode will end.
*/ */
maintenanceEndDate: Date; maintenanceEndDate: Date;
openPaymentModal: () => void;
closePaymentModal: () => void;
} }
const initialState: UIContextState = { export const UIContext = createContext<UIContextState>({
paymentModal: false,
maintenanceActive: false, maintenanceActive: false,
maintenanceEndDate: null, maintenanceEndDate: null,
openPaymentModal: () => {}, });
closePaymentModal: () => {},
};
export const UIContext = createContext<UIContextState>(initialState);
UIContext.displayName = 'UIContext'; UIContext.displayName = 'UIContext';
function sideReducer(state: any, action: any) {
switch (action.type) {
case 'TOGGLE_PAYMENT_MODAL': {
return {
...state,
paymentModal: !state.paymentModal,
};
}
default:
return { ...state };
}
}
export function UIProvider(props: PropsWithChildren<unknown>) { export function UIProvider(props: PropsWithChildren<unknown>) {
const [state, dispatch] = useReducer(sideReducer, initialState);
const router = useRouter(); const router = useRouter();
const openPaymentModal = () => dispatch({ type: 'TOGGLE_PAYMENT_MODAL' });
const closePaymentModal = () => dispatch({ type: 'TOGGLE_PAYMENT_MODAL' });
const maintenanceUnlocked = const maintenanceUnlocked =
process.env.NEXT_PUBLIC_MAINTENANCE_UNLOCK_SECRET && process.env.NEXT_PUBLIC_MAINTENANCE_UNLOCK_SECRET &&
process.env.NEXT_PUBLIC_MAINTENANCE_UNLOCK_SECRET === process.env.NEXT_PUBLIC_MAINTENANCE_UNLOCK_SECRET ===
@@ -55,9 +30,6 @@ export function UIProvider(props: PropsWithChildren<unknown>) {
const value: UIContextState = useMemo( const value: UIContextState = useMemo(
() => ({ () => ({
...state,
openPaymentModal,
closePaymentModal,
maintenanceActive: maintenanceUnlocked maintenanceActive: maintenanceUnlocked
? false ? false
: process.env.NEXT_PUBLIC_MAINTENANCE_ACTIVE === 'true', : process.env.NEXT_PUBLIC_MAINTENANCE_ACTIVE === 'true',
@@ -67,7 +39,7 @@ export function UIProvider(props: PropsWithChildren<unknown>) {
? new Date(Date.parse(process.env.NEXT_PUBLIC_MAINTENANCE_END_DATE)) ? new Date(Date.parse(process.env.NEXT_PUBLIC_MAINTENANCE_END_DATE))
: null, : null,
}), }),
[state, maintenanceUnlocked], [maintenanceUnlocked],
); );
return <UIContext.Provider value={value} {...props} />; return <UIContext.Provider value={value} {...props} />;

View File

@@ -234,7 +234,6 @@ export default function SettingsGeneralPage() {
disabled: maintenanceActive, disabled: maintenanceActive,
onClick: () => { onClick: () => {
openDialog({ openDialog({
title: '',
component: ( component: (
<RemoveApplicationModal <RemoveApplicationModal
close={closeDialog} close={closeDialog}
@@ -243,7 +242,6 @@ export default function SettingsGeneralPage() {
), ),
props: { props: {
PaperProps: { className: 'max-w-sm' }, PaperProps: { className: 'max-w-sm' },
hideTitle: true,
}, },
}); });
}, },

View File

@@ -1,11 +1,11 @@
import { BillingPaymentMethodForm } from '@/components/billing-payment-method/BillingPaymentMethodForm'; import { useDialog } from '@/components/common/DialogProvider';
import AuthenticatedLayout from '@/components/layout/AuthenticatedLayout'; import AuthenticatedLayout from '@/components/layout/AuthenticatedLayout';
import Container from '@/components/layout/Container'; import Container from '@/components/layout/Container';
import { BillingPaymentMethodForm } from '@/components/workspace/BillingPaymentMethodForm';
import { useUI } from '@/context/UIContext'; import { useUI } from '@/context/UIContext';
import features from '@/data/features.json'; import features from '@/data/features.json';
import { useSubmitState } from '@/hooks/useSubmitState'; import { useSubmitState } from '@/hooks/useSubmitState';
import { Alert } from '@/ui/Alert'; import { Alert } from '@/ui/Alert';
import { Modal } from '@/ui/Modal';
import ActivityIndicator from '@/ui/v2/ActivityIndicator'; import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import Box from '@/ui/v2/Box'; import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button'; import Button from '@/ui/v2/Button';
@@ -44,7 +44,7 @@ import type { ApolloError } from '@apollo/client';
import { useUserData } from '@nhost/nextjs'; import { useUserData } from '@nhost/nextjs';
import Image from 'next/image'; import Image from 'next/image';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import type { ReactElement } from 'react'; import type { FormEvent, ReactElement } from 'react';
import { cloneElement, isValidElement, useState } from 'react'; import { cloneElement, isValidElement, useState } from 'react';
import { toast } from 'react-hot-toast'; import { toast } from 'react-hot-toast';
import slugify from 'slugify'; import slugify from 'slugify';
@@ -67,6 +67,7 @@ export function NewProjectPageContent({
preSelectedWorkspace, preSelectedWorkspace,
preSelectedRegion, preSelectedRegion,
}: NewAppPageProps) { }: NewAppPageProps) {
const { openDialog, closeDialog } = useDialog();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const router = useRouter(); const router = useRouter();
@@ -102,11 +103,7 @@ export function NewProjectPageContent({
const [plan, setPlan] = useState(defaultSelectedPlan); const [plan, setPlan] = useState(defaultSelectedPlan);
// state
const { submitState, setSubmitState } = useSubmitState(); const { submitState, setSubmitState } = useSubmitState();
const [showPaymentModal, setShowPaymentModal] = useState(false);
// graphql mutations
const [insertApp] = useInsertApplicationMutation({ const [insertApp] = useInsertApplicationMutation({
refetchQueries: [GetAllWorkspacesAndProjectsDocument], refetchQueries: [GetAllWorkspacesAndProjectsDocument],
@@ -146,13 +143,8 @@ export function NewProjectPageContent({
setDatabasePassword(newRandomDatabasePassword); setDatabasePassword(newRandomDatabasePassword);
}; };
const handleSubmit = async (e) => { async function handleCreateProject(event: FormEvent) {
e.preventDefault(); event.preventDefault();
if (!plan.isFree && workspace.paymentMethods.length === 0) {
setShowPaymentModal(true);
return;
}
setSubmitState({ setSubmitState({
error: null, error: null,
@@ -225,7 +217,29 @@ export function NewProjectPageContent({
loading: false, loading: false,
}); });
} }
}; }
async function handleSubmit(event: FormEvent) {
event.preventDefault();
if (!plan.isFree && workspace.paymentMethods.length === 0) {
openDialog({
component: (
<BillingPaymentMethodForm
onPaymentMethodAdded={() => {
handleCreateProject(event);
closeDialog();
}}
workspaceId={workspace.id}
/>
),
});
return;
}
handleCreateProject(event);
}
if (!selectedWorkspace) { if (!selectedWorkspace) {
return ( return (
@@ -288,7 +302,13 @@ export function NewProjectPageContent({
const workspaceInList = workspaces.find( const workspaceInList = workspaces.find(
({ id }) => id === value, ({ id }) => id === value,
); );
setPlan(plans[0]);
if (numberOfFreeAndLiveProjects >= MAX_FREE_PROJECTS) {
setPlan(plans.find((currentPlan) => !currentPlan.isFree));
} else {
setPlan(plans[0]);
}
setSelectedWorkspace({ setSelectedWorkspace({
id: workspaceInList.id, id: workspaceInList.id,
name: workspaceInList.name, name: workspaceInList.name,
@@ -561,23 +581,6 @@ export function NewProjectPageContent({
)} )}
<div className="flex justify-end"> <div className="flex justify-end">
{showPaymentModal && (
<Modal
showModal={showPaymentModal}
close={() => {
setShowPaymentModal(false);
}}
>
<BillingPaymentMethodForm
close={() => {
setShowPaymentModal(false);
}}
onPaymentMethodAdded={handleSubmit}
workspaceId={workspace.id}
/>
</Modal>
)}
<Button <Button
type="submit" type="submit"
loading={submitState.loading} loading={submitState.loading}
@@ -597,7 +600,9 @@ export default function NewProjectPage() {
const router = useRouter(); const router = useRouter();
const user = useUserData(); const user = useUserData();
const { data, loading, error } = usePrefetchNewAppQuery(); const { data, loading, error } = usePrefetchNewAppQuery({
fetchPolicy: 'cache-and-network',
});
const { const {
data: freeAndActiveProjectsData, data: freeAndActiveProjectsData,