Compare commits
7 Commits
@nhost/vue
...
@nhost/das
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c625317342 | ||
|
|
8ab75a4146 | ||
|
|
607f465616 | ||
|
|
668c877130 | ||
|
|
4bd870eb96 | ||
|
|
39b3161d91 | ||
|
|
ae090a6585 |
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './BillingPaymentMethodForm';
|
||||||
|
export { default as BillingPaymentMethodForm } from './BillingPaymentMethodForm';
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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} />;
|
||||||
|
|||||||
@@ -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,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user