Compare commits

..

30 Commits

Author SHA1 Message Date
Szilárd Dóró
22cdd7f8d7 Merge pull request #1834 from nhost/changeset-release/main
chore: update versions
2023-04-13 14:38:10 +02:00
github-actions[bot]
f3a91a1f76 chore: update versions 2023-04-13 11:09:10 +00:00
Szilárd Dóró
1e9b92fcf8 Merge pull request #1835 from nhost/chore/remove-user-context
chore(dashboard): cleanup unused code
2023-04-13 13:07:30 +02:00
Szilárd Dóró
6cc56066c2 chore: update changeset 2023-04-13 11:44:38 +02:00
Szilárd Dóró
a3ad84925c chore: cleanup additional GraphQL operations 2023-04-12 14:36:11 +02:00
Szilárd Dóró
b8611b6a1c chore: cleanup unused GraphQL operations 2023-04-12 14:25:41 +02:00
Szilárd Dóró
a0e3030005 chore: cleanup UIContext 2023-04-12 14:01:41 +02:00
Szilárd Dóró
0cf1f1d938 Merge branch 'main' into chore/remove-user-context 2023-04-12 13:32:25 +02:00
Szilárd Dóró
88f026066f Merge pull request #1830 from rikardwissing/patch-1
Add generateLinks instead of link and onError args
2023-04-12 13:26:13 +02:00
Szilárd Dóró
185bef878d fix: accommodate dashboard test 2023-04-12 11:56:03 +02:00
Szilárd Dóró
a1c7b00e74 chore: add changeset 2023-04-12 11:55:29 +02:00
Szilárd Dóró
6da4562e79 chore: format code 2023-04-12 11:55:22 +02:00
Szilárd Dóró
e44cfcb2f2 Merge branch 'patch-1' of https://github.com/rikardwissing/nhost into pr/1830 2023-04-12 11:50:32 +02:00
Rikard Wissing
23fabaf8a6 Check for undefined 2023-04-12 11:48:48 +02:00
Szilárd Dóró
f4dca9836f fix: block UI when user is not available 2023-04-12 11:34:47 +02:00
Szilárd Dóró
f2704ea149 chore: improve query refetch 2023-04-12 11:30:34 +02:00
Szilárd Dóró
dd1b053212 fix: don't break provisioning page 2023-04-12 10:46:37 +02:00
Szilárd Dóró
d4ccc65655 chore: add changeset 2023-04-12 10:41:23 +02:00
Szilárd Dóró
2c2570fc82 chore: cleanup unused hooks 2023-04-12 10:40:49 +02:00
Rikard Wissing
a60f26966b Update integrations/apollo/src/index.ts
Co-authored-by: Szilárd Dóró <doroszilard@gmail.com>
2023-04-12 09:52:19 +02:00
Rikard Wissing
a988de2d61 Update integrations/apollo/src/index.ts
Co-authored-by: Szilárd Dóró <doroszilard@gmail.com>
2023-04-12 09:51:58 +02:00
Rikard Wissing
de54ca460e Update integrations/apollo/src/index.ts
Co-authored-by: Szilárd Dóró <doroszilard@gmail.com>
2023-04-12 09:51:48 +02:00
Szilárd Dóró
afdffab743 Merge pull request #1831 from nhost/fix/functions-response
fix(nhost-js): don't suppress error messages
2023-04-12 09:46:02 +02:00
Szilárd Dóró
4c61520397 chore: add changeset 2023-04-12 09:09:09 +02:00
Szilárd Dóró
f02cd444d5 fix: don't break builds 2023-04-11 17:38:10 +02:00
Szilárd Dóró
7f45a51aca fix: don't break build 2023-04-11 17:30:21 +02:00
Szilárd Dóró
08e70b9df9 fix: don't suppress error messages 2023-04-11 17:21:40 +02:00
Szilárd Dóró
bfaa5b4c4a Merge branch 'main' into pr/1830 2023-04-11 14:57:39 +02:00
Rikard Wissing
a1a00b33ad Make backwards compatible
There is a behavioral change because of how customLink was implemented before. But I think this is the intended behavior.
2023-04-11 11:49:24 +02:00
Rikard Wissing
a269f4ca3f Add generateLinks instead of link and onError args
Breaking change, but makes it more versatile.
Not tested
2023-04-11 11:16:15 +02:00
80 changed files with 482 additions and 2445 deletions

View File

@@ -1,5 +1,13 @@
# @nhost/dashboard
## 0.14.7
### Patch Changes
- d4ccc656: chore: cleanup unused code
- @nhost/react-apollo@5.0.18
- @nhost/nextjs@1.13.21
## 0.14.6
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "0.14.6",
"version": "0.14.7",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -8,7 +8,7 @@
"build": "next build --no-lint",
"analyze": "ANALYZE=true pnpm build --no-lint",
"start": "next start",
"lint": "next lint --max-warnings 1",
"lint": "next lint --max-warnings 0",
"test": "vitest",
"codegen": "graphql-codegen --config graphql.config.yaml --errors-only",
"nhost:dev": "nhost dev -d",

View File

@@ -1,6 +1,5 @@
import {
GetAllWorkspacesAndProjectsDocument,
GetOneUserDocument,
useDeleteApplicationMutation,
} from '@/generated/graphql';
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
@@ -11,6 +10,7 @@ import Text from '@/ui/v2/Text';
import { copy } from '@/utils/copy';
import { getApplicationStatusString } from '@/utils/helpers';
import getServerError from '@/utils/settings/getServerError';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import { formatDistance } from 'date-fns';
import { useRouter } from 'next/router';
import { toast } from 'react-hot-toast';
@@ -18,7 +18,7 @@ import { toast } from 'react-hot-toast';
export default function ApplicationInfo() {
const { currentProject } = useCurrentWorkspaceAndProject();
const [deleteApplication] = useDeleteApplicationMutation({
refetchQueries: [GetOneUserDocument, GetAllWorkspacesAndProjectsDocument],
refetchQueries: [GetAllWorkspacesAndProjectsDocument],
});
const router = useRouter();
@@ -37,6 +37,7 @@ export default function ApplicationInfo() {
'An error occurred while deleting the project. Please try again.',
),
},
getToastStyleProps(),
);
await router.push('/');

View File

@@ -5,7 +5,6 @@ import { useDialog } from '@/components/common/DialogProvider';
import Container from '@/components/layout/Container';
import {
GetAllWorkspacesAndProjectsDocument,
GetOneUserDocument,
useGetFreeAndActiveProjectsQuery,
useUnpauseApplicationMutation,
} from '@/generated/graphql';
@@ -26,8 +25,12 @@ import { toast } from 'react-hot-toast';
import { RemoveApplicationModal } from './RemoveApplicationModal';
export default function ApplicationPaused() {
const { openAlertDialog } = useDialog();
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
const { openDialog } = useDialog();
const {
currentWorkspace,
currentProject,
refetch: refetchWorkspaceAndProject,
} = useCurrentWorkspaceAndProject();
const user = useUserData();
const isOwner = currentWorkspace.workspaceMembers.some(
({ id, type }) => id === user?.id && type === 'owner',
@@ -35,7 +38,7 @@ export default function ApplicationPaused() {
const [showDeletingModal, setShowDeletingModal] = useState(false);
const [unpauseApplication, { loading: changingApplicationStateLoading }] =
useUnpauseApplicationMutation({
refetchQueries: [GetOneUserDocument, GetAllWorkspacesAndProjectsDocument],
refetchQueries: [GetAllWorkspacesAndProjectsDocument],
});
const { data, loading } = useGetFreeAndActiveProjectsQuery({
@@ -70,6 +73,8 @@ export default function ApplicationPaused() {
},
getToastStyleProps(),
);
await refetchWorkspaceAndProject();
} catch {
// Note: The toast will handle the error.
}
@@ -118,9 +123,9 @@ export default function ApplicationPaused() {
<Button
className="mx-auto w-full max-w-[280px]"
onClick={() => {
openAlertDialog({
openDialog({
title: 'Upgrade your plan.',
payload: <ChangePlanModal />,
component: <ChangePlanModal />,
props: {
PaperProps: { className: 'p-0' },
hidePrimaryAction: true,

View File

@@ -5,7 +5,7 @@ import {
refetchGetApplicationPlanQuery,
useGetAppPlanAndGlobalPlansQuery,
useGetPaymentMethodsQuery,
useUpdateAppMutation,
useUpdateApplicationMutation,
} from '@/generated/graphql';
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
import { Modal } from '@/ui/Modal';
@@ -89,7 +89,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
const isDowngrade = currentPlan.price > selectedPlan?.price;
// graphql mutations
const [updateApp] = useUpdateAppMutation({
const [updateApp] = useUpdateApplicationMutation({
refetchQueries: [
refetchGetApplicationPlanQuery({
workspace: currentWorkspace.slug,
@@ -102,7 +102,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
const handleUpdateAppPlan = async () => {
await updateApp({
variables: {
id: app.id,
appId: app.id,
app: {
planId: selectedPlan.id,
},

View File

@@ -6,7 +6,6 @@ import Divider from '@/ui/v2/Divider';
import Text from '@/ui/v2/Text';
import {
GetAllWorkspacesAndProjectsDocument,
GetOneUserDocument,
useDeleteApplicationMutation,
} from '@/utils/__generated__/graphql';
import { discordAnnounce } from '@/utils/discordAnnounce';
@@ -47,7 +46,7 @@ export function RemoveApplicationModal({
className,
}: RemoveApplicationModalProps) {
const [deleteApplication] = useDeleteApplicationMutation({
refetchQueries: [GetOneUserDocument, GetAllWorkspacesAndProjectsDocument],
refetchQueries: [GetAllWorkspacesAndProjectsDocument],
});
const [loadingRemove, setLoadingRemove] = useState(false);
const { currentProject } = useCurrentWorkspaceAndProject();

View File

@@ -19,7 +19,7 @@ export function UnlockFeatureByUpgrading({
className,
...props
}: UnlockFeatureByUpgradingProps) {
const { openAlertDialog } = useDialog();
const { openDialog } = useDialog();
return (
<div className={twMerge('flex', className)} {...props}>
@@ -29,9 +29,9 @@ export function UnlockFeatureByUpgrading({
<Button
variant="borderless"
onClick={() => {
openAlertDialog({
openDialog({
title: 'Upgrade your plan.',
payload: <ChangePlanModal />,
component: <ChangePlanModal />,
props: {
PaperProps: { className: 'p-0 max-w-xl w-full' },
hidePrimaryAction: true,

View File

@@ -2,7 +2,7 @@ import type { EditRepositorySettingsFormData } from '@/components/applications/g
import { useDialog } from '@/components/common/DialogProvider';
import ErrorBoundaryFallback from '@/components/common/ErrorBoundaryFallback';
import GithubIcon from '@/components/icons/GithubIcon';
import { useUpdateAppMutation } from '@/generated/graphql';
import { useUpdateApplicationMutation } from '@/generated/graphql';
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
import Button from '@/ui/v2/Button';
import Text from '@/ui/v2/Text';
@@ -29,7 +29,7 @@ export function EditRepositorySettingsModal({
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateApp, { loading }] = useUpdateAppMutation();
const [updateApp, { loading }] = useUpdateApplicationMutation();
const client = useApolloClient();
@@ -40,7 +40,7 @@ export function EditRepositorySettingsModal({
if (!currentProject.githubRepository || selectedRepoId) {
await updateApp({
variables: {
id: currentProject.id,
appId: currentProject.id,
app: {
githubRepositoryId: selectedRepoId,
repositoryProductionBranch: data.productionBranch,
@@ -51,7 +51,7 @@ export function EditRepositorySettingsModal({
} else {
await updateApp({
variables: {
id: currentProject.id,
appId: currentProject.id,
app: {
repositoryProductionBranch: data.productionBranch,
nhostBaseFolder: data.repoBaseFolder,

View File

@@ -3,14 +3,14 @@ import Form from '@/components/common/Form';
import type { DialogFormProps } from '@/types/common';
import Button from '@/ui/v2/Button';
import Input from '@/ui/v2/Input';
import { slugifyString } from '@/utils/helpers';
import getServerError from '@/utils/settings/getServerError';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import {
refetchGetOneUserQuery,
GetAllWorkspacesAndProjectsDocument,
useInsertWorkspaceMutation,
useUpdateWorkspaceMutation,
} from '@/utils/__generated__/graphql';
import { slugifyString } from '@/utils/helpers';
import getServerError from '@/utils/settings/getServerError';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import { yupResolver } from '@hookform/resolvers/yup';
import { useUserData } from '@nhost/nextjs';
import { useRouter } from 'next/router';
@@ -85,11 +85,7 @@ export default function EditWorkspaceNameForm({
const currentUser = useUserData();
const [insertWorkspace, { client }] = useInsertWorkspaceMutation();
const [updateWorkspaceName] = useUpdateWorkspaceMutation({
refetchQueries: [
refetchGetOneUserQuery({
userId: currentUser.id,
}),
],
refetchQueries: [GetAllWorkspacesAndProjectsDocument],
awaitRefetchQueries: true,
ignoreResults: true,
});
@@ -196,7 +192,7 @@ export default function EditWorkspaceNameForm({
}
await client.refetchQueries({
include: ['getOneUser'],
include: [GetAllWorkspacesAndProjectsDocument],
});
// The form has been submitted, it's not dirty anymore

View File

@@ -1,4 +1,8 @@
import { useGetWorkspaceMemberInvitesToManageQuery } from '@/generated/graphql';
import {
GetAllWorkspacesAndProjectsDocument,
GetWorkspaceMemberInvitesToManageDocument,
useGetWorkspaceMemberInvitesToManageQuery,
} from '@/generated/graphql';
import useIsPlatform from '@/hooks/common/useIsPlatform';
import { useSubmitState } from '@/hooks/useSubmitState';
import Box from '@/ui/v2/Box';
@@ -114,7 +118,10 @@ export function InviteAnnounce() {
// just refetch all data
await client.refetchQueries({
include: ['getOneUser', 'getWorkspaceMemberInvitesToManage'],
include: [
GetAllWorkspacesAndProjectsDocument,
GetWorkspaceMemberInvitesToManageDocument,
],
});
setIgnoreState({

View File

@@ -4,7 +4,6 @@ import type { AuthenticatedLayoutProps } from '@/components/layout/Authenticated
import AuthenticatedLayout from '@/components/layout/AuthenticatedLayout';
import useIsPlatform from '@/hooks/common/useIsPlatform';
import useProjectRoutes from '@/hooks/common/useProjectRoutes';
import { useGetAllUserWorkspacesAndApplications } from '@/hooks/useGetAllUserWorkspacesAndApplications';
import { useNavigationVisible } from '@/hooks/useNavigationVisible';
import useNotFoundRedirect from '@/hooks/useNotFoundRedirect';
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
@@ -47,7 +46,6 @@ function ProjectLayoutContent({
),
);
useGetAllUserWorkspacesAndApplications(false);
useNotFoundRedirect();
useEffect(() => {

View File

@@ -15,7 +15,7 @@ export default function OverviewTopBar() {
const isPlatform = useIsPlatform();
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
const isPro = !currentProject?.plan?.isFree;
const { openAlertDialog } = useDialog();
const { openDialog } = useDialog();
const { maintenanceActive } = useUI();
if (!isPlatform) {
@@ -92,9 +92,9 @@ export default function OverviewTopBar() {
variant="borderless"
className="mr-2"
onClick={() => {
openAlertDialog({
openDialog({
title: 'Upgrade your plan.',
payload: <ChangePlanModal />,
component: <ChangePlanModal />,
props: {
PaperProps: { className: 'p-0 max-w-xl w-full' },
hidePrimaryAction: true,

View File

@@ -2,7 +2,10 @@ import Form from '@/components/common/Form';
import InlineCode from '@/components/common/InlineCode';
import SettingsContainer from '@/components/settings/SettingsContainer';
import { useUI } from '@/context/UIContext';
import { useUpdateAppMutation } from '@/generated/graphql';
import {
GetAllWorkspacesAndProjectsDocument,
useUpdateApplicationMutation,
} from '@/generated/graphql';
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
import { Alert } from '@/ui/Alert';
import Input from '@/ui/v2/Input';
@@ -24,7 +27,7 @@ export interface BaseDirectoryFormValues {
export default function BaseDirectorySettings() {
const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateApp] = useUpdateAppMutation();
const [updateApp] = useUpdateApplicationMutation();
const client = useApolloClient();
const form = useForm<BaseDirectoryFormValues>({
@@ -45,7 +48,7 @@ export default function BaseDirectorySettings() {
const handleBaseFolderChange = async (values: BaseDirectoryFormValues) => {
const updateAppMutation = updateApp({
variables: {
id: currentProject.id,
appId: currentProject.id,
app: {
...values,
},
@@ -67,7 +70,9 @@ export default function BaseDirectorySettings() {
form.reset(values);
try {
await client.refetchQueries({ include: ['getOneUser'] });
await client.refetchQueries({
include: [GetAllWorkspacesAndProjectsDocument],
});
} catch (error) {
await discordAnnounce(
error.message || 'Error while trying to update application cache',

View File

@@ -1,7 +1,10 @@
import Form from '@/components/common/Form';
import SettingsContainer from '@/components/settings/SettingsContainer';
import { useUI } from '@/context/UIContext';
import { useUpdateAppMutation } from '@/generated/graphql';
import {
GetAllWorkspacesAndProjectsDocument,
useUpdateApplicationMutation,
} from '@/generated/graphql';
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
import { Alert } from '@/ui/Alert';
import Input from '@/ui/v2/Input';
@@ -23,7 +26,7 @@ export interface DeploymentBranchFormValues {
export default function DeploymentBranchSettings() {
const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateApp] = useUpdateAppMutation();
const [updateApp] = useUpdateApplicationMutation();
const client = useApolloClient();
const form = useForm<DeploymentBranchFormValues>({
@@ -46,7 +49,7 @@ export default function DeploymentBranchSettings() {
) => {
const updateAppMutation = updateApp({
variables: {
id: currentProject.id,
appId: currentProject.id,
app: {
...values,
},
@@ -68,7 +71,9 @@ export default function DeploymentBranchSettings() {
form.reset(values);
try {
await client.refetchQueries({ include: ['getOneUser'] });
await client.refetchQueries({
include: [GetAllWorkspacesAndProjectsDocument],
});
} catch (error) {
await discordAnnounce(
error.message || 'Error while trying to update application cache',

View File

@@ -1,90 +0,0 @@
import { useDialog } from '@/components/common/DialogProvider';
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
import { Alert } from '@/ui/Alert';
import Button from '@/ui/v2/Button';
import Link from '@/ui/v2/Link';
import Text from '@/ui/v2/Text';
import ArrowSquareOutIcon from '@/ui/v2/icons/ArrowSquareOutIcon';
import { useConfirmProvidersUpdatedMutation } from '@/utils/__generated__/graphql';
import getServerError from '@/utils/settings/getServerError';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import { useState } from 'react';
import toast from 'react-hot-toast';
export default function ProvidersUpdatedAlert() {
const { currentProject } = useCurrentWorkspaceAndProject();
const { openAlertDialog } = useDialog();
const [confirmed, setConfirmed] = useState(true);
const [confirmProvidersUpdated] = useConfirmProvidersUpdatedMutation({
variables: { id: currentProject?.id },
});
async function handleSubmitConfirmation() {
const confirmProvidersUpdatedPromise = confirmProvidersUpdated();
await toast.promise(
confirmProvidersUpdatedPromise,
{
loading: 'Confirming...',
success: 'Your settings have been updated successfully.',
error: getServerError(
'An error occurred while trying to confirm the message.',
),
},
getToastStyleProps(),
);
setConfirmed(false);
}
function handleOpenConfirmationDialog() {
openAlertDialog({
title: 'Confirm all providers updated?',
payload: (
<Text variant="subtitle1" component="span">
Please make sure to update all providers before continuing. Your
sign-in flows might break if you don&apos;t.
</Text>
),
props: {
onPrimaryAction: handleSubmitConfirmation,
},
});
}
if (!confirmed) {
return null;
}
return (
<Alert className="grid grid-flow-row place-items-center items-center gap-2 bg-amber-500 p-4 lg:grid-flow-col lg:place-content-between">
<div className="grid grid-flow-row gap-1 text-left">
<Text className="font-semibold">
Please update the Redirect URL for all providers being used
</Text>
<Text className="text-sm+">
We are deprecating your project&apos;s old DNS name in favor of
individual DNS names for each service. Please make sure to update your
providers to use the new auth specific URL under <b>Redirect URL</b>{' '}
before the 1st of February 2023.{' '}
<Link
href="https://github.com/nhost/nhost/discussions/1319"
target="_blank"
rel="noopener noreferrer"
underline="hover"
className="font-medium"
>
Read the discussion here.
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
</Link>
</Text>
</div>
<Button variant="borderless" onClick={handleOpenConfirmationDialog}>
I have updated all Redirect URLs
</Button>
</Alert>
);
}

View File

@@ -1 +0,0 @@
export { default } from './ProvidersUpdatedAlert';

View File

@@ -1,19 +1,35 @@
import { useUI } from '@/context/UIContext';
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
import { Alert } from '@/ui/Alert';
import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button';
import Checkbox from '@/ui/v2/Checkbox';
import Text from '@/ui/v2/Text';
import { useDeleteWorkspaceMutation } from '@/utils/__generated__/graphql';
import {
GetAllWorkspacesAndProjectsDocument,
useDeleteWorkspaceMutation,
} from '@/utils/__generated__/graphql';
import { getErrorMessage } from '@/utils/getErrorMessage';
import { triggerToast } from '@/utils/toast';
import getServerError from '@/utils/settings/getServerError';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import router from 'next/router';
import { useState } from 'react';
import { toast } from 'react-hot-toast';
export default function RemoveWorkspaceModal() {
export interface RemoveWorkspaceModalProps {
/**
* Function to be called when the form is submitted.
*/
onSubmit?: () => Promise<void>;
/**
* Function to be called when the operation is cancelled.
*/
onCancel?: VoidFunction;
}
export default function RemoveWorkspaceModal({
onSubmit,
onCancel,
}: RemoveWorkspaceModalProps) {
const [remove, setRemove] = useState(false);
const { closeDeleteWorkspaceModal } = useUI();
const [deleteWorkspace, { loading, error: mutationError, client }] =
useDeleteWorkspaceMutation();
@@ -22,66 +38,63 @@ export default function RemoveWorkspaceModal() {
async function handleClick() {
try {
await deleteWorkspace({
variables: {
id: currentWorkspace.id,
await toast.promise(
deleteWorkspace({
variables: {
id: currentWorkspace.id,
},
}),
{
loading: 'Deleting workspace...',
success: `Workspace "${currentWorkspace.name}" has been deleted successfully.`,
error: getServerError(
`An error occurred while trying to delete the workspace "${currentWorkspace.name}". Please try again.`,
),
},
});
triggerToast(`Workspace ${currentWorkspace.name} successfully deleted`);
closeDeleteWorkspaceModal();
getToastStyleProps(),
);
} catch (error) {
// TODO: Display error to user and use a logging solution
return;
}
await onSubmit?.();
await router.push('/');
await client.refetchQueries({ include: ['getOneUser'] });
await client.refetchQueries({
include: [GetAllWorkspacesAndProjectsDocument],
});
}
return (
<Box className="w-modal rounded-lg p-6 text-left">
<div className="grid grid-flow-row gap-4">
<div className="grid grid-flow-row gap-1">
<Text variant="h3" component="h2">
Delete Workspace
</Text>
<Box className="grid grid-flow-row gap-4 px-6 pt-4 pb-6">
<Box className="border-y py-2">
<Checkbox
id="accept-remove"
label={`I'm sure I want to delete ${currentWorkspace.name}`}
className="py-2"
checked={remove}
onChange={(_event, checked) => setRemove(checked)}
aria-label="Confirm Delete Workspace"
/>
</Box>
<Text>There is no way to recover this workspace later.</Text>
</div>
<div className="grid grid-flow-row gap-2">
{mutationError && (
<Alert severity="error">{getErrorMessage(mutationError)}</Alert>
)}
<Box className="border-y py-2">
<Checkbox
id="accept-remove"
label={`I'm sure I want to delete ${currentWorkspace.name}`}
className="py-2"
checked={remove}
onChange={(_event, checked) => setRemove(checked)}
aria-label="Confirm Delete Workspace"
/>
</Box>
<Button
color="error"
onClick={handleClick}
disabled={!remove || !!mutationError}
className=""
loading={loading}
>
Delete
</Button>
<div className="grid grid-flow-row gap-2">
{mutationError && (
<Alert severity="error">{getErrorMessage(mutationError)}</Alert>
)}
<Button
color="error"
onClick={handleClick}
disabled={!remove || !!mutationError}
className=""
loading={loading}
>
Delete Workspace
</Button>
<Button
variant="outlined"
color="secondary"
onClick={closeDeleteWorkspaceModal}
>
Cancel
</Button>
</div>
<Button variant="outlined" color="secondary" onClick={onCancel}>
Cancel
</Button>
</div>
</Box>
);

View File

@@ -1,10 +1,8 @@
import { useDialog } from '@/components/common/DialogProvider';
import { EditWorkspaceNameForm } from '@/components/home/EditWorkspaceNameForm';
import RemoveWorkspaceModal from '@/components/workspace/RemoveWorkspaceModal';
import { useUI } from '@/context/UIContext';
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
import { Avatar } from '@/ui/Avatar';
import { Modal } from '@/ui/Modal';
import Button from '@/ui/v2/Button';
import Divider from '@/ui/v2/Divider';
import { Dropdown } from '@/ui/v2/Dropdown';
@@ -16,12 +14,6 @@ import Image from 'next/image';
export default function WorkspaceHeader() {
const { currentWorkspace } = useCurrentWorkspaceAndProject();
const {
openDeleteWorkspaceModal,
closeDeleteWorkspaceModal,
deleteWorkspaceModal,
} = useUI();
const { openDialog } = useDialog();
const user = nhost.auth.getUser();
@@ -36,11 +28,6 @@ export default function WorkspaceHeader() {
return (
<div className="mx-auto flex max-w-3xl flex-col">
<Modal
showModal={deleteWorkspaceModal}
close={closeDeleteWorkspaceModal}
Component={RemoveWorkspaceModal}
/>
<div className="flex flex-row place-content-between">
<div className="flex flex-row items-center">
{IS_DEFAULT_WORKSPACE &&
@@ -98,7 +85,7 @@ export default function WorkspaceHeader() {
</Dropdown.Trigger>
<Dropdown.Content
PaperProps={{ className: 'mt-1 w-[280px]' }}
PaperProps={{ className: 'mt-1 max-w-[280px]' }}
menu
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
@@ -125,7 +112,7 @@ export default function WorkspaceHeader() {
});
}}
>
Change workspace name
Change Workspace Name
</Dropdown.Item>
<Divider component="li" sx={{ margin: 0 }} />
@@ -133,18 +120,34 @@ export default function WorkspaceHeader() {
<Dropdown.Item
className="grid grid-flow-row whitespace-pre-wrap py-2 font-medium"
disabled={!noApplications}
onClick={openDeleteWorkspaceModal}
onClick={() =>
openDialog({
title: (
<span className="grid grid-flow-row">
<span>Delete Workspace</span>
<Text variant="subtitle1" component="span">
There is no way to recover this workspace later.
</Text>
</span>
),
component: <RemoveWorkspaceModal />,
props: {
titleProps: { className: '!pb-0' },
},
})
}
sx={{ color: 'error.main' }}
>
I want to remove this workspace
Delete Workspace
{!noApplications && (
<Text
variant="caption"
className="font-medium"
color="disabled"
>
You can&apos;t remove this workspace because you have apps
running. Remove all apps first.
You can&apos;t delete this workspace because you have
projects running. Delete all projects first.
</Text>
)}
</Dropdown.Item>

View File

@@ -3,11 +3,6 @@ import type { PropsWithChildren } from 'react';
import { createContext, useContext, useMemo, useReducer } from 'react';
export interface UIContextState {
newWorkspace: boolean;
modal: boolean;
deleteApplicationModal: boolean;
deleteWorkspaceModal: boolean;
resourcesCollapsible: boolean;
paymentModal: boolean;
/**
* Determines whether or not the dashboard is in maintenance mode.
@@ -19,23 +14,14 @@ export interface UIContextState {
maintenanceEndDate: Date;
openPaymentModal: () => void;
closePaymentModal: () => void;
openDeleteWorkspaceModal: () => void;
closeDeleteWorkspaceModal: () => void;
}
const initialState: UIContextState = {
newWorkspace: false,
modal: false,
deleteApplicationModal: false,
deleteWorkspaceModal: false,
resourcesCollapsible: true,
paymentModal: false,
maintenanceActive: false,
maintenanceEndDate: null,
openPaymentModal: () => {},
closePaymentModal: () => {},
openDeleteWorkspaceModal: () => {},
closeDeleteWorkspaceModal: () => {},
};
export const UIContext = createContext<UIContextState>(initialState);
@@ -44,12 +30,6 @@ UIContext.displayName = 'UIContext';
function sideReducer(state: any, action: any) {
switch (action.type) {
case 'TOGGLE_DELETE_WORKSPACE_MODAL': {
return {
...state,
deleteWorkspaceModal: !state.deleteWorkspaceModal,
};
}
case 'TOGGLE_PAYMENT_MODAL': {
return {
...state,
@@ -67,10 +47,6 @@ export function UIProvider(props: PropsWithChildren<unknown>) {
const openPaymentModal = () => dispatch({ type: 'TOGGLE_PAYMENT_MODAL' });
const closePaymentModal = () => dispatch({ type: 'TOGGLE_PAYMENT_MODAL' });
const openDeleteWorkspaceModal = () =>
dispatch({ type: 'TOGGLE_DELETE_WORKSPACE_MODAL' });
const closeDeleteWorkspaceModal = () =>
dispatch({ type: 'TOGGLE_DELETE_WORKSPACE_MODAL' });
const maintenanceUnlocked =
process.env.NEXT_PUBLIC_MAINTENANCE_UNLOCK_SECRET &&
@@ -80,8 +56,6 @@ export function UIProvider(props: PropsWithChildren<unknown>) {
const value: UIContextState = useMemo(
() => ({
...state,
openDeleteWorkspaceModal,
closeDeleteWorkspaceModal,
openPaymentModal,
closePaymentModal,
maintenanceActive: maintenanceUnlocked

View File

@@ -1,69 +0,0 @@
import type { Workspace } from '@/types/workspace';
import type { PropsWithChildren } from 'react';
import { createContext, useContext, useMemo, useState } from 'react';
type Metadata = {
lastWorkspace: string;
template?: string;
};
type UserContextData = {
workspaces: Workspace[];
metadata?: Metadata;
};
export type UserDataContent = {
userContext: UserContextData;
setUserContext: (d: UserContextData) => void;
};
export const UserDataContext = createContext<UserDataContent>({
userContext: {
workspaces: [],
metadata: { lastWorkspace: '' },
},
setUserContext: () => {},
});
export interface UserDataProviderProps {
/**
* Initial workspaces to be used in the context.
*/
initialWorkspaces?: Workspace[];
/**
* Initial metadata to be used in the context.
*/
initialMetadata?: Record<string, any>;
}
export function UserDataProvider({
children,
initialWorkspaces,
initialMetadata,
}: PropsWithChildren<UserDataProviderProps>) {
const [userContext, setUserContext] = useState({
workspaces: initialWorkspaces || [],
metadata: initialMetadata || {},
});
const value = useMemo(
() => ({ userContext, setUserContext }),
[userContext, setUserContext],
);
return (
// @ts-ignore
<UserDataContext.Provider value={value}>
{children}
</UserDataContext.Provider>
);
}
export const useUserDataContext = () => {
const context = useContext(UserDataContext);
if (context === undefined) {
throw new Error(`useUserDataContext must be used under a UserDataProvider`);
}
return context;
};

View File

@@ -1,12 +0,0 @@
query getAllAppsWhere($where: apps_bool_exp!) {
apps(where: $where) {
id
name
slug
workspace {
id
name
slug
}
}
}

View File

@@ -1,43 +0,0 @@
fragment GetAppByWorkspaceAndName on apps {
updatedAt
id
slug
subdomain
name
createdAt
isProvisioned
providersUpdated
githubRepository {
id
name
githubAppInstallation {
id
accountLogin
}
}
repositoryProductionBranch
githubRepositoryId
region {
countryCode
city
}
workspace {
name
slug
id
}
workspaceId
config(resolve: true) {
hasura {
adminSecret
}
}
}
query getAppByWorkspaceAndName($workspace: String!, $slug: String!) {
apps(
where: { workspace: { slug: { _eq: $workspace } }, slug: { _eq: $slug } }
) {
...GetAppByWorkspaceAndName
}
}

View File

@@ -1,8 +0,0 @@
query getApps {
apps(order_by: { createdAt: desc }) {
id
slug
name
subdomain
}
}

View File

@@ -1,10 +0,0 @@
query getAppProvisionStatus($workspace: String!, $slug: String!) {
apps(
where: { workspace: { slug: { _eq: $workspace } }, slug: { _eq: $slug } }
) {
id
isProvisioned
subdomain
createdAt
}
}

View File

@@ -1,5 +0,0 @@
mutation updateApp($id: uuid!, $app: apps_set_input!) {
updateApp(pk_columns: { id: $id }, _set: $app) {
id
}
}

View File

@@ -9,17 +9,6 @@ fragment DeploymentRow on deployments {
commitMessage
}
query getDeployments($id: uuid!, $limit: Int!, $offset: Int!) {
deployments(
where: { appId: { _eq: $id } }
order_by: { deploymentStartedAt: desc }
limit: $limit
offset: $offset
) {
...DeploymentRow
}
}
subscription ScheduledOrPendingDeploymentsSub($appId: uuid!) {
deployments(
where: { deploymentStatus: { _in: ["SCHEDULED"] }, appId: { _eq: $appId } }

View File

@@ -1,5 +0,0 @@
mutation insertFeatureFlag($flag: featureFlags_insert_input!) {
insertFeatureFlag(object: $flag){
id
}
}

View File

@@ -1,5 +0,0 @@
mutation deleteFiles($fileIds: [uuid!]!) {
deleteFiles(where: { id: { _in: $fileIds } }) {
affected_rows
}
}

View File

@@ -1,13 +0,0 @@
mutation changePaymentMethod(
$workspaceId: uuid!
$paymentMethod: paymentMethods_insert_input!
) {
# delete all cards on the current workspace
deletePaymentMethods(where: { workspaceId: { _eq: $workspaceId } }) {
affected_rows
}
# add new
insertPaymentMethod(object: $paymentMethod) {
id
}
}

View File

@@ -1,30 +0,0 @@
query getPlans {
plans(order_by: { sort: asc }) {
id
name
isFree
price
isDefault
}
regions {
id
isGdprCompliant
city
country {
name
continent {
name
}
}
}
workspaces {
id
name
slug
paymentMethods {
id
cardBrand
cardLast4
}
}
}

View File

@@ -1,10 +0,0 @@
query getRemoteAppFilesUsage {
filesAggregate {
aggregate {
count
sum {
size
}
}
}
}

View File

@@ -1,54 +0,0 @@
fragment GetRemoteAppUser on users {
id
createdAt
displayName
locale
avatarUrl
email
emailVerified
passwordHash
locale
disabled
phoneNumber
phoneNumberVerified
defaultRole
roles {
role
}
userProviders {
id
provider {
id
}
}
}
fragment GetRemoteAppUserAuthRoles on authRoles {
role
}
query getRemoteAppUser($id: uuid!) {
user(id: $id) {
...GetRemoteAppUser
}
authRoles {
role
}
}
query getRemoteAppUserWhere($where: users_bool_exp!) {
users(where: $where) {
id
displayName
email
defaultRole
}
}
query getRemoteAppById($id: uuid!) {
user(id: $id) {
id
displayName
email
}
}

View File

@@ -1,5 +0,0 @@
mutation confirmProvidersUpdated($id: uuid!) {
updateApp(pk_columns: { id: $id }, _set: { providersUpdated: true }) {
id
}
}

View File

@@ -1,20 +0,0 @@
query getAllUserData {
workspaceMembers {
id
workspace {
id
name
creatorUserId
apps {
id
name
subdomain
config(resolve: true) {
hasura {
adminSecret
}
}
}
}
}
}

View File

@@ -1,6 +0,0 @@
query GetAvatar($userId: uuid!) {
user(id: $userId) {
id
avatarUrl
}
}

View File

@@ -1,22 +0,0 @@
query getOneUser($userId: uuid!) {
user(id: $userId) {
id
displayName
avatarUrl
workspaceMembers {
id
userId
workspaceId
type
workspace {
creatorUserId
id
slug
name
apps {
...Project
}
}
}
}
}

View File

@@ -1,20 +0,0 @@
query getUserAllWorkspaces {
workspaceMembers {
id
userId
workspace {
id
name
slug
apps {
id
name
plan {
id
name
}
slug
}
}
}
}

View File

@@ -1,14 +0,0 @@
query getAppsByWorkspace($workspace_id: uuid!) {
workspace(id: $workspace_id) {
id
name
slug
apps {
name
plan {
id
name
}
}
}
}

View File

@@ -1,5 +0,0 @@
query getWorkspaceInvoices($id: uuid!) {
workspace(id: $id) {
id
}
}

View File

@@ -1,15 +0,0 @@
query getWorkspaceSettings($id: uuid!) {
workspace(id: $id) {
id
name
addressLine1
addressLine2
addressPostalCode
addressPostalCode
addressCity
addressState
addressCountryCode
companyName
email
}
}

View File

@@ -1,49 +0,0 @@
fragment GetWorkspace on workspaces {
id
name
email
companyName
addressLine1
addressLine2
addressPostalCode
addressCity
addressCountryCode
slug
taxIdType
taxIdValue
apps {
id
name
slug
createdAt
workspace {
id
slug
}
}
paymentMethods {
id
cardBrand
cardLast4
stripePaymentMethodId
}
workspaceMembers {
id
user {
id
}
type
}
}
query getWorkspace($id: uuid!) {
workspace(id: $id) {
...GetWorkspace
}
}
query getWorkspaceWhere($where: workspaces_bool_exp!) {
workspaces(where: $where) {
...GetWorkspace
}
}

View File

@@ -1,16 +0,0 @@
query GetWorkspacesAppsById($workspaceId: uuid!) {
workspace(id: $workspaceId) {
id
slug
apps {
id
name
slug
updatedAt
plan {
id
name
}
}
}
}

View File

@@ -1,9 +0,0 @@
query getWorkspaces {
workspaces(order_by: { name: asc }) {
id
createdAt
name
slug
creatorUserId
}
}

View File

@@ -4,7 +4,10 @@ import type {
GetApplicationStateQuery,
GetApplicationStateQueryVariables,
} from '@/utils/__generated__/graphql';
import { useGetApplicationStateQuery } from '@/utils/__generated__/graphql';
import {
GetAllWorkspacesAndProjectsDocument,
useGetApplicationStateQuery,
} from '@/utils/__generated__/graphql';
import type { QueryHookOptions } from '@apollo/client';
import { useEffect } from 'react';
@@ -31,7 +34,7 @@ export default function useProjectRedirectWhenReady(
useEffect(() => {
async function updateOwnCache() {
await client.refetchQueries({
include: ['getOneUser'],
include: [GetAllWorkspacesAndProjectsDocument],
});
}

View File

@@ -1,38 +0,0 @@
import { useGetUserAllWorkspacesQuery } from '@/utils/__generated__/graphql';
import { useEffect, useState } from 'react';
function checkForApplicationsOnAllWorkspaces(workspaces, setNoApplications) {
let noApplications = true;
workspaces.forEach(({ workspace }) => {
if (noApplications && workspace.apps.length !== 0) {
noApplications = false;
}
});
setNoApplications(noApplications);
}
export function useCheckApplications() {
const { data, loading, error } = useGetUserAllWorkspacesQuery();
const [noApplications, setNoApplications] = useState(false);
useEffect(() => {
if (!data) {
return;
}
const { workspaceMembers } = data;
const noWorkspaces = workspaceMembers?.length === 0;
if (noWorkspaces) {
setNoApplications(true);
}
checkForApplicationsOnAllWorkspaces(workspaceMembers, setNoApplications);
}, [data, loading, noApplications, setNoApplications]);
return { data, loading, error, noApplications };
}
export default useCheckApplications;

View File

@@ -1,6 +1,5 @@
import {
GetAllWorkspacesAndProjectsDocument,
GetOneUserDocument,
useGetApplicationStateQuery,
} from '@/generated/graphql';
import useIsPlatform from '@/hooks/common/useIsPlatform';
@@ -21,33 +20,33 @@ type ApplicationStateMetadata = {
* it will update the entire cache with the application state.
*/
export function useCheckProvisioning() {
const { currentWorkspace } = useCurrentWorkspaceAndProject();
const { currentProject } = useCurrentWorkspaceAndProject();
const [currentApplicationState, setCurrentApplicationState] =
useState<ApplicationStateMetadata>({ state: ApplicationStatus.Empty });
const isPlatform = useIsPlatform();
const { data, startPolling, stopPolling, client } =
useGetApplicationStateQuery({
variables: { appId: currentWorkspace?.id },
skip: !isPlatform || !currentWorkspace?.id,
variables: { appId: currentProject?.id },
skip: !isPlatform || !currentProject?.id,
});
async function updateOwnCache() {
await client.refetchQueries({
include: [GetOneUserDocument, GetAllWorkspacesAndProjectsDocument],
include: [GetAllWorkspacesAndProjectsDocument],
});
}
const memoizedUpdateCache = useCallback(updateOwnCache, [client]);
const currentApplicationId = currentWorkspace?.id;
const currentApplicationId = currentProject?.id;
useEffect(() => {
startPolling(2000);
}, [startPolling]);
useEffect(() => {
if (!data) {
if (!data?.app) {
return;
}

View File

@@ -1,124 +0,0 @@
import { useUserDataContext } from '@/context/UserDataContext';
import { useGetOneUserLazyQuery } from '@/generated/graphql';
import type { Workspace } from '@/types/workspace';
import { nhost } from '@/utils/nhost';
import { useEffect, useState } from 'react';
import useIsPlatform from './common/useIsPlatform';
import { useWithin } from './useWithin';
export type UserData = {
workspaces: Workspace[] | [];
};
export function useGetAllUserWorkspacesAndApplications(
fromState: boolean = false,
) {
const { userContext, setUserContext } = useUserDataContext();
const [userData, setUserData] = useState<UserData | null>(null);
const isPlatform = useIsPlatform();
const { within } = useWithin();
const user = nhost.auth.getUser();
const [getAllUserData, { loading, data, called }] = useGetOneUserLazyQuery({
variables: {
userId: user?.id,
},
});
useEffect(() => {
if (data || !isPlatform) {
return;
}
getAllUserData();
}, [data, isPlatform, getAllUserData]);
// TODO: This useEffect should be broken down into multiple smaller parts
// because dependency array is not expandable with the necessary dependencies
// in its current form.
useEffect(() => {
if (data && userData && userData.workspaces.length !== 0) {
return;
}
if (within && !data) {
return;
}
if (
within &&
data &&
data.user?.workspaceMembers &&
data.user?.workspaceMembers.length === 0
) {
return;
}
if (
data?.user?.workspaceMembers &&
data?.user?.workspaceMembers.length !== 0
) {
const workspaces = data.user.workspaceMembers.map(({ workspace }) => {
// note: this could be rather defined by the infrastructure when
// creating the initial workspace
const isDefaultWorkspace =
workspace.name.toLowerCase() === 'default workspace' &&
workspace.creatorUserId === user?.id &&
/default-workspace-[a-z]+/i.test(workspace.slug);
return {
id: workspace.id,
name: workspace.name,
slug: workspace.slug,
creatorUserId: workspace.creatorUserId,
default: isDefaultWorkspace,
members: data.user.workspaceMembers.filter(
({ workspaceId }) => workspaceId === workspace.id,
),
applications: workspace.apps.map((app) => {
const userContextAppProps: any = {
users: 0,
userMetrics: {
growth: 0,
difference: 0,
growthPercentage: 0,
totalUsers: 0,
},
dbSize: 0,
};
if (userContext.workspaces?.length > 0) {
const currentWorkspace = userContext.workspaces.find(
(x) => x.id === workspace.id,
);
const currentApp = currentWorkspace?.applications.find(
(x) => x.id === app.id,
);
if (currentWorkspace && currentApp) {
return {
...app,
};
}
}
return {
...app,
...userContextAppProps,
};
}),
} as Workspace;
});
if (fromState) {
setUserData({ workspaces });
} else {
setUserContext({ workspaces, metadata: userContext.metadata });
}
}
}, [data, setUserData, called]);
return { userData, setUserData, getAllUserData, loading, data, called };
}

View File

@@ -1,15 +0,0 @@
import { useApolloClient } from '@apollo/client';
export function useLazyRefetchUserData() {
const client = useApolloClient();
const refetchUserData = async () => {
await client.refetchQueries({
include: ['getOneUser'],
});
};
return { refetchUserData };
}
export default useLazyRefetchUserData;

View File

@@ -24,6 +24,10 @@ export interface UseCurrentWorkspaceAndProjectReturnType {
* The error if any.
*/
error?: Error;
/**
* Refetch the query.
*/
refetch: (options?: any) => Promise<any>;
}
export default function useCurrentWorkspaceAndProject(): UseCurrentWorkspaceAndProjectReturnType {
@@ -39,7 +43,11 @@ export default function useCurrentWorkspaceAndProject(): UseCurrentWorkspaceAndP
// We can't use the hook exported by the codegen here because there are cases
// where it doesn't target the Nhost backend, but the currently active project
// instead.
const { data: response, isFetching } = useQuery(
const {
data: response,
isFetching,
refetch,
} = useQuery(
['currentWorkspaceAndProject', workspaceSlug, appSlug],
() =>
client.graphql.request<{
@@ -105,6 +113,7 @@ export default function useCurrentWorkspaceAndProject(): UseCurrentWorkspaceAndP
},
currentProject: localProject,
loading: false,
refetch: () => Promise.resolve(),
};
}
@@ -122,5 +131,6 @@ export default function useCurrentWorkspaceAndProject(): UseCurrentWorkspaceAndP
error: response?.error
? new Error(error?.message || 'Unknown error occurred.')
: null,
refetch,
};
}

View File

@@ -30,6 +30,7 @@ export default function AppIndexPage() {
return <ApplicationLive />;
case ApplicationStatus.Errored:
return <ApplicationErrored />;
case ApplicationStatus.Pausing:
case ApplicationStatus.Paused:
return <ApplicationPaused />;
case ApplicationStatus.Unpausing:

View File

@@ -7,7 +7,6 @@ import SettingsLayout from '@/components/settings/SettingsLayout';
import { useUI } from '@/context/UIContext';
import {
GetAllWorkspacesAndProjectsDocument,
GetOneUserDocument,
useDeleteApplicationMutation,
usePauseApplicationMutation,
useUpdateApplicationMutation,
@@ -44,11 +43,11 @@ export default function SettingsGeneralPage() {
const client = useApolloClient();
const [pauseApplication] = usePauseApplicationMutation({
variables: { appId: currentProject?.id },
refetchQueries: [GetOneUserDocument],
refetchQueries: [GetAllWorkspacesAndProjectsDocument],
});
const [deleteApplication] = useDeleteApplicationMutation({
variables: { appId: currentProject?.id },
refetchQueries: [GetOneUserDocument],
refetchQueries: [GetAllWorkspacesAndProjectsDocument],
});
const router = useRouter();
const { maintenanceActive } = useUI();
@@ -118,7 +117,7 @@ export default function SettingsGeneralPage() {
`/${currentWorkspace.slug}/${newProjectSlug}/settings/general`,
);
await client.refetchQueries({
include: [GetOneUserDocument, GetAllWorkspacesAndProjectsDocument],
include: [GetAllWorkspacesAndProjectsDocument],
});
} catch (error) {
await discordAnnounce(

View File

@@ -7,7 +7,7 @@ import DeploymentBranchSettings from '@/components/settings/git/DeploymentBranch
import SettingsContainer from '@/components/settings/SettingsContainer';
import SettingsLayout from '@/components/settings/SettingsLayout';
import { useUI } from '@/context/UIContext';
import { useUpdateAppMutation } from '@/generated/graphql';
import { useUpdateApplicationMutation } from '@/generated/graphql';
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button';
@@ -24,7 +24,7 @@ export default function SettingsGitPage() {
const { openAlertDialog } = useDialog();
const client = useApolloClient();
const [updateApp] = useUpdateAppMutation();
const [updateApp] = useUpdateApplicationMutation();
return (
<Container
@@ -73,7 +73,7 @@ export default function SettingsGitPage() {
onPrimaryAction: async () => {
await updateApp({
variables: {
id: currentProject.id,
appId: currentProject.id,
app: {
githubRepositoryId: null,
},

View File

@@ -10,7 +10,6 @@ import GitHubProviderSettings from '@/components/settings/signInMethods/GitHubPr
import GoogleProviderSettings from '@/components/settings/signInMethods/GoogleProviderSettings';
import LinkedInProviderSettings from '@/components/settings/signInMethods/LinkedInProviderSettings';
import MagicLinkSettings from '@/components/settings/signInMethods/MagicLinkSettings';
import ProvidersUpdatedAlert from '@/components/settings/signInMethods/ProvidersUpdatedAlert';
import SMSSettings from '@/components/settings/signInMethods/SMSSettings';
import SpotifyProviderSettings from '@/components/settings/signInMethods/SpotifyProviderSettings';
import TwitchProviderSettings from '@/components/settings/signInMethods/TwitchProviderSettings';
@@ -55,7 +54,6 @@ export default function SettingsSignInMethodsPage() {
<WebAuthnSettings />
<AnonymousSignInSettings />
<SMSSettings />
{!currentProject.providersUpdated && <ProvidersUpdatedAlert />}
<AppleProviderSettings />
<AzureADProviderSettings />
<DiscordProviderSettings />

View File

@@ -8,7 +8,6 @@ import {
} from '@/components/workspace';
import { WorkspaceInvoices } from '@/components/workspace/WorkspaceInvoices';
import WorkspacePaymentMethods from '@/components/workspace/WorkspacePaymentMethods';
import { useGetAllUserWorkspacesAndApplications } from '@/hooks/useGetAllUserWorkspacesAndApplications';
import useNotFoundRedirect from '@/hooks/useNotFoundRedirect';
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
import { NextSeo } from 'next-seo';
@@ -17,7 +16,6 @@ import type { ReactElement } from 'react';
export default function WorkspaceDetailsPage() {
const { currentWorkspace, loading } = useCurrentWorkspaceAndProject();
useGetAllUserWorkspacesAndApplications(false);
useNotFoundRedirect();
if (!currentWorkspace || loading) {

View File

@@ -1,7 +1,6 @@
import DialogProvider from '@/components/common/DialogProvider/DialogProvider';
import { DialogProvider } from '@/components/common/DialogProvider';
import ErrorBoundaryFallback from '@/components/common/ErrorBoundaryFallback';
import { ManagedUIContext } from '@/context/UIContext';
import { UserDataProvider } from '@/context/UserDataContext';
import useIsPlatform from '@/hooks/common/useIsPlatform';
import '@/styles/fonts.css';
import '@/styles/globals.css';
@@ -93,26 +92,24 @@ function MyApp({
nhost={nhost}
connectToDevTools={process.env.NEXT_PUBLIC_ENV === 'dev'}
>
<UserDataProvider>
<ManagedUIContext>
<Toaster position="bottom-center" />
<ManagedUIContext>
<Toaster position="bottom-center" />
{isPlatform && (
<Script
id="segment"
dangerouslySetInnerHTML={{ __html: renderSnippet() }}
/>
)}
{isPlatform && (
<Script
id="segment"
dangerouslySetInnerHTML={{ __html: renderSnippet() }}
/>
)}
<ThemeProvider
colorPreferenceStorageKey={COLOR_PREFERENCE_STORAGE_KEY}
>
<DialogProvider>
{getLayout(<Component {...pageProps} />)}
</DialogProvider>
</ThemeProvider>
</ManagedUIContext>
</UserDataProvider>
<ThemeProvider
colorPreferenceStorageKey={COLOR_PREFERENCE_STORAGE_KEY}
>
<DialogProvider>
{getLayout(<Component {...pageProps} />)}
</DialogProvider>
</ThemeProvider>
</ManagedUIContext>
</NhostApolloProvider>
</NhostProvider>
</CacheProvider>

View File

@@ -42,7 +42,7 @@ export default function IndexPage() {
stopPolling();
}, [data?.workspaces, stopPolling]);
if (!data && loading) {
if ((!data && loading) || !user) {
return <LoadingScreen />;
}

View File

@@ -3,8 +3,6 @@ import AuthenticatedLayout from '@/components/layout/AuthenticatedLayout';
import Container from '@/components/layout/Container';
import { useUI } from '@/context/UIContext';
import features from '@/data/features.json';
import { useGetAllUserWorkspacesAndApplications } from '@/hooks/useGetAllUserWorkspacesAndApplications';
import { useLazyRefetchUserData } from '@/hooks/useLazyRefetchUserData';
import { useSubmitState } from '@/hooks/useSubmitState';
import { Alert } from '@/ui/Alert';
import { Modal } from '@/ui/Modal';
@@ -12,7 +10,6 @@ import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button';
import IconButton from '@/ui/v2/IconButton';
import CopyIcon from '@/ui/v2/icons/CopyIcon';
import Input from '@/ui/v2/Input';
import InputAdornment from '@/ui/v2/InputAdornment';
import Option from '@/ui/v2/Option';
@@ -22,7 +19,19 @@ import Select from '@/ui/v2/Select';
import type { TextProps } from '@/ui/v2/Text';
import Text from '@/ui/v2/Text';
import Tooltip from '@/ui/v2/Tooltip';
import CopyIcon from '@/ui/v2/icons/CopyIcon';
import { MAX_FREE_PROJECTS } from '@/utils/CONSTANTS';
import type {
PrefetchNewAppPlansFragment,
PrefetchNewAppRegionsFragment,
PrefetchNewAppWorkspaceFragment,
} from '@/utils/__generated__/graphql';
import {
GetAllWorkspacesAndProjectsDocument,
useGetFreeAndActiveProjectsQuery,
useInsertApplicationMutation,
usePrefetchNewAppQuery,
} from '@/utils/__generated__/graphql';
import { copy } from '@/utils/copy';
import { getErrorMessage } from '@/utils/getErrorMessage';
import { getCurrentEnvironment } from '@/utils/helpers';
@@ -31,16 +40,6 @@ import generateRandomDatabasePassword from '@/utils/settings/generateRandomDatab
import { resetDatabasePasswordValidationSchema } from '@/utils/settings/resetDatabasePasswordValidationSchema';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import { triggerToast } from '@/utils/toast';
import type {
PrefetchNewAppPlansFragment,
PrefetchNewAppRegionsFragment,
PrefetchNewAppWorkspaceFragment,
} from '@/utils/__generated__/graphql';
import {
useGetFreeAndActiveProjectsQuery,
useInsertApplicationMutation,
usePrefetchNewAppQuery,
} from '@/utils/__generated__/graphql';
import type { ApolloError } from '@apollo/client';
import { useUserData } from '@nhost/nextjs';
import Image from 'next/image';
@@ -70,8 +69,6 @@ export function NewProjectPageContent({
}: NewAppPageProps) {
const { maintenanceActive } = useUI();
const router = useRouter();
// pre hook
useGetAllUserWorkspacesAndApplications();
// form
const [name, setName] = useState('');
@@ -111,8 +108,9 @@ export function NewProjectPageContent({
// graphql mutations
const [insertApp] = useInsertApplicationMutation({});
const { refetchUserData } = useLazyRefetchUserData();
const [insertApp] = useInsertApplicationMutation({
refetchQueries: [GetAllWorkspacesAndProjectsDocument],
});
// options
const workspaceOptions = workspaces.map((workspace) => ({
@@ -220,7 +218,6 @@ export function NewProjectPageContent({
getToastStyleProps(),
);
await refetchUserData();
await router.push(`/${selectedWorkspace.slug}/${slug}`);
} catch (error) {
setSubmitState({

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,6 @@
import { DialogProvider } from '@/components/common/DialogProvider';
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
import { ManagedUIContext } from '@/context/UIContext';
import { UserDataProvider } from '@/context/UserDataContext';
import createTheme from '@/ui/v2/createTheme';
import { createHttpLink } from '@apollo/client';
import { CacheProvider } from '@emotion/react';
@@ -104,18 +103,18 @@ function Providers({ children }: PropsWithChildren<{}>) {
<NhostProvider nhost={nhost} initial={mockSession}>
<NhostApolloProvider
nhost={nhost}
link={createHttpLink({
uri: 'https://local.graphql.nhost.run/v1',
})}
generateLinks={() => [
createHttpLink({
uri: 'https://local.graphql.nhost.run/v1',
}),
]}
>
<UserDataProvider>
<ManagedUIContext>
<Toaster position="bottom-center" />
<ThemeProvider theme={theme}>
<DialogProvider>{children}</DialogProvider>
</ThemeProvider>
</ManagedUIContext>
</UserDataProvider>
<ManagedUIContext>
<Toaster position="bottom-center" />
<ThemeProvider theme={theme}>
<DialogProvider>{children}</DialogProvider>
</ThemeProvider>
</ManagedUIContext>
</NhostApolloProvider>
</NhostProvider>
</CacheProvider>

View File

@@ -1,4 +1,5 @@
import type { ApolloClient, ApolloQueryResult } from '@apollo/client';
import { GetAllWorkspacesAndProjectsDocument } from './__generated__/graphql';
/**
* This function will refetch the main query we use for the cache
@@ -9,7 +10,7 @@ export async function updateOwnCache(
client: ApolloClient<any>,
): Promise<ApolloQueryResult<any>[]> {
return client.refetchQueries({
include: ['getOneUser'],
include: [GetAllWorkspacesAndProjectsDocument],
});
}

View File

@@ -1,5 +1,13 @@
# @nhost/apollo
## 5.2.2
### Patch Changes
- a1c7b00e: chore(links): add support for `generateLinks`
- Updated dependencies [08e70b9d]
- @nhost/nhost-js@2.2.1
## 5.2.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/apollo",
"version": "5.2.1",
"version": "5.2.2",
"description": "Nhost Apollo Client library",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,6 @@
import {
ApolloClient,
ApolloLink,
createHttpLink,
from,
InMemoryCache,
@@ -23,8 +24,15 @@ export type NhostApolloClientOptions = {
fetchPolicy?: WatchQueryFetchPolicy
connectToDevTools?: boolean
cache?: InMemoryCache
/**
* @deprecated Please use `generateLinks` instead.
*/
onError?: RequestHandler
link?: ApolloClient<any>['link']
/**
* @deprecated Please use `generateLinks` instead.
*/
link?: ApolloLink
generateLinks?: (links: (ApolloLink | RequestHandler)[]) => (ApolloLink | RequestHandler)[]
}
export const createApolloClient = ({
@@ -36,7 +44,8 @@ export const createApolloClient = ({
cache = new InMemoryCache(),
connectToDevTools = isBrowser && process.env.NODE_ENV === 'development',
onError,
link: customLink
link: customLink,
generateLinks
}: NhostApolloClientOptions) => {
const backendUrl = graphqlUrl || nhost?.graphql.httpUrl
@@ -106,7 +115,7 @@ export const createApolloClient = ({
}
})).concat(createHttpLink({ uri }))
const link = wsLink
const splitLink = wsLink
? split(
({ query }) => {
const mainDefinition = getMainDefinition(query)
@@ -124,6 +133,20 @@ export const createApolloClient = ({
)
: httpLink
const links = []
if (onError) {
links.push(onError)
}
if (customLink) {
links.push(customLink)
}
links.push(splitLink)
const link = from(generateLinks ? generateLinks(links) : links)
const client = new ApolloClient({
cache: cache || new InMemoryCache(),
ssrMode: !isBrowser,
@@ -133,9 +156,7 @@ export const createApolloClient = ({
}
},
connectToDevTools,
link: customLink
? from([customLink])
: from(typeof onError === 'function' ? [onError, link] : [link])
link
})
interpreter?.onTransition(async (state, event) => {

View File

@@ -1,5 +1,13 @@
# @nhost/react-apollo
## 5.0.18
### Patch Changes
- Updated dependencies [a1c7b00e]
- @nhost/apollo@5.2.2
- @nhost/react@2.0.15
## 5.0.17
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/react-apollo",
"version": "5.0.17",
"version": "5.0.18",
"description": "Nhost React Apollo client",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,11 @@
# @nhost/react-urql
## 2.0.16
### Patch Changes
- @nhost/react@2.0.15
## 2.0.15
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/react-urql",
"version": "2.0.15",
"version": "2.0.16",
"description": "Nhost React URQL client",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,11 @@
# @nhost/nextjs
## 1.13.21
### Patch Changes
- @nhost/react@2.0.15
## 1.13.20
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/nextjs",
"version": "1.13.20",
"version": "1.13.21",
"description": "Nhost NextJS library",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,11 @@
# @nhost/nhost-js
## 2.2.1
### Patch Changes
- 08e70b9d: fix(functions): show more detailed error messages
## 2.2.0
### Minor Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/nhost-js",
"version": "2.2.0",
"version": "2.2.1",
"description": "Nhost JavaScript SDK",
"license": "MIT",
"keywords": [

View File

@@ -39,20 +39,51 @@ export class NhostFunctionsClient {
}
/**
* Use `nhost.functions.call` to call (sending a POST request to) a serverless function.
* Use `nhost.functions.call` to call (sending a POST request to) a serverless function. Use generic
* types to specify the expected response data, request body and error message.
*
* @example
* ### Without generic types
* ```ts
* await nhost.functions.call('send-welcome-email', { email: 'joe@example.com', name: 'Joe Doe' })
* ```
*
* @example
* ### Using generic types
* ```ts
* type Data = {
* message: string
* }
*
* type Body = {
* email: string
* name: string
* }
*
* type ErrorMessage = {
* details: string
* }
*
* // The function will only accept a body of type `Body`
* const { res, error } = await nhost.functions.call<Data, Body, ErrorMessage>(
* 'send-welcome-email',
* { email: 'joe@example.com', name: 'Joe Doe' }
* )
*
* // Now the response data is typed as `Data`
* console.log(res?.data.message)
*
* // Now the error message is typed as `ErrorMessage`
* console.log(error?.message.details)
* ```
*
* @docs https://docs.nhost.io/reference/javascript/nhost-js/functions/call
*/
async call<T = unknown, D = any>(
async call<TData = unknown, TBody = any, TErrorMessage = any>(
url: string,
body: D | null,
body?: TBody | null,
config?: NhostFunctionCallConfig
): Promise<NhostFunctionCallResponse<T>> {
): Promise<NhostFunctionCallResponse<TData, TErrorMessage>> {
const headers: HeadersInit = {
'Content-Type': 'application/json',
...this.generateAccessTokenHeaders(),
@@ -69,15 +100,30 @@ export class NhostFunctionsClient {
})
if (!result.ok) {
throw new Error(result.statusText)
let message: TErrorMessage
if (result.headers.get('content-type')?.includes('application/json')) {
message = await result.json()
} else {
message = (await result.text()) as unknown as TErrorMessage
}
return {
res: null,
error: {
message,
error: result.statusText,
status: result.status
}
}
}
let data: T
let data: TData
if (result.headers.get('content-type')?.includes('application/json')) {
data = await result.json()
} else {
data = (await result.text()) as unknown as T
data = (await result.text()) as unknown as TData
}
return {
@@ -89,7 +135,7 @@ export class NhostFunctionsClient {
return {
res: null,
error: {
message: error.message,
message: error.message as unknown as TErrorMessage,
status: error.name === 'AbortError' ? 0 : 500,
error: error.name === 'AbortError' ? 'abort-error' : 'unknown'
}

View File

@@ -11,10 +11,10 @@ export interface NhostFunctionsConstructorParams {
adminSecret?: string
}
export type NhostFunctionCallResponse<T = unknown> =
export type NhostFunctionCallResponse<TData = unknown, TErrorMessage = any> =
| {
res: {
data: T
data: TData
status: number
statusText: string
}
@@ -22,7 +22,7 @@ export type NhostFunctionCallResponse<T = unknown> =
}
| {
res: null
error: ErrorPayload
error: ErrorPayload<TErrorMessage>
}
/** Subset of RequestInit parameters that are supported by the functions client */

View File

@@ -1,10 +1,10 @@
import { NhostAuthConstructorParams } from '@nhost/hasura-auth-js'
// TODO shared with other packages
export interface ErrorPayload {
export interface ErrorPayload<TMessage = any> {
error: string
status: number
message: string
message: TMessage
}
// TODO shared with other packages

View File

@@ -1,5 +1,12 @@
# @nhost/react
## 2.0.15
### Patch Changes
- Updated dependencies [08e70b9d]
- @nhost/nhost-js@2.2.1
## 2.0.14
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/react",
"version": "2.0.14",
"version": "2.0.15",
"description": "Nhost React library",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,13 @@
# @nhost/vue
## 1.13.20
### Patch Changes
- 4c615203: fix(hooks): use correct return type for `useError`
- Updated dependencies [08e70b9d]
- @nhost/nhost-js@2.2.1
## 1.13.19
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/vue",
"version": "1.13.19",
"version": "1.13.20",
"description": "Nhost Vue library",
"license": "MIT",
"keywords": [

View File

@@ -1,10 +1,10 @@
import { ErrorPayload, StateErrorTypes } from '@nhost/nhost-js'
import { AuthErrorPayload, StateErrorTypes } from '@nhost/nhost-js'
import { useSelector } from '@xstate/vue'
import { Ref } from 'vue'
import { useAuthInterpreter } from './useAuthInterpreter'
/** @internal */
export const useError = (type: StateErrorTypes): Ref<ErrorPayload | null> => {
export const useError = (type: StateErrorTypes): Ref<AuthErrorPayload | null> => {
const service = useAuthInterpreter()
return useSelector(
service.value,