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 # @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 ## 0.14.6
### Patch Changes ### Patch Changes

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,7 +19,7 @@ export function UnlockFeatureByUpgrading({
className, className,
...props ...props
}: UnlockFeatureByUpgradingProps) { }: UnlockFeatureByUpgradingProps) {
const { openAlertDialog } = useDialog(); const { openDialog } = useDialog();
return ( return (
<div className={twMerge('flex', className)} {...props}> <div className={twMerge('flex', className)} {...props}>
@@ -29,9 +29,9 @@ export function UnlockFeatureByUpgrading({
<Button <Button
variant="borderless" variant="borderless"
onClick={() => { onClick={() => {
openAlertDialog({ openDialog({
title: 'Upgrade your plan.', title: 'Upgrade your plan.',
payload: <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, hidePrimaryAction: true,

View File

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

View File

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

View File

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

View File

@@ -15,7 +15,7 @@ export default function OverviewTopBar() {
const isPlatform = useIsPlatform(); const isPlatform = useIsPlatform();
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject(); const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
const isPro = !currentProject?.plan?.isFree; const isPro = !currentProject?.plan?.isFree;
const { openAlertDialog } = useDialog(); const { openDialog } = useDialog();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
if (!isPlatform) { if (!isPlatform) {
@@ -92,9 +92,9 @@ export default function OverviewTopBar() {
variant="borderless" variant="borderless"
className="mr-2" className="mr-2"
onClick={() => { onClick={() => {
openAlertDialog({ openDialog({
title: 'Upgrade your plan.', title: 'Upgrade your plan.',
payload: <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, hidePrimaryAction: true,

View File

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

View File

@@ -1,7 +1,10 @@
import Form from '@/components/common/Form'; import Form from '@/components/common/Form';
import SettingsContainer from '@/components/settings/SettingsContainer'; import SettingsContainer from '@/components/settings/SettingsContainer';
import { useUI } from '@/context/UIContext'; import { useUI } from '@/context/UIContext';
import { useUpdateAppMutation } from '@/generated/graphql'; import {
GetAllWorkspacesAndProjectsDocument,
useUpdateApplicationMutation,
} from '@/generated/graphql';
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
import { Alert } from '@/ui/Alert'; import { Alert } from '@/ui/Alert';
import Input from '@/ui/v2/Input'; import Input from '@/ui/v2/Input';
@@ -23,7 +26,7 @@ export interface DeploymentBranchFormValues {
export default function DeploymentBranchSettings() { export default function DeploymentBranchSettings() {
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateApp] = useUpdateAppMutation(); const [updateApp] = useUpdateApplicationMutation();
const client = useApolloClient(); const client = useApolloClient();
const form = useForm<DeploymentBranchFormValues>({ const form = useForm<DeploymentBranchFormValues>({
@@ -46,7 +49,7 @@ export default function DeploymentBranchSettings() {
) => { ) => {
const updateAppMutation = updateApp({ const updateAppMutation = updateApp({
variables: { variables: {
id: currentProject.id, appId: currentProject.id,
app: { app: {
...values, ...values,
}, },
@@ -68,7 +71,9 @@ export default function DeploymentBranchSettings() {
form.reset(values); form.reset(values);
try { try {
await client.refetchQueries({ include: ['getOneUser'] }); await client.refetchQueries({
include: [GetAllWorkspacesAndProjectsDocument],
});
} catch (error) { } catch (error) {
await discordAnnounce( await discordAnnounce(
error.message || 'Error while trying to update application cache', 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 { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
import { Alert } from '@/ui/Alert'; import { Alert } from '@/ui/Alert';
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 Text from '@/ui/v2/Text'; import {
import { useDeleteWorkspaceMutation } from '@/utils/__generated__/graphql'; GetAllWorkspacesAndProjectsDocument,
useDeleteWorkspaceMutation,
} from '@/utils/__generated__/graphql';
import { getErrorMessage } from '@/utils/getErrorMessage'; 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 router from 'next/router';
import { useState } from 'react'; 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 [remove, setRemove] = useState(false);
const { closeDeleteWorkspaceModal } = useUI();
const [deleteWorkspace, { loading, error: mutationError, client }] = const [deleteWorkspace, { loading, error: mutationError, client }] =
useDeleteWorkspaceMutation(); useDeleteWorkspaceMutation();
@@ -22,66 +38,63 @@ export default function RemoveWorkspaceModal() {
async function handleClick() { async function handleClick() {
try { try {
await deleteWorkspace({ await toast.promise(
variables: { deleteWorkspace({
id: currentWorkspace.id, 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.`,
),
}, },
}); getToastStyleProps(),
triggerToast(`Workspace ${currentWorkspace.name} successfully deleted`); );
closeDeleteWorkspaceModal();
} catch (error) { } catch (error) {
// TODO: Display error to user and use a logging solution // TODO: Display error to user and use a logging solution
return; return;
} }
await onSubmit?.();
await router.push('/'); await router.push('/');
await client.refetchQueries({ include: ['getOneUser'] }); await client.refetchQueries({
include: [GetAllWorkspacesAndProjectsDocument],
});
} }
return ( return (
<Box className="w-modal rounded-lg p-6 text-left"> <Box className="grid grid-flow-row gap-4 px-6 pt-4 pb-6">
<div className="grid grid-flow-row gap-4"> <Box className="border-y py-2">
<div className="grid grid-flow-row gap-1"> <Checkbox
<Text variant="h3" component="h2"> id="accept-remove"
Delete Workspace label={`I'm sure I want to delete ${currentWorkspace.name}`}
</Text> 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 className="grid grid-flow-row gap-2">
</div> {mutationError && (
<Alert severity="error">{getErrorMessage(mutationError)}</Alert>
)}
<Box className="border-y py-2"> <Button
<Checkbox color="error"
id="accept-remove" onClick={handleClick}
label={`I'm sure I want to delete ${currentWorkspace.name}`} disabled={!remove || !!mutationError}
className="py-2" className=""
checked={remove} loading={loading}
onChange={(_event, checked) => setRemove(checked)} >
aria-label="Confirm Delete Workspace" Delete
/> </Button>
</Box>
<div className="grid grid-flow-row gap-2"> <Button variant="outlined" color="secondary" onClick={onCancel}>
{mutationError && ( Cancel
<Alert severity="error">{getErrorMessage(mutationError)}</Alert> </Button>
)}
<Button
color="error"
onClick={handleClick}
disabled={!remove || !!mutationError}
className=""
loading={loading}
>
Delete Workspace
</Button>
<Button
variant="outlined"
color="secondary"
onClick={closeDeleteWorkspaceModal}
>
Cancel
</Button>
</div>
</div> </div>
</Box> </Box>
); );

View File

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

View File

@@ -3,11 +3,6 @@ import type { PropsWithChildren } from 'react';
import { createContext, useContext, useMemo, useReducer } from 'react'; import { createContext, useContext, useMemo, useReducer } from 'react';
export interface UIContextState { export interface UIContextState {
newWorkspace: boolean;
modal: boolean;
deleteApplicationModal: boolean;
deleteWorkspaceModal: boolean;
resourcesCollapsible: boolean;
paymentModal: boolean; paymentModal: boolean;
/** /**
* Determines whether or not the dashboard is in maintenance mode. * Determines whether or not the dashboard is in maintenance mode.
@@ -19,23 +14,14 @@ export interface UIContextState {
maintenanceEndDate: Date; maintenanceEndDate: Date;
openPaymentModal: () => void; openPaymentModal: () => void;
closePaymentModal: () => void; closePaymentModal: () => void;
openDeleteWorkspaceModal: () => void;
closeDeleteWorkspaceModal: () => void;
} }
const initialState: UIContextState = { const initialState: UIContextState = {
newWorkspace: false,
modal: false,
deleteApplicationModal: false,
deleteWorkspaceModal: false,
resourcesCollapsible: true,
paymentModal: false, paymentModal: false,
maintenanceActive: false, maintenanceActive: false,
maintenanceEndDate: null, maintenanceEndDate: null,
openPaymentModal: () => {}, openPaymentModal: () => {},
closePaymentModal: () => {}, closePaymentModal: () => {},
openDeleteWorkspaceModal: () => {},
closeDeleteWorkspaceModal: () => {},
}; };
export const UIContext = createContext<UIContextState>(initialState); export const UIContext = createContext<UIContextState>(initialState);
@@ -44,12 +30,6 @@ UIContext.displayName = 'UIContext';
function sideReducer(state: any, action: any) { function sideReducer(state: any, action: any) {
switch (action.type) { switch (action.type) {
case 'TOGGLE_DELETE_WORKSPACE_MODAL': {
return {
...state,
deleteWorkspaceModal: !state.deleteWorkspaceModal,
};
}
case 'TOGGLE_PAYMENT_MODAL': { case 'TOGGLE_PAYMENT_MODAL': {
return { return {
...state, ...state,
@@ -67,10 +47,6 @@ export function UIProvider(props: PropsWithChildren<unknown>) {
const openPaymentModal = () => dispatch({ type: 'TOGGLE_PAYMENT_MODAL' }); const openPaymentModal = () => dispatch({ type: 'TOGGLE_PAYMENT_MODAL' });
const closePaymentModal = () => 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 = const maintenanceUnlocked =
process.env.NEXT_PUBLIC_MAINTENANCE_UNLOCK_SECRET && process.env.NEXT_PUBLIC_MAINTENANCE_UNLOCK_SECRET &&
@@ -80,8 +56,6 @@ export function UIProvider(props: PropsWithChildren<unknown>) {
const value: UIContextState = useMemo( const value: UIContextState = useMemo(
() => ({ () => ({
...state, ...state,
openDeleteWorkspaceModal,
closeDeleteWorkspaceModal,
openPaymentModal, openPaymentModal,
closePaymentModal, closePaymentModal,
maintenanceActive: maintenanceUnlocked 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 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!) { subscription ScheduledOrPendingDeploymentsSub($appId: uuid!) {
deployments( deployments(
where: { deploymentStatus: { _in: ["SCHEDULED"] }, appId: { _eq: $appId } } 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, GetApplicationStateQuery,
GetApplicationStateQueryVariables, GetApplicationStateQueryVariables,
} from '@/utils/__generated__/graphql'; } from '@/utils/__generated__/graphql';
import { useGetApplicationStateQuery } from '@/utils/__generated__/graphql'; import {
GetAllWorkspacesAndProjectsDocument,
useGetApplicationStateQuery,
} from '@/utils/__generated__/graphql';
import type { QueryHookOptions } from '@apollo/client'; import type { QueryHookOptions } from '@apollo/client';
import { useEffect } from 'react'; import { useEffect } from 'react';
@@ -31,7 +34,7 @@ export default function useProjectRedirectWhenReady(
useEffect(() => { useEffect(() => {
async function updateOwnCache() { async function updateOwnCache() {
await client.refetchQueries({ 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 { import {
GetAllWorkspacesAndProjectsDocument, GetAllWorkspacesAndProjectsDocument,
GetOneUserDocument,
useGetApplicationStateQuery, useGetApplicationStateQuery,
} from '@/generated/graphql'; } from '@/generated/graphql';
import useIsPlatform from '@/hooks/common/useIsPlatform'; import useIsPlatform from '@/hooks/common/useIsPlatform';
@@ -21,33 +20,33 @@ type ApplicationStateMetadata = {
* it will update the entire cache with the application state. * it will update the entire cache with the application state.
*/ */
export function useCheckProvisioning() { export function useCheckProvisioning() {
const { currentWorkspace } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [currentApplicationState, setCurrentApplicationState] = const [currentApplicationState, setCurrentApplicationState] =
useState<ApplicationStateMetadata>({ state: ApplicationStatus.Empty }); useState<ApplicationStateMetadata>({ state: ApplicationStatus.Empty });
const isPlatform = useIsPlatform(); const isPlatform = useIsPlatform();
const { data, startPolling, stopPolling, client } = const { data, startPolling, stopPolling, client } =
useGetApplicationStateQuery({ useGetApplicationStateQuery({
variables: { appId: currentWorkspace?.id }, variables: { appId: currentProject?.id },
skip: !isPlatform || !currentWorkspace?.id, skip: !isPlatform || !currentProject?.id,
}); });
async function updateOwnCache() { async function updateOwnCache() {
await client.refetchQueries({ await client.refetchQueries({
include: [GetOneUserDocument, GetAllWorkspacesAndProjectsDocument], include: [GetAllWorkspacesAndProjectsDocument],
}); });
} }
const memoizedUpdateCache = useCallback(updateOwnCache, [client]); const memoizedUpdateCache = useCallback(updateOwnCache, [client]);
const currentApplicationId = currentWorkspace?.id; const currentApplicationId = currentProject?.id;
useEffect(() => { useEffect(() => {
startPolling(2000); startPolling(2000);
}, [startPolling]); }, [startPolling]);
useEffect(() => { useEffect(() => {
if (!data) { if (!data?.app) {
return; 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. * The error if any.
*/ */
error?: Error; error?: Error;
/**
* Refetch the query.
*/
refetch: (options?: any) => Promise<any>;
} }
export default function useCurrentWorkspaceAndProject(): UseCurrentWorkspaceAndProjectReturnType { 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 // 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 // where it doesn't target the Nhost backend, but the currently active project
// instead. // instead.
const { data: response, isFetching } = useQuery( const {
data: response,
isFetching,
refetch,
} = useQuery(
['currentWorkspaceAndProject', workspaceSlug, appSlug], ['currentWorkspaceAndProject', workspaceSlug, appSlug],
() => () =>
client.graphql.request<{ client.graphql.request<{
@@ -105,6 +113,7 @@ export default function useCurrentWorkspaceAndProject(): UseCurrentWorkspaceAndP
}, },
currentProject: localProject, currentProject: localProject,
loading: false, loading: false,
refetch: () => Promise.resolve(),
}; };
} }
@@ -122,5 +131,6 @@ export default function useCurrentWorkspaceAndProject(): UseCurrentWorkspaceAndP
error: response?.error error: response?.error
? new Error(error?.message || 'Unknown error occurred.') ? new Error(error?.message || 'Unknown error occurred.')
: null, : null,
refetch,
}; };
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,6 @@ import {
} from '@/components/workspace'; } from '@/components/workspace';
import { WorkspaceInvoices } from '@/components/workspace/WorkspaceInvoices'; import { WorkspaceInvoices } from '@/components/workspace/WorkspaceInvoices';
import WorkspacePaymentMethods from '@/components/workspace/WorkspacePaymentMethods'; import WorkspacePaymentMethods from '@/components/workspace/WorkspacePaymentMethods';
import { useGetAllUserWorkspacesAndApplications } from '@/hooks/useGetAllUserWorkspacesAndApplications';
import useNotFoundRedirect from '@/hooks/useNotFoundRedirect'; import useNotFoundRedirect from '@/hooks/useNotFoundRedirect';
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
import { NextSeo } from 'next-seo'; import { NextSeo } from 'next-seo';
@@ -17,7 +16,6 @@ import type { ReactElement } from 'react';
export default function WorkspaceDetailsPage() { export default function WorkspaceDetailsPage() {
const { currentWorkspace, loading } = useCurrentWorkspaceAndProject(); const { currentWorkspace, loading } = useCurrentWorkspaceAndProject();
useGetAllUserWorkspacesAndApplications(false);
useNotFoundRedirect(); useNotFoundRedirect();
if (!currentWorkspace || loading) { 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 ErrorBoundaryFallback from '@/components/common/ErrorBoundaryFallback';
import { ManagedUIContext } from '@/context/UIContext'; import { ManagedUIContext } from '@/context/UIContext';
import { UserDataProvider } from '@/context/UserDataContext';
import useIsPlatform from '@/hooks/common/useIsPlatform'; import useIsPlatform from '@/hooks/common/useIsPlatform';
import '@/styles/fonts.css'; import '@/styles/fonts.css';
import '@/styles/globals.css'; import '@/styles/globals.css';
@@ -93,26 +92,24 @@ function MyApp({
nhost={nhost} nhost={nhost}
connectToDevTools={process.env.NEXT_PUBLIC_ENV === 'dev'} connectToDevTools={process.env.NEXT_PUBLIC_ENV === 'dev'}
> >
<UserDataProvider> <ManagedUIContext>
<ManagedUIContext> <Toaster position="bottom-center" />
<Toaster position="bottom-center" />
{isPlatform && ( {isPlatform && (
<Script <Script
id="segment" id="segment"
dangerouslySetInnerHTML={{ __html: renderSnippet() }} dangerouslySetInnerHTML={{ __html: renderSnippet() }}
/> />
)} )}
<ThemeProvider <ThemeProvider
colorPreferenceStorageKey={COLOR_PREFERENCE_STORAGE_KEY} colorPreferenceStorageKey={COLOR_PREFERENCE_STORAGE_KEY}
> >
<DialogProvider> <DialogProvider>
{getLayout(<Component {...pageProps} />)} {getLayout(<Component {...pageProps} />)}
</DialogProvider> </DialogProvider>
</ThemeProvider> </ThemeProvider>
</ManagedUIContext> </ManagedUIContext>
</UserDataProvider>
</NhostApolloProvider> </NhostApolloProvider>
</NhostProvider> </NhostProvider>
</CacheProvider> </CacheProvider>

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -1,5 +1,13 @@
# @nhost/apollo # @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 ## 5.2.1
### Patch Changes ### Patch Changes

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/nhost-js", "name": "@nhost/nhost-js",
"version": "2.2.0", "version": "2.2.1",
"description": "Nhost JavaScript SDK", "description": "Nhost JavaScript SDK",
"license": "MIT", "license": "MIT",
"keywords": [ "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 * @example
* ### Without generic types
* ```ts * ```ts
* await nhost.functions.call('send-welcome-email', { email: 'joe@example.com', name: 'Joe Doe' }) * 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 * @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, url: string,
body: D | null, body?: TBody | null,
config?: NhostFunctionCallConfig config?: NhostFunctionCallConfig
): Promise<NhostFunctionCallResponse<T>> { ): Promise<NhostFunctionCallResponse<TData, TErrorMessage>> {
const headers: HeadersInit = { const headers: HeadersInit = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...this.generateAccessTokenHeaders(), ...this.generateAccessTokenHeaders(),
@@ -69,15 +100,30 @@ export class NhostFunctionsClient {
}) })
if (!result.ok) { 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')) { if (result.headers.get('content-type')?.includes('application/json')) {
data = await result.json() data = await result.json()
} else { } else {
data = (await result.text()) as unknown as T data = (await result.text()) as unknown as TData
} }
return { return {
@@ -89,7 +135,7 @@ export class NhostFunctionsClient {
return { return {
res: null, res: null,
error: { error: {
message: error.message, message: error.message as unknown as TErrorMessage,
status: error.name === 'AbortError' ? 0 : 500, status: error.name === 'AbortError' ? 0 : 500,
error: error.name === 'AbortError' ? 'abort-error' : 'unknown' error: error.name === 'AbortError' ? 'abort-error' : 'unknown'
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,13 @@
# @nhost/vue # @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 ## 1.13.19
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/vue", "name": "@nhost/vue",
"version": "1.13.19", "version": "1.13.20",
"description": "Nhost Vue library", "description": "Nhost Vue library",
"license": "MIT", "license": "MIT",
"keywords": [ "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 { useSelector } from '@xstate/vue'
import { Ref } from 'vue' import { Ref } from 'vue'
import { useAuthInterpreter } from './useAuthInterpreter' import { useAuthInterpreter } from './useAuthInterpreter'
/** @internal */ /** @internal */
export const useError = (type: StateErrorTypes): Ref<ErrorPayload | null> => { export const useError = (type: StateErrorTypes): Ref<AuthErrorPayload | null> => {
const service = useAuthInterpreter() const service = useAuthInterpreter()
return useSelector( return useSelector(
service.value, service.value,