Compare commits
56 Commits
@nhost/rea
...
@nhost/das
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
decb0b057c | ||
|
|
fc79b890df | ||
|
|
211eb42af5 | ||
|
|
a7398451e3 | ||
|
|
4b4f0d0150 | ||
|
|
f37e2a23e2 | ||
|
|
1a4a061284 | ||
|
|
78555c7e85 | ||
|
|
01ded8ffff | ||
|
|
3c7cf92edf | ||
|
|
bb4301fd34 | ||
|
|
c8c8948755 | ||
|
|
17e9e5899e | ||
|
|
bd22c48131 | ||
|
|
89a239ff3a | ||
|
|
0131886011 | ||
|
|
340c014fe8 | ||
|
|
bc9c8b9456 | ||
|
|
c22b2621ba | ||
|
|
726746c4d3 | ||
|
|
c431570783 | ||
|
|
445d8ef449 | ||
|
|
0f4ea18e42 | ||
|
|
dae7c5d517 | ||
|
|
f673adea00 | ||
|
|
1c6f1e3b33 | ||
|
|
d1365ea516 | ||
|
|
72dbba7881 | ||
|
|
a3f3991d5a | ||
|
|
c71fe2cf72 | ||
|
|
24c5ed3ea4 | ||
|
|
2d9145f918 | ||
|
|
9a0ab5b887 | ||
|
|
1ddf704c5b | ||
|
|
6f4ee845c6 | ||
|
|
0368663dea | ||
|
|
76ce7d7b6e | ||
|
|
538bfbcb3e | ||
|
|
07b35d1e5f | ||
|
|
2200a0ed07 | ||
|
|
df23d97126 | ||
|
|
104f149369 | ||
|
|
01228583a0 | ||
|
|
93309dd851 | ||
|
|
2cc18dcb51 | ||
|
|
3b48a62790 | ||
|
|
8897dec056 | ||
|
|
324dda8309 | ||
|
|
b755e9086c | ||
|
|
962563d6a0 | ||
|
|
8bf58ba26b | ||
|
|
0c175e7a11 | ||
|
|
70f2fbcfc2 | ||
|
|
d2c4ad3260 | ||
|
|
a9ca2c2946 | ||
|
|
d854dd74b1 |
18
.github/CODEOWNERS
vendored
18
.github/CODEOWNERS
vendored
@@ -1,14 +1,14 @@
|
|||||||
# Documentation
|
# Documentation
|
||||||
# https://help.github.com/en/articles/about-code-owners
|
# https://help.github.com/en/articles/about-code-owners
|
||||||
|
|
||||||
/packages @plmercereau @szilarddoro
|
/packages @szilarddoro
|
||||||
/packages/docgen @szilarddoro
|
/packages/docgen @szilarddoro
|
||||||
/integrations/stripe-graphql-js @elitan
|
/integrations/stripe-graphql-js @elitan
|
||||||
/.github @plmercereau
|
/.github @szilarddoro
|
||||||
/dashboard/ @szilarddoro @guicurcio
|
/dashboard/ @szilarddoro
|
||||||
/docs/ @guicurcio @elitan
|
/docs/ @elitan
|
||||||
/config/ @plmercereau @szilarddoro
|
/config/ @szilarddoro
|
||||||
/examples/ @plmercereau
|
/examples/ @szilarddoro
|
||||||
/examples/codegen-react-apollo @elitan @plmercereau
|
/examples/codegen-react-apollo @elitan @szilarddoro
|
||||||
/examples/codegen-react-query @elitan @plmercereau
|
/examples/codegen-react-query @elitan @szilarddoro
|
||||||
/examples/react-apollo-crm @elitan @plmercereau
|
/examples/react-apollo-crm @elitan @szilarddoro
|
||||||
|
|||||||
@@ -1,5 +1,63 @@
|
|||||||
# @nhost/dashboard
|
# @nhost/dashboard
|
||||||
|
|
||||||
|
## 0.11.20
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 4b4f0d01: chore(dashboard): improve dialog management
|
||||||
|
|
||||||
|
## 0.11.19
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@5.0.6
|
||||||
|
- @nhost/nextjs@1.13.11
|
||||||
|
|
||||||
|
## 0.11.18
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 01318860: fix(nhost-js): use correct URL for functions requests
|
||||||
|
- Updated dependencies [01318860]
|
||||||
|
- @nhost/react-apollo@5.0.5
|
||||||
|
- @nhost/nextjs@1.13.10
|
||||||
|
|
||||||
|
## 0.11.17
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- f673adea: fix(dashboard): set correct Content-Type for user creation
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/react-apollo` to 5.0.4
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/nextjs` to 1.13.9
|
||||||
|
- 0368663d: fix(dashboard): allow permission editing for auth and storage schemas
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- @nhost/react-apollo@5.0.4
|
||||||
|
- @nhost/nextjs@1.13.9
|
||||||
|
|
||||||
|
## 0.11.16
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- b755e908: fix(dashboard): use correct date for last seen
|
||||||
|
- 2d9145f9: chore(deps): revert GraphQL client
|
||||||
|
- 1ddf704c: fix(dashboard): don't show false positive message for failed user creation
|
||||||
|
- @nhost/react-apollo@5.0.3
|
||||||
|
- @nhost/nextjs@1.13.8
|
||||||
|
|
||||||
|
## 0.11.15
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@5.0.2
|
||||||
|
- @nhost/nextjs@1.13.7
|
||||||
|
|
||||||
|
## 0.11.14
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 2cc18dcb: fix(dashboard): prevent permission editor dropdown from being always open
|
||||||
|
|
||||||
## 0.11.13
|
## 0.11.13
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/dashboard",
|
"name": "@nhost/dashboard",
|
||||||
"version": "0.11.13",
|
"version": "0.11.20",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ function ControlledAutocomplete(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
inputValue={typeof field.value === 'string' ? field.value : undefined}
|
inputValue={
|
||||||
|
typeof field.value !== 'object' ? field.value.toString() : undefined
|
||||||
|
}
|
||||||
{...props}
|
{...props}
|
||||||
{...field}
|
{...field}
|
||||||
ref={mergeRefs([field.ref, ref])}
|
ref={mergeRefs([field.ref, ref])}
|
||||||
|
|||||||
@@ -1,31 +1,8 @@
|
|||||||
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import type { CommonDialogProps } from '@/ui/v2/Dialog';
|
import type { CommonDialogProps } from '@/ui/v2/Dialog';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactElement, ReactNode } from 'react';
|
||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
|
|
||||||
/**
|
|
||||||
* Available dialog types.
|
|
||||||
*/
|
|
||||||
export type DialogType =
|
|
||||||
| 'EDIT_WORKSPACE_NAME'
|
|
||||||
| 'CREATE_RECORD'
|
|
||||||
| 'CREATE_COLUMN'
|
|
||||||
| 'EDIT_COLUMN'
|
|
||||||
| 'CREATE_TABLE'
|
|
||||||
| 'EDIT_TABLE'
|
|
||||||
| 'EDIT_PERMISSIONS'
|
|
||||||
| 'CREATE_FOREIGN_KEY'
|
|
||||||
| 'EDIT_FOREIGN_KEY'
|
|
||||||
| 'CREATE_ROLE'
|
|
||||||
| 'EDIT_ROLE'
|
|
||||||
| 'CREATE_USER'
|
|
||||||
| 'CREATE_PERMISSION_VARIABLE'
|
|
||||||
| 'EDIT_PERMISSION_VARIABLE'
|
|
||||||
| 'CREATE_ENVIRONMENT_VARIABLE'
|
|
||||||
| 'EDIT_ENVIRONMENT_VARIABLE'
|
|
||||||
| 'EDIT_USER'
|
|
||||||
| 'EDIT_USER_PASSWORD'
|
|
||||||
| 'EDIT_JWT_SECRET';
|
|
||||||
|
|
||||||
export interface DialogConfig<TPayload = unknown> {
|
export interface DialogConfig<TPayload = unknown> {
|
||||||
/**
|
/**
|
||||||
* Title of the dialog.
|
* Title of the dialog.
|
||||||
@@ -41,21 +18,36 @@ export interface DialogConfig<TPayload = unknown> {
|
|||||||
payload?: TPayload;
|
payload?: TPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OpenDialogOptions {
|
||||||
|
/**
|
||||||
|
* Title of the dialog.
|
||||||
|
*/
|
||||||
|
title: ReactNode;
|
||||||
|
/**
|
||||||
|
* Component to render inside the dialog skeleton.
|
||||||
|
*/
|
||||||
|
component: ReactElement<{
|
||||||
|
location?: 'drawer' | 'dialog';
|
||||||
|
onCancel?: () => void;
|
||||||
|
onSubmit?: (args?: any) => Promise<any> | void;
|
||||||
|
}>;
|
||||||
|
/**
|
||||||
|
* Props to pass to the root dialog component.
|
||||||
|
*/
|
||||||
|
props?: Partial<CommonDialogProps>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DialogContextProps {
|
export interface DialogContextProps {
|
||||||
/**
|
/**
|
||||||
* Call this function to open a dialog.
|
* Call this function to open a dialog. It will automatically apply the
|
||||||
|
* necessary functionality to the dialog.
|
||||||
*/
|
*/
|
||||||
openDialog: <TPayload = unknown>(
|
openDialog: (options: OpenDialogOptions) => void;
|
||||||
type: DialogType,
|
|
||||||
config?: DialogConfig<TPayload>,
|
|
||||||
) => void;
|
|
||||||
/**
|
/**
|
||||||
* Call this function to open a drawer.
|
* Call this function to open a drawer. It will automatically apply the
|
||||||
|
* necessary functionality to the drawer.
|
||||||
*/
|
*/
|
||||||
openDrawer: <TPayload = unknown>(
|
openDrawer: (options: OpenDialogOptions) => void;
|
||||||
type: DialogType,
|
|
||||||
config?: DialogConfig<TPayload>,
|
|
||||||
) => void;
|
|
||||||
/**
|
/**
|
||||||
* Call this function to open an alert dialog.
|
* Call this function to open an alert dialog.
|
||||||
*/
|
*/
|
||||||
@@ -87,7 +79,7 @@ export interface DialogContextProps {
|
|||||||
*/
|
*/
|
||||||
onDirtyStateChange: (
|
onDirtyStateChange: (
|
||||||
isDirty: boolean,
|
isDirty: boolean,
|
||||||
location?: 'drawer' | 'dialog',
|
location?: DialogFormProps['location'],
|
||||||
) => void;
|
) => void;
|
||||||
/**
|
/**
|
||||||
* Call this function to open a dirty confirmation dialog.
|
* Call this function to open a dirty confirmation dialog.
|
||||||
|
|||||||
@@ -1,30 +1,12 @@
|
|||||||
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
|
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
|
||||||
import CreateForeignKeyForm from '@/components/dataBrowser/CreateForeignKeyForm';
|
|
||||||
import EditForeignKeyForm from '@/components/dataBrowser/EditForeignKeyForm';
|
|
||||||
import EditWorkspaceNameForm from '@/components/home/EditWorkspaceNameForm';
|
|
||||||
import CreateEnvironmentVariableForm from '@/components/settings/environmentVariables/CreateEnvironmentVariableForm';
|
|
||||||
import EditEnvironmentVariableForm from '@/components/settings/environmentVariables/EditEnvironmentVariableForm';
|
|
||||||
import EditJwtSecretForm from '@/components/settings/environmentVariables/EditJwtSecretForm';
|
|
||||||
import CreatePermissionVariableForm from '@/components/settings/permissions/CreatePermissionVariableForm';
|
|
||||||
import EditPermissionVariableForm from '@/components/settings/permissions/EditPermissionVariableForm';
|
|
||||||
import CreateRoleForm from '@/components/settings/roles/CreateRoleForm';
|
|
||||||
import EditRoleForm from '@/components/settings/roles/EditRoleForm';
|
|
||||||
import CreateUserForm from '@/components/users/CreateUserForm';
|
|
||||||
import EditUserForm from '@/components/users/EditUserForm';
|
|
||||||
import EditUserPasswordForm from '@/components/users/EditUserPasswordForm';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
|
||||||
import AlertDialog from '@/ui/v2/AlertDialog';
|
import AlertDialog from '@/ui/v2/AlertDialog';
|
||||||
import { BaseDialog } from '@/ui/v2/Dialog';
|
import { BaseDialog } from '@/ui/v2/Dialog';
|
||||||
import Drawer from '@/ui/v2/Drawer';
|
import Drawer from '@/ui/v2/Drawer';
|
||||||
import dynamic from 'next/dynamic';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import type {
|
import type { BaseSyntheticEvent, PropsWithChildren } from 'react';
|
||||||
BaseSyntheticEvent,
|
|
||||||
DetailedHTMLProps,
|
|
||||||
HTMLProps,
|
|
||||||
PropsWithChildren,
|
|
||||||
} from 'react';
|
|
||||||
import {
|
import {
|
||||||
|
cloneElement,
|
||||||
|
isValidElement,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
@@ -33,7 +15,7 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import type { DialogConfig, DialogType } from './DialogContext';
|
import type { DialogConfig, OpenDialogOptions } from './DialogContext';
|
||||||
import DialogContext from './DialogContext';
|
import DialogContext from './DialogContext';
|
||||||
import {
|
import {
|
||||||
alertDialogReducer,
|
alertDialogReducer,
|
||||||
@@ -41,67 +23,11 @@ import {
|
|||||||
drawerReducer,
|
drawerReducer,
|
||||||
} from './dialogReducers';
|
} from './dialogReducers';
|
||||||
|
|
||||||
function LoadingComponent({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> = {}) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
{...props}
|
|
||||||
className={twMerge(
|
|
||||||
'grid items-center justify-center px-6 py-4',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<ActivityIndicator
|
|
||||||
circularProgressProps={{ className: 'w-5 h-5' }}
|
|
||||||
label="Loading form..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const CreateRecordForm = dynamic(
|
|
||||||
() => import('@/components/dataBrowser/CreateRecordForm'),
|
|
||||||
{ ssr: false, loading: () => LoadingComponent() },
|
|
||||||
);
|
|
||||||
|
|
||||||
const CreateColumnForm = dynamic(
|
|
||||||
() => import('@/components/dataBrowser/CreateColumnForm'),
|
|
||||||
{ ssr: false, loading: () => LoadingComponent() },
|
|
||||||
);
|
|
||||||
|
|
||||||
const EditColumnForm = dynamic(
|
|
||||||
() => import('@/components/dataBrowser/EditColumnForm'),
|
|
||||||
{ ssr: false, loading: () => LoadingComponent() },
|
|
||||||
);
|
|
||||||
|
|
||||||
const CreateTableForm = dynamic(
|
|
||||||
() => import('@/components/dataBrowser/CreateTableForm'),
|
|
||||||
{ ssr: false, loading: () => LoadingComponent() },
|
|
||||||
);
|
|
||||||
|
|
||||||
const EditTableForm = dynamic(
|
|
||||||
() => import('@/components/dataBrowser/EditTableForm'),
|
|
||||||
{ ssr: false, loading: () => LoadingComponent() },
|
|
||||||
);
|
|
||||||
|
|
||||||
const EditPermissionsForm = dynamic(
|
|
||||||
() => import('@/components/dataBrowser/EditPermissionsForm'),
|
|
||||||
{ ssr: false, loading: () => LoadingComponent() },
|
|
||||||
);
|
|
||||||
|
|
||||||
function DialogProvider({ children }: PropsWithChildren<unknown>) {
|
function DialogProvider({ children }: PropsWithChildren<unknown>) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [
|
const [
|
||||||
{
|
{ open: dialogOpen, title: dialogTitle, activeDialog, dialogProps },
|
||||||
open: dialogOpen,
|
|
||||||
activeDialogType,
|
|
||||||
dialogProps,
|
|
||||||
title: dialogTitle,
|
|
||||||
payload: dialogPayload,
|
|
||||||
},
|
|
||||||
dialogDispatch,
|
dialogDispatch,
|
||||||
] = useReducer(dialogReducer, {
|
] = useReducer(dialogReducer, {
|
||||||
open: false,
|
open: false,
|
||||||
@@ -110,10 +36,9 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
|
|||||||
const [
|
const [
|
||||||
{
|
{
|
||||||
open: drawerOpen,
|
open: drawerOpen,
|
||||||
activeDialogType: activeDrawerType,
|
|
||||||
dialogProps: drawerProps,
|
|
||||||
title: drawerTitle,
|
title: drawerTitle,
|
||||||
payload: drawerPayload,
|
activeDialog: activeDrawer,
|
||||||
|
dialogProps: drawerProps,
|
||||||
},
|
},
|
||||||
drawerDispatch,
|
drawerDispatch,
|
||||||
] = useReducer(drawerReducer, {
|
] = useReducer(drawerReducer, {
|
||||||
@@ -136,12 +61,9 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
|
|||||||
const isDialogDirty = useRef(false);
|
const isDialogDirty = useRef(false);
|
||||||
const [showDirtyConfirmation, setShowDirtyConfirmation] = useState(false);
|
const [showDirtyConfirmation, setShowDirtyConfirmation] = useState(false);
|
||||||
|
|
||||||
const openDialog = useCallback(
|
const openDialog = useCallback((options: OpenDialogOptions) => {
|
||||||
<TConfig,>(type: DialogType, config?: DialogConfig<TConfig>) => {
|
dialogDispatch({ type: 'OPEN_DIALOG', payload: options });
|
||||||
dialogDispatch({ type: 'OPEN_DIALOG', payload: { type, config } });
|
}, []);
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const closeDialog = useCallback(() => {
|
const closeDialog = useCallback(() => {
|
||||||
dialogDispatch({ type: 'HIDE_DIALOG' });
|
dialogDispatch({ type: 'HIDE_DIALOG' });
|
||||||
@@ -152,12 +74,9 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
|
|||||||
dialogDispatch({ type: 'CLEAR_DIALOG_CONTENT' });
|
dialogDispatch({ type: 'CLEAR_DIALOG_CONTENT' });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const openDrawer = useCallback(
|
const openDrawer = useCallback((options: OpenDialogOptions) => {
|
||||||
<TConfig,>(type: DialogType, config?: DialogConfig<TConfig>) => {
|
drawerDispatch({ type: 'OPEN_DRAWER', payload: options });
|
||||||
drawerDispatch({ type: 'OPEN_DRAWER', payload: { type, config } });
|
}, []);
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const closeDrawer = useCallback(() => {
|
const closeDrawer = useCallback(() => {
|
||||||
drawerDispatch({ type: 'HIDE_DRAWER' });
|
drawerDispatch({ type: 'HIDE_DRAWER' });
|
||||||
@@ -228,9 +147,6 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
|
|||||||
[closeDialog, openDirtyConfirmation],
|
[closeDialog, openDirtyConfirmation],
|
||||||
);
|
);
|
||||||
|
|
||||||
// We are coupling this logic with the location of the dialog content which is
|
|
||||||
// not ideal. We shoule figure out a better logic for tracking the dirty
|
|
||||||
// state in the future.
|
|
||||||
const onDirtyStateChange = useCallback(
|
const onDirtyStateChange = useCallback(
|
||||||
(dirty: boolean, location: 'drawer' | 'dialog' = 'drawer') => {
|
(dirty: boolean, location: 'drawer' | 'dialog' = 'drawer') => {
|
||||||
if (location === 'dialog') {
|
if (location === 'dialog') {
|
||||||
@@ -271,25 +187,6 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const sharedDialogProps = {
|
|
||||||
...dialogPayload,
|
|
||||||
onSubmit: async (values: any) => {
|
|
||||||
await dialogPayload?.onSubmit?.(values);
|
|
||||||
|
|
||||||
closeDialog();
|
|
||||||
},
|
|
||||||
onCancel: closeDialogWithDirtyGuard,
|
|
||||||
};
|
|
||||||
|
|
||||||
const sharedDrawerProps = {
|
|
||||||
onSubmit: async () => {
|
|
||||||
await drawerPayload?.onSubmit();
|
|
||||||
|
|
||||||
closeDrawer();
|
|
||||||
},
|
|
||||||
onCancel: closeDrawerWithDirtyGuard,
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleCloseDrawerAndDialog() {
|
function handleCloseDrawerAndDialog() {
|
||||||
if (isDrawerDirty.current || isDialogDirty.current) {
|
if (isDrawerDirty.current || isDialogDirty.current) {
|
||||||
@@ -367,56 +264,20 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
|
|||||||
<RetryableErrorBoundary
|
<RetryableErrorBoundary
|
||||||
errorMessageProps={{ className: 'pt-0 pb-5 px-6' }}
|
errorMessageProps={{ className: 'pt-0 pb-5 px-6' }}
|
||||||
>
|
>
|
||||||
{activeDialogType === 'EDIT_WORKSPACE_NAME' && (
|
{isValidElement(activeDialog)
|
||||||
<EditWorkspaceNameForm {...sharedDialogProps} />
|
? cloneElement(activeDialog, {
|
||||||
)}
|
...activeDialog.props,
|
||||||
|
location: 'dialog',
|
||||||
{activeDialogType === 'CREATE_FOREIGN_KEY' && (
|
onSubmit: async (values?: any) => {
|
||||||
<CreateForeignKeyForm {...sharedDialogProps} />
|
await activeDialog?.props?.onSubmit?.(values);
|
||||||
)}
|
closeDialog();
|
||||||
|
},
|
||||||
{activeDialogType === 'EDIT_FOREIGN_KEY' && (
|
onCancel: () => {
|
||||||
<EditForeignKeyForm {...sharedDialogProps} />
|
activeDialog?.props?.onCancel?.();
|
||||||
)}
|
closeDialogWithDirtyGuard();
|
||||||
|
},
|
||||||
{activeDialogType === 'CREATE_ROLE' && (
|
})
|
||||||
<CreateRoleForm {...sharedDialogProps} />
|
: null}
|
||||||
)}
|
|
||||||
|
|
||||||
{activeDialogType === 'EDIT_ROLE' && (
|
|
||||||
<EditRoleForm {...sharedDialogProps} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeDialogType === 'CREATE_USER' && (
|
|
||||||
<CreateUserForm {...sharedDialogProps} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeDialogType === 'CREATE_PERMISSION_VARIABLE' && (
|
|
||||||
<CreatePermissionVariableForm {...sharedDialogProps} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeDialogType === 'EDIT_PERMISSION_VARIABLE' && (
|
|
||||||
<EditPermissionVariableForm {...sharedDialogProps} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeDialogType === 'CREATE_ENVIRONMENT_VARIABLE' && (
|
|
||||||
<CreateEnvironmentVariableForm {...sharedDialogProps} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeDialogType === 'EDIT_ENVIRONMENT_VARIABLE' && (
|
|
||||||
<EditEnvironmentVariableForm {...sharedDialogProps} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeDialogType === 'EDIT_USER_PASSWORD' && (
|
|
||||||
<EditUserPasswordForm
|
|
||||||
{...sharedDialogProps}
|
|
||||||
user={sharedDialogProps?.user}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeDialogType === 'EDIT_JWT_SECRET' && (
|
|
||||||
<EditJwtSecretForm {...sharedDialogProps} />
|
|
||||||
)}
|
|
||||||
</RetryableErrorBoundary>
|
</RetryableErrorBoundary>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
||||||
@@ -436,51 +297,20 @@ function DialogProvider({ children }: PropsWithChildren<unknown>) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RetryableErrorBoundary>
|
<RetryableErrorBoundary>
|
||||||
{activeDrawerType === 'CREATE_RECORD' && (
|
{isValidElement(activeDrawer)
|
||||||
<CreateRecordForm
|
? cloneElement(activeDrawer, {
|
||||||
{...sharedDrawerProps}
|
...activeDrawer.props,
|
||||||
columns={drawerPayload?.columns}
|
location: 'drawer',
|
||||||
/>
|
onSubmit: async (values?: any) => {
|
||||||
)}
|
await activeDrawer?.props?.onSubmit?.(values);
|
||||||
|
closeDrawer();
|
||||||
{activeDrawerType === 'CREATE_COLUMN' && (
|
},
|
||||||
<CreateColumnForm {...sharedDrawerProps} />
|
onCancel: () => {
|
||||||
)}
|
activeDrawer?.props?.onCancel?.();
|
||||||
|
closeDrawerWithDirtyGuard();
|
||||||
{activeDrawerType === 'EDIT_COLUMN' && (
|
},
|
||||||
<EditColumnForm
|
})
|
||||||
{...sharedDrawerProps}
|
: null}
|
||||||
column={drawerPayload?.column}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeDrawerType === 'CREATE_TABLE' && (
|
|
||||||
<CreateTableForm
|
|
||||||
{...sharedDrawerProps}
|
|
||||||
schema={drawerPayload?.schema}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeDrawerType === 'EDIT_TABLE' && (
|
|
||||||
<EditTableForm
|
|
||||||
{...sharedDrawerProps}
|
|
||||||
table={drawerPayload?.table}
|
|
||||||
schema={drawerPayload?.schema}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeDrawerType === 'EDIT_PERMISSIONS' && (
|
|
||||||
<EditPermissionsForm
|
|
||||||
{...sharedDrawerProps}
|
|
||||||
disabled={drawerPayload?.disabled}
|
|
||||||
schema={drawerPayload?.schema}
|
|
||||||
table={drawerPayload?.table}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeDrawerType === 'EDIT_USER' && (
|
|
||||||
<EditUserForm {...sharedDrawerProps} {...drawerPayload} />
|
|
||||||
)}
|
|
||||||
</RetryableErrorBoundary>
|
</RetryableErrorBoundary>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { CommonDialogProps } from '@/ui/v2/Dialog';
|
import type { CommonDialogProps } from '@/ui/v2/Dialog';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactElement, ReactNode } from 'react';
|
||||||
import type { DialogConfig, DialogType } from './DialogContext';
|
import type { DialogConfig, OpenDialogOptions } from './DialogContext';
|
||||||
|
|
||||||
export interface DialogState {
|
export interface DialogState {
|
||||||
/**
|
/**
|
||||||
@@ -12,9 +12,13 @@ export interface DialogState {
|
|||||||
*/
|
*/
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
/**
|
/**
|
||||||
* Type of the currently active dialog.
|
* Component to render inside the dialog skeleton.
|
||||||
*/
|
*/
|
||||||
activeDialogType?: DialogType;
|
activeDialog?: ReactElement<{
|
||||||
|
location?: 'drawer' | 'dialog';
|
||||||
|
onCancel?: () => void;
|
||||||
|
onSubmit?: (args?: any) => Promise<any> | void;
|
||||||
|
}>;
|
||||||
/**
|
/**
|
||||||
* Props passed to the currently active dialog.
|
* Props passed to the currently active dialog.
|
||||||
*/
|
*/
|
||||||
@@ -27,10 +31,7 @@ export interface DialogState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type DialogAction =
|
export type DialogAction =
|
||||||
| {
|
| { type: 'OPEN_DIALOG'; payload: OpenDialogOptions }
|
||||||
type: 'OPEN_DIALOG';
|
|
||||||
payload: { type: DialogType; config?: DialogConfig };
|
|
||||||
}
|
|
||||||
| { type: 'HIDE_DIALOG' }
|
| { type: 'HIDE_DIALOG' }
|
||||||
| { type: 'CLEAR_DIALOG_CONTENT' };
|
| { type: 'CLEAR_DIALOG_CONTENT' };
|
||||||
|
|
||||||
@@ -50,10 +51,9 @@ export function dialogReducer(
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
open: true,
|
open: true,
|
||||||
activeDialogType: action.payload?.type,
|
title: action.payload.title,
|
||||||
dialogProps: action.payload.config?.props,
|
activeDialog: action.payload.component,
|
||||||
title: action.payload.config?.title,
|
dialogProps: action.payload.props,
|
||||||
payload: action.payload.config?.payload,
|
|
||||||
};
|
};
|
||||||
case 'HIDE_DIALOG':
|
case 'HIDE_DIALOG':
|
||||||
return {
|
return {
|
||||||
@@ -64,8 +64,7 @@ export function dialogReducer(
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
title: undefined,
|
title: undefined,
|
||||||
payload: undefined,
|
activeDialog: undefined,
|
||||||
activeDialogType: undefined,
|
|
||||||
dialogProps: undefined,
|
dialogProps: undefined,
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
@@ -74,10 +73,7 @@ export function dialogReducer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type DrawerAction =
|
export type DrawerAction =
|
||||||
| {
|
| { type: 'OPEN_DRAWER'; payload: OpenDialogOptions }
|
||||||
type: 'OPEN_DRAWER';
|
|
||||||
payload: { type: DialogType; config?: DialogConfig };
|
|
||||||
}
|
|
||||||
| { type: 'HIDE_DRAWER' }
|
| { type: 'HIDE_DRAWER' }
|
||||||
| { type: 'CLEAR_DRAWER_CONTENT' };
|
| { type: 'CLEAR_DRAWER_CONTENT' };
|
||||||
|
|
||||||
@@ -97,10 +93,9 @@ export function drawerReducer(
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
open: true,
|
open: true,
|
||||||
activeDialogType: action.payload?.type,
|
title: action.payload.title,
|
||||||
dialogProps: action.payload.config?.props,
|
activeDialog: action.payload.component,
|
||||||
title: action.payload.config?.title,
|
dialogProps: action.payload.props,
|
||||||
payload: action.payload.config?.payload,
|
|
||||||
};
|
};
|
||||||
case 'HIDE_DRAWER':
|
case 'HIDE_DRAWER':
|
||||||
return {
|
return {
|
||||||
@@ -111,8 +106,7 @@ export function drawerReducer(
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
title: undefined,
|
title: undefined,
|
||||||
payload: undefined,
|
activeDialog: undefined,
|
||||||
activeDialogType: undefined,
|
|
||||||
dialogProps: undefined,
|
dialogProps: undefined,
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
|
import type { BoxProps } from '@/ui/v2/Box';
|
||||||
|
import Box from '@/ui/v2/Box';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
export interface FormActivityIndicatorProps extends BoxProps {}
|
||||||
|
|
||||||
|
export default function FormActivityIndicator({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: FormActivityIndicatorProps) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
{...props}
|
||||||
|
className={twMerge(
|
||||||
|
'grid items-center justify-center px-6 py-4',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ActivityIndicator
|
||||||
|
circularProgressProps={{ className: 'w-5 h-5' }}
|
||||||
|
label="Loading form..."
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './FormActivityIndicator';
|
||||||
|
export { default } from './FormActivityIndicator';
|
||||||
@@ -3,6 +3,7 @@ import ControlledCheckbox from '@/components/common/ControlledCheckbox';
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import InlineCode from '@/components/common/InlineCode';
|
import InlineCode from '@/components/common/InlineCode';
|
||||||
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import type { ColumnType, DatabaseColumn } from '@/types/dataBrowser';
|
import type { ColumnType, DatabaseColumn } from '@/types/dataBrowser';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
@@ -22,7 +23,7 @@ import ForeignKeyEditor from './ForeignKeyEditor';
|
|||||||
|
|
||||||
export type BaseColumnFormValues = DatabaseColumn;
|
export type BaseColumnFormValues = DatabaseColumn;
|
||||||
|
|
||||||
export interface BaseColumnFormProps {
|
export interface BaseColumnFormProps extends DialogFormProps {
|
||||||
/**
|
/**
|
||||||
* Function to be called when the form is submitted.
|
* Function to be called when the form is submitted.
|
||||||
*/
|
*/
|
||||||
@@ -60,6 +61,7 @@ export default function BaseColumnForm({
|
|||||||
onSubmit: handleExternalSubmit,
|
onSubmit: handleExternalSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
submitButtonText = 'Save',
|
submitButtonText = 'Save',
|
||||||
|
location,
|
||||||
}: BaseColumnFormProps) {
|
}: BaseColumnFormProps) {
|
||||||
const { onDirtyStateChange } = useDialog();
|
const { onDirtyStateChange } = useDialog();
|
||||||
|
|
||||||
@@ -91,8 +93,8 @@ export default function BaseColumnForm({
|
|||||||
const isDirty = Object.keys(dirtyFields).length > 0;
|
const isDirty = Object.keys(dirtyFields).length > 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onDirtyStateChange(isDirty, 'drawer');
|
onDirtyStateChange(isDirty, location);
|
||||||
}, [isDirty, onDirtyStateChange]);
|
}, [isDirty, location, onDirtyStateChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import type { BaseForeignKeyFormValues } from '@/components/dataBrowser/BaseForeignKeyForm';
|
import CreateForeignKeyForm from '@/components/dataBrowser/CreateForeignKeyForm';
|
||||||
|
import EditForeignKeyForm from '@/components/dataBrowser/EditForeignKeyForm';
|
||||||
import type { DatabaseColumn } from '@/types/dataBrowser';
|
import type { DatabaseColumn } from '@/types/dataBrowser';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
@@ -29,7 +30,7 @@ const ForeignKeyEditorInput = forwardRef(
|
|||||||
) => {
|
) => {
|
||||||
const { openDialog } = useDialog();
|
const { openDialog } = useDialog();
|
||||||
const { setValue } = useFormContext();
|
const { setValue } = useFormContext();
|
||||||
const column = useWatch<Partial<DatabaseColumn>>();
|
const column = useWatch() as DatabaseColumn;
|
||||||
const { foreignKeyRelation } = column;
|
const { foreignKeyRelation } = column;
|
||||||
|
|
||||||
if (!column.foreignKeyRelation) {
|
if (!column.foreignKeyRelation) {
|
||||||
@@ -39,8 +40,8 @@ const ForeignKeyEditorInput = forwardRef(
|
|||||||
className="py-1"
|
className="py-1"
|
||||||
disabled={!column.name || !column.type}
|
disabled={!column.name || !column.type}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
openDialog('CREATE_FOREIGN_KEY', {
|
openDialog({
|
||||||
title: (
|
title: (
|
||||||
<span className="grid grid-flow-row">
|
<span className="grid grid-flow-row">
|
||||||
<span>Add a Foreign Key Relation</span>
|
<span>Add a Foreign Key Relation</span>
|
||||||
@@ -51,16 +52,18 @@ const ForeignKeyEditorInput = forwardRef(
|
|||||||
</Text>
|
</Text>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
payload: {
|
component: (
|
||||||
selectedColumn: column.name,
|
<CreateForeignKeyForm
|
||||||
availableColumns: [column],
|
selectedColumn={column.name}
|
||||||
onSubmit: (values: BaseForeignKeyFormValues) => {
|
availableColumns={[column]}
|
||||||
setValue('foreignKeyRelation', values);
|
onSubmit={(values) => {
|
||||||
onCreateSubmit();
|
setValue('foreignKeyRelation', values);
|
||||||
},
|
onCreateSubmit();
|
||||||
},
|
}}
|
||||||
})
|
/>
|
||||||
}
|
),
|
||||||
|
});
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Add Foreign Key
|
Add Foreign Key
|
||||||
</Button>
|
</Button>
|
||||||
@@ -86,20 +89,22 @@ const ForeignKeyEditorInput = forwardRef(
|
|||||||
<div className="grid grid-flow-col">
|
<div className="grid grid-flow-col">
|
||||||
<Button
|
<Button
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
openDialog('EDIT_FOREIGN_KEY', {
|
openDialog({
|
||||||
title: 'Edit Foreign Key Relation',
|
title: 'Edit Foreign Key Relation',
|
||||||
payload: {
|
component: (
|
||||||
foreignKeyRelation,
|
<EditForeignKeyForm
|
||||||
availableColumns: [column],
|
foreignKeyRelation={foreignKeyRelation}
|
||||||
selectedColumn: column.name,
|
selectedColumn={column.name}
|
||||||
onSubmit: (values: BaseForeignKeyFormValues) => {
|
availableColumns={[column]}
|
||||||
setValue('foreignKeyRelation', values);
|
onSubmit={(values) => {
|
||||||
onEditSubmit();
|
setValue('foreignKeyRelation', values);
|
||||||
},
|
onEditSubmit();
|
||||||
},
|
}}
|
||||||
})
|
/>
|
||||||
}
|
),
|
||||||
|
});
|
||||||
|
}}
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
className="min-w-[initial] py-1 px-2"
|
className="min-w-[initial] py-1 px-2"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import ControlledSelect from '@/components/common/ControlledSelect';
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import useDatabaseQuery from '@/hooks/dataBrowser/useDatabaseQuery';
|
import useDatabaseQuery from '@/hooks/dataBrowser/useDatabaseQuery';
|
||||||
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import type { DatabaseColumn, ForeignKeyRelation } from '@/types/dataBrowser';
|
import type { DatabaseColumn, ForeignKeyRelation } from '@/types/dataBrowser';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
@@ -23,7 +24,7 @@ export interface BaseForeignKeyFormValues extends ForeignKeyRelation {
|
|||||||
disableOriginColumn?: boolean;
|
disableOriginColumn?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseForeignKeyFormProps {
|
export interface BaseForeignKeyFormProps extends DialogFormProps {
|
||||||
/**
|
/**
|
||||||
* Available columns in the table.
|
* Available columns in the table.
|
||||||
*/
|
*/
|
||||||
@@ -64,6 +65,7 @@ export function BaseForeignKeyForm({
|
|||||||
onSubmit: handleExternalSubmit,
|
onSubmit: handleExternalSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
submitButtonText = 'Save',
|
submitButtonText = 'Save',
|
||||||
|
location,
|
||||||
}: BaseForeignKeyFormProps) {
|
}: BaseForeignKeyFormProps) {
|
||||||
const { onDirtyStateChange } = useDialog();
|
const { onDirtyStateChange } = useDialog();
|
||||||
|
|
||||||
@@ -86,8 +88,8 @@ export function BaseForeignKeyForm({
|
|||||||
const isDirty = Object.keys(dirtyFields).length > 0;
|
const isDirty = Object.keys(dirtyFields).length > 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onDirtyStateChange(isDirty, 'dialog');
|
onDirtyStateChange(isDirty, location);
|
||||||
}, [isDirty, onDirtyStateChange]);
|
}, [isDirty, location, onDirtyStateChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import DatabaseRecordInputGroup from '@/components/dataBrowser/DatabaseRecordInputGroup';
|
import DatabaseRecordInputGroup from '@/components/dataBrowser/DatabaseRecordInputGroup';
|
||||||
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import type {
|
import type {
|
||||||
ColumnInsertOptions,
|
ColumnInsertOptions,
|
||||||
DataBrowserGridColumn,
|
DataBrowserGridColumn,
|
||||||
@@ -10,7 +11,7 @@ import Button from '@/ui/v2/Button';
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
export interface BaseRecordFormProps {
|
export interface BaseRecordFormProps extends DialogFormProps {
|
||||||
/**
|
/**
|
||||||
* The columns of the table.
|
* The columns of the table.
|
||||||
*/
|
*/
|
||||||
@@ -36,6 +37,7 @@ export default function BaseRecordForm({
|
|||||||
onSubmit: handleExternalSubmit,
|
onSubmit: handleExternalSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
submitButtonText = 'Save',
|
submitButtonText = 'Save',
|
||||||
|
location,
|
||||||
}: BaseRecordFormProps) {
|
}: BaseRecordFormProps) {
|
||||||
const { onDirtyStateChange } = useDialog();
|
const { onDirtyStateChange } = useDialog();
|
||||||
const { requiredColumns, optionalColumns } = columns.reduce(
|
const { requiredColumns, optionalColumns } = columns.reduce(
|
||||||
@@ -70,8 +72,8 @@ export default function BaseRecordForm({
|
|||||||
const isDirty = Object.keys(dirtyFields).length > 0;
|
const isDirty = Object.keys(dirtyFields).length > 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onDirtyStateChange(isDirty, 'drawer');
|
onDirtyStateChange(isDirty, location);
|
||||||
}, [isDirty, onDirtyStateChange]);
|
}, [isDirty, location, onDirtyStateChange]);
|
||||||
|
|
||||||
// Stores columns in a map to have constant time lookup. This is necessary
|
// Stores columns in a map to have constant time lookup. This is necessary
|
||||||
// for tables with many columns.
|
// for tables with many columns.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import { baseColumnValidationSchema } from '@/components/dataBrowser/BaseColumnForm';
|
import { baseColumnValidationSchema } from '@/components/dataBrowser/BaseColumnForm';
|
||||||
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import type { DatabaseTable, ForeignKeyRelation } from '@/types/dataBrowser';
|
import type { DatabaseTable, ForeignKeyRelation } from '@/types/dataBrowser';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
@@ -30,7 +31,7 @@ export interface BaseTableFormValues
|
|||||||
foreignKeyRelations?: ForeignKeyRelation[];
|
foreignKeyRelations?: ForeignKeyRelation[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseTableFormProps {
|
export interface BaseTableFormProps extends DialogFormProps {
|
||||||
/**
|
/**
|
||||||
* Function to be called when the form is submitted.
|
* Function to be called when the form is submitted.
|
||||||
*/
|
*/
|
||||||
@@ -99,7 +100,9 @@ function NameInput() {
|
|||||||
function FormFooter({
|
function FormFooter({
|
||||||
onCancel,
|
onCancel,
|
||||||
submitButtonText,
|
submitButtonText,
|
||||||
}: Pick<BaseTableFormProps, 'onCancel' | 'submitButtonText'>) {
|
location,
|
||||||
|
}: Pick<BaseTableFormProps, 'onCancel' | 'submitButtonText'> &
|
||||||
|
Pick<DialogFormProps, 'location'>) {
|
||||||
const { onDirtyStateChange } = useDialog();
|
const { onDirtyStateChange } = useDialog();
|
||||||
const { isSubmitting, dirtyFields } = useFormState();
|
const { isSubmitting, dirtyFields } = useFormState();
|
||||||
|
|
||||||
@@ -108,8 +111,8 @@ function FormFooter({
|
|||||||
const isDirty = Object.keys(dirtyFields).length > 0;
|
const isDirty = Object.keys(dirtyFields).length > 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onDirtyStateChange(isDirty, 'drawer');
|
onDirtyStateChange(isDirty, location);
|
||||||
}, [isDirty, onDirtyStateChange]);
|
}, [isDirty, location, onDirtyStateChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="grid flex-shrink-0 grid-flow-col justify-between gap-3 border-t-1 p-2">
|
<Box className="grid flex-shrink-0 grid-flow-col justify-between gap-3 border-t-1 p-2">
|
||||||
@@ -135,6 +138,7 @@ function FormFooter({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function BaseTableForm({
|
export default function BaseTableForm({
|
||||||
|
location,
|
||||||
onSubmit: handleExternalSubmit,
|
onSubmit: handleExternalSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
submitButtonText = 'Save',
|
submitButtonText = 'Save',
|
||||||
@@ -168,7 +172,11 @@ export default function BaseTableForm({
|
|||||||
<ForeignKeyEditorSection />
|
<ForeignKeyEditorSection />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FormFooter onCancel={onCancel} submitButtonText={submitButtonText} />
|
<FormFooter
|
||||||
|
onCancel={onCancel}
|
||||||
|
submitButtonText={submitButtonText}
|
||||||
|
location={location}
|
||||||
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import type { BaseForeignKeyFormValues } from '@/components/dataBrowser/BaseForeignKeyForm';
|
import type { BaseForeignKeyFormValues } from '@/components/dataBrowser/BaseForeignKeyForm';
|
||||||
|
import CreateForeignKeyForm from '@/components/dataBrowser/CreateForeignKeyForm';
|
||||||
|
import EditForeignKeyForm from '@/components/dataBrowser/EditForeignKeyForm';
|
||||||
import type { DatabaseColumn, ForeignKeyRelation } from '@/types/dataBrowser';
|
import type { DatabaseColumn, ForeignKeyRelation } from '@/types/dataBrowser';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import PlusIcon from '@/ui/v2/icons/PlusIcon';
|
import PlusIcon from '@/ui/v2/icons/PlusIcon';
|
||||||
@@ -68,18 +70,19 @@ export default function ForeignKeyEditorSection() {
|
|||||||
onEdit={() => {
|
onEdit={() => {
|
||||||
const primaryKeyIndex = getValues('primaryKeyIndex');
|
const primaryKeyIndex = getValues('primaryKeyIndex');
|
||||||
|
|
||||||
openDialog('EDIT_FOREIGN_KEY', {
|
openDialog({
|
||||||
title: 'Edit Foreign Key Relation',
|
title: 'Edit Foreign Key Relation',
|
||||||
payload: {
|
component: (
|
||||||
foreignKeyRelation: fields[index],
|
<EditForeignKeyForm
|
||||||
availableColumns: columns.map((column, columnIndex) =>
|
foreignKeyRelation={fields[index] as ForeignKeyRelation}
|
||||||
columnIndex === primaryKeyIndex
|
availableColumns={columns.map((column, columnIndex) =>
|
||||||
? { ...column, isPrimary: true }
|
columnIndex === primaryKeyIndex
|
||||||
: column,
|
? { ...column, isPrimary: true }
|
||||||
),
|
: column,
|
||||||
onSubmit: (values: BaseForeignKeyFormValues) =>
|
)}
|
||||||
handleEdit(values, index),
|
onSubmit={(values) => handleEdit(values, index)}
|
||||||
},
|
/>
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onDelete={() => remove(index)}
|
onDelete={() => remove(index)}
|
||||||
@@ -105,7 +108,7 @@ export default function ForeignKeyEditorSection() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
const primaryKeyIndex = getValues('primaryKeyIndex');
|
const primaryKeyIndex = getValues('primaryKeyIndex');
|
||||||
|
|
||||||
openDialog('CREATE_FOREIGN_KEY', {
|
openDialog({
|
||||||
title: (
|
title: (
|
||||||
<span className="grid grid-flow-row">
|
<span className="grid grid-flow-row">
|
||||||
<span>Add a Foreign Key Relation</span>
|
<span>Add a Foreign Key Relation</span>
|
||||||
@@ -116,14 +119,16 @@ export default function ForeignKeyEditorSection() {
|
|||||||
</Text>
|
</Text>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
payload: {
|
component: (
|
||||||
availableColumns: columns.map((column, index) =>
|
<CreateForeignKeyForm
|
||||||
index === primaryKeyIndex
|
availableColumns={columns.map((column, index) =>
|
||||||
? { ...column, isPrimary: true }
|
index === primaryKeyIndex
|
||||||
: column,
|
? { ...column, isPrimary: true }
|
||||||
),
|
: column,
|
||||||
onSubmit: handleCreate,
|
)}
|
||||||
},
|
onSubmit={handleCreate}
|
||||||
|
/>
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ import { useRouter } from 'next/router';
|
|||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
|
||||||
export interface CreateColumnFormProps
|
export interface CreateColumnFormProps
|
||||||
extends Pick<BaseColumnFormProps, 'onCancel'> {
|
extends Pick<BaseColumnFormProps, 'onCancel' | 'location'> {
|
||||||
/**
|
/**
|
||||||
* Function to be called when the form is submitted.
|
* Function to be called when the form is submitted.
|
||||||
*/
|
*/
|
||||||
onSubmit?: () => Promise<void>;
|
onSubmit?: (args?: any) => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CreateColumnForm({
|
export default function CreateColumnForm({
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ import { useState } from 'react';
|
|||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
|
||||||
export interface CreateForeignKeyFormProps
|
export interface CreateForeignKeyFormProps
|
||||||
extends Pick<BaseForeignKeyFormProps, 'onCancel' | 'availableColumns'> {
|
extends Pick<
|
||||||
|
BaseForeignKeyFormProps,
|
||||||
|
'onCancel' | 'availableColumns' | 'location'
|
||||||
|
> {
|
||||||
/**
|
/**
|
||||||
* Column selected by default.
|
* Column selected by default.
|
||||||
*/
|
*/
|
||||||
@@ -21,7 +24,7 @@ export interface CreateForeignKeyFormProps
|
|||||||
/**
|
/**
|
||||||
* Function to be called when the form is submitted.
|
* Function to be called when the form is submitted.
|
||||||
*/
|
*/
|
||||||
onSubmit?: (values: BaseForeignKeyFormValues) => Promise<void>;
|
onSubmit?: (values: BaseForeignKeyFormValues) => Promise<void> | void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CreateForeignKeyForm({
|
export default function CreateForeignKeyForm({
|
||||||
@@ -51,9 +54,7 @@ export default function CreateForeignKeyForm({
|
|||||||
setError(undefined);
|
setError(undefined);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (onSubmit) {
|
await onSubmit?.(values);
|
||||||
await onSubmit(values);
|
|
||||||
}
|
|
||||||
} catch (submitError) {
|
} catch (submitError) {
|
||||||
if (submitError && submitError instanceof Error) {
|
if (submitError && submitError instanceof Error) {
|
||||||
setError(submitError);
|
setError(submitError);
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ import { yupResolver } from '@hookform/resolvers/yup';
|
|||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
|
||||||
export interface CreateRecordFormProps
|
export interface CreateRecordFormProps
|
||||||
extends Pick<BaseRecordFormProps, 'columns' | 'onCancel'> {
|
extends Pick<BaseRecordFormProps, 'columns' | 'onCancel' | 'location'> {
|
||||||
/**
|
/**
|
||||||
* Function to be called when the form is submitted.
|
* Function to be called when the form is submitted.
|
||||||
*/
|
*/
|
||||||
onSubmit?: () => Promise<void>;
|
onSubmit?: (args?: any) => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CreateRecordForm({
|
export default function CreateRecordForm({
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { useRouter } from 'next/router';
|
|||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
|
||||||
export interface CreateTableFormProps
|
export interface CreateTableFormProps
|
||||||
extends Pick<BaseTableFormProps, 'onCancel'> {
|
extends Pick<BaseTableFormProps, 'onCancel' | 'location'> {
|
||||||
/**
|
/**
|
||||||
* Schema where the table should be created.
|
* Schema where the table should be created.
|
||||||
*/
|
*/
|
||||||
@@ -25,7 +25,7 @@ export interface CreateTableFormProps
|
|||||||
/**
|
/**
|
||||||
* Function to be called when the form is submitted.
|
* Function to be called when the form is submitted.
|
||||||
*/
|
*/
|
||||||
onSubmit?: () => Promise<void>;
|
onSubmit?: (args?: any) => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CreateTableForm({
|
export default function CreateTableForm({
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import DataGridDateCell from '@/components/common/DataGridDateCell';
|
|||||||
import DataGridNumericCell from '@/components/common/DataGridNumericCell';
|
import DataGridNumericCell from '@/components/common/DataGridNumericCell';
|
||||||
import DataGridTextCell from '@/components/common/DataGridTextCell';
|
import DataGridTextCell from '@/components/common/DataGridTextCell';
|
||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import FormActivityIndicator from '@/components/common/FormActivityIndicator';
|
||||||
import InlineCode from '@/components/common/InlineCode';
|
import InlineCode from '@/components/common/InlineCode';
|
||||||
import DataBrowserEmptyState from '@/components/dataBrowser/DataBrowserEmptyState';
|
import DataBrowserEmptyState from '@/components/dataBrowser/DataBrowserEmptyState';
|
||||||
import DataBrowserGridControls from '@/components/dataBrowser/DataBrowserGridControls';
|
import DataBrowserGridControls from '@/components/dataBrowser/DataBrowserGridControls';
|
||||||
@@ -28,9 +29,25 @@ import {
|
|||||||
} from '@/utils/dataBrowser/postgresqlConstants';
|
} from '@/utils/dataBrowser/postgresqlConstants';
|
||||||
import { isSchemaLocked } from '@/utils/dataBrowser/schemaHelpers';
|
import { isSchemaLocked } from '@/utils/dataBrowser/schemaHelpers';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
const CreateColumnForm = dynamic(
|
||||||
|
() => import('@/components/dataBrowser/CreateColumnForm'),
|
||||||
|
{ ssr: false, loading: () => <FormActivityIndicator /> },
|
||||||
|
);
|
||||||
|
|
||||||
|
const EditColumnForm = dynamic(
|
||||||
|
() => import('@/components/dataBrowser/EditColumnForm'),
|
||||||
|
{ ssr: false, loading: () => <FormActivityIndicator /> },
|
||||||
|
);
|
||||||
|
|
||||||
|
const CreateRecordForm = dynamic(
|
||||||
|
() => import('@/components/dataBrowser/CreateRecordForm'),
|
||||||
|
{ ssr: false, loading: () => <FormActivityIndicator /> },
|
||||||
|
);
|
||||||
|
|
||||||
export interface DataBrowserGridProps extends Partial<DataGridProps<any>> {}
|
export interface DataBrowserGridProps extends Partial<DataGridProps<any>> {}
|
||||||
|
|
||||||
export function createDataGridColumn(
|
export function createDataGridColumn(
|
||||||
@@ -273,33 +290,36 @@ export default function DataBrowserGrid({
|
|||||||
const memoizedData = useMemo(() => rows, [rows]);
|
const memoizedData = useMemo(() => rows, [rows]);
|
||||||
|
|
||||||
async function handleInsertRowClick() {
|
async function handleInsertRowClick() {
|
||||||
openDrawer('CREATE_RECORD', {
|
openDrawer({
|
||||||
title: 'Insert a New Row',
|
title: 'Insert a New Row',
|
||||||
payload: {
|
component: (
|
||||||
columns: memoizedColumns,
|
<CreateRecordForm
|
||||||
onSubmit: refetch,
|
// TODO: Create proper typings for data browser columns
|
||||||
},
|
columns={memoizedColumns as unknown as DataBrowserGridColumn[]}
|
||||||
|
onSubmit={refetch}
|
||||||
|
/>
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleInsertColumnClick() {
|
async function handleInsertColumnClick() {
|
||||||
openDrawer('CREATE_COLUMN', {
|
openDrawer({
|
||||||
title: 'Insert a New Column',
|
title: 'Insert a New Column',
|
||||||
payload: {
|
component: <CreateColumnForm onSubmit={refetch} />,
|
||||||
onSubmit: refetch,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleEditColumnClick(
|
async function handleEditColumnClick(
|
||||||
column: DataBrowserGridColumn<NormalizedQueryDataRow>,
|
column: DataBrowserGridColumn<NormalizedQueryDataRow>,
|
||||||
) {
|
) {
|
||||||
openDrawer('EDIT_COLUMN', {
|
openDrawer({
|
||||||
title: 'Edit Column',
|
title: 'Edit Column',
|
||||||
payload: {
|
component: (
|
||||||
column,
|
<EditColumnForm
|
||||||
onSubmit: () => queryClient.refetchQueries([currentTablePath]),
|
column={column}
|
||||||
},
|
onSubmit={() => queryClient.refetchQueries([currentTablePath])}
|
||||||
|
/>
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import FormActivityIndicator from '@/components/common/FormActivityIndicator';
|
||||||
import InlineCode from '@/components/common/InlineCode';
|
import InlineCode from '@/components/common/InlineCode';
|
||||||
import NavLink from '@/components/common/NavLink';
|
import NavLink from '@/components/common/NavLink';
|
||||||
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
|
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
|
||||||
@@ -31,11 +32,36 @@ import Select from '@/ui/v2/Select';
|
|||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
import { isSchemaLocked } from '@/utils/dataBrowser/schemaHelpers';
|
import { isSchemaLocked } from '@/utils/dataBrowser/schemaHelpers';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
const CreateTableForm = dynamic(
|
||||||
|
() => import('@/components/dataBrowser/CreateTableForm'),
|
||||||
|
{
|
||||||
|
ssr: false,
|
||||||
|
loading: () => <FormActivityIndicator />,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const EditTableForm = dynamic(
|
||||||
|
() => import('@/components/dataBrowser/EditTableForm'),
|
||||||
|
{
|
||||||
|
ssr: false,
|
||||||
|
loading: () => <FormActivityIndicator />,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const EditPermissionsForm = dynamic(
|
||||||
|
() => import('@/components/dataBrowser/EditPermissionsForm'),
|
||||||
|
{
|
||||||
|
ssr: false,
|
||||||
|
loading: () => <FormActivityIndicator />,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export interface DataBrowserSidebarProps extends Omit<BoxProps, 'children'> {
|
export interface DataBrowserSidebarProps extends Omit<BoxProps, 'children'> {
|
||||||
/**
|
/**
|
||||||
* Function to be called when a sidebar item is clicked.
|
* Function to be called when a sidebar item is clicked.
|
||||||
@@ -200,7 +226,7 @@ function DataBrowserSidebarContent({
|
|||||||
table: string,
|
table: string,
|
||||||
disabled?: boolean,
|
disabled?: boolean,
|
||||||
) {
|
) {
|
||||||
openDrawer('EDIT_PERMISSIONS', {
|
openDrawer({
|
||||||
title: (
|
title: (
|
||||||
<span className="inline-grid grid-flow-col items-center gap-2">
|
<span className="inline-grid grid-flow-col items-center gap-2">
|
||||||
Permissions
|
Permissions
|
||||||
@@ -208,22 +234,18 @@ function DataBrowserSidebarContent({
|
|||||||
<Chip label="Preview" size="small" color="info" component="span" />
|
<Chip label="Preview" size="small" color="info" component="span" />
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
|
component: (
|
||||||
|
<EditPermissionsForm
|
||||||
|
disabled={disabled}
|
||||||
|
schema={schema}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
|
),
|
||||||
props: {
|
props: {
|
||||||
PaperProps: {
|
PaperProps: {
|
||||||
className: 'lg:w-[65%] lg:max-w-7xl',
|
className: 'lg:w-[65%] lg:max-w-7xl',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
payload: {
|
|
||||||
onSubmit: async () => {
|
|
||||||
await queryClient.refetchQueries([
|
|
||||||
`${dataSourceSlug}.${schema}.${table}`,
|
|
||||||
]);
|
|
||||||
await refetch();
|
|
||||||
},
|
|
||||||
disabled,
|
|
||||||
schema,
|
|
||||||
table,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,9 +318,11 @@ function DataBrowserSidebarContent({
|
|||||||
endIcon={<PlusIcon />}
|
endIcon={<PlusIcon />}
|
||||||
className="mt-1 w-full justify-between px-2"
|
className="mt-1 w-full justify-between px-2"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openDrawer('CREATE_TABLE', {
|
openDrawer({
|
||||||
title: 'Create a New Table',
|
title: 'Create a New Table',
|
||||||
payload: { onSubmit: refetch, schema: selectedSchema },
|
component: (
|
||||||
|
<CreateTableForm onSubmit={refetch} schema={selectedSchema} />
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
onSidebarItemClick();
|
onSidebarItemClick();
|
||||||
@@ -328,69 +352,68 @@ function DataBrowserSidebarContent({
|
|||||||
className="group"
|
className="group"
|
||||||
key={tablePath}
|
key={tablePath}
|
||||||
secondaryAction={
|
secondaryAction={
|
||||||
!isSelectedSchemaLocked && (
|
<Dropdown.Root
|
||||||
<Dropdown.Root
|
id="table-management-menu"
|
||||||
id="table-management-menu"
|
onOpen={() => setSidebarMenuTable(tablePath)}
|
||||||
onOpen={() => setSidebarMenuTable(tablePath)}
|
onClose={() => setSidebarMenuTable(undefined)}
|
||||||
onClose={() => setSidebarMenuTable(undefined)}
|
>
|
||||||
|
<Dropdown.Trigger
|
||||||
|
asChild
|
||||||
|
hideChevron
|
||||||
|
disabled={tablePath === removableTable}
|
||||||
>
|
>
|
||||||
<Dropdown.Trigger
|
<IconButton
|
||||||
asChild
|
variant="borderless"
|
||||||
hideChevron
|
color={isSelected ? 'primary' : 'secondary'}
|
||||||
disabled={tablePath === removableTable}
|
className={twMerge(
|
||||||
|
!isSelected &&
|
||||||
|
'opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 group-active:opacity-100',
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<IconButton
|
<DotsHorizontalIcon />
|
||||||
variant="borderless"
|
</IconButton>
|
||||||
color={isSelected ? 'primary' : 'secondary'}
|
</Dropdown.Trigger>
|
||||||
className={twMerge(
|
|
||||||
!isSelected &&
|
<Dropdown.Content menu PaperProps={{ className: 'w-52' }}>
|
||||||
'opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 group-active:opacity-100',
|
{isGitHubConnected ? (
|
||||||
)}
|
<Dropdown.Item
|
||||||
|
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
|
||||||
|
onClick={() =>
|
||||||
|
handleEditPermissionClick(
|
||||||
|
table.table_schema,
|
||||||
|
table.table_name,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<DotsHorizontalIcon />
|
<UsersIcon
|
||||||
</IconButton>
|
className="h-4 w-4"
|
||||||
</Dropdown.Trigger>
|
sx={{ color: 'text.secondary' }}
|
||||||
|
/>
|
||||||
|
|
||||||
<Dropdown.Content
|
<span>View Permissions</span>
|
||||||
menu
|
</Dropdown.Item>
|
||||||
PaperProps={{ className: 'w-52' }}
|
) : (
|
||||||
>
|
[
|
||||||
{isGitHubConnected ? (
|
!isSelectedSchemaLocked && (
|
||||||
<Dropdown.Item
|
|
||||||
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
|
|
||||||
onClick={() =>
|
|
||||||
handleEditPermissionClick(
|
|
||||||
table.table_schema,
|
|
||||||
table.table_name,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<UsersIcon
|
|
||||||
className="h-4 w-4"
|
|
||||||
sx={{ color: 'text.secondary' }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span>View Permissions</span>
|
|
||||||
</Dropdown.Item>
|
|
||||||
) : (
|
|
||||||
[
|
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
key="edit-table"
|
key="edit-table"
|
||||||
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
|
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
openDrawer('EDIT_TABLE', {
|
openDrawer({
|
||||||
title: 'Edit Table',
|
title: 'Edit Table',
|
||||||
payload: {
|
component: (
|
||||||
onSubmit: async () => {
|
<EditTableForm
|
||||||
await queryClient.refetchQueries([
|
onSubmit={async () => {
|
||||||
`${dataSourceSlug}.${table.table_schema}.${table.table_name}`,
|
await queryClient.refetchQueries([
|
||||||
]);
|
`${dataSourceSlug}.${table.table_schema}.${table.table_name}`,
|
||||||
await refetch();
|
]);
|
||||||
},
|
await refetch();
|
||||||
schema: table.table_schema,
|
}}
|
||||||
table,
|
schema={table.table_schema}
|
||||||
},
|
table={table}
|
||||||
|
/>
|
||||||
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -400,32 +423,38 @@ function DataBrowserSidebarContent({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<span>Edit Table</span>
|
<span>Edit Table</span>
|
||||||
</Dropdown.Item>,
|
</Dropdown.Item>
|
||||||
|
),
|
||||||
|
!isSelectedSchemaLocked && (
|
||||||
<Divider
|
<Divider
|
||||||
key="edit-table-separator"
|
key="edit-table-separator"
|
||||||
component="li"
|
component="li"
|
||||||
/>,
|
/>
|
||||||
<Dropdown.Item
|
),
|
||||||
key="edit-permissions"
|
<Dropdown.Item
|
||||||
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
|
key="edit-permissions"
|
||||||
onClick={() =>
|
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
|
||||||
handleEditPermissionClick(
|
onClick={() =>
|
||||||
table.table_schema,
|
handleEditPermissionClick(
|
||||||
table.table_name,
|
table.table_schema,
|
||||||
)
|
table.table_name,
|
||||||
}
|
)
|
||||||
>
|
}
|
||||||
<UsersIcon
|
>
|
||||||
className="h-4 w-4"
|
<UsersIcon
|
||||||
sx={{ color: 'text.secondary' }}
|
className="h-4 w-4"
|
||||||
/>
|
sx={{ color: 'text.secondary' }}
|
||||||
|
/>
|
||||||
|
|
||||||
<span>Edit Permissions</span>
|
<span>Edit Permissions</span>
|
||||||
</Dropdown.Item>,
|
</Dropdown.Item>,
|
||||||
|
!isSelectedSchemaLocked && (
|
||||||
<Divider
|
<Divider
|
||||||
key="edit-permissions-separator"
|
key="edit-permissions-separator"
|
||||||
component="li"
|
component="li"
|
||||||
/>,
|
/>
|
||||||
|
),
|
||||||
|
!isSelectedSchemaLocked && (
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
key="delete-table"
|
key="delete-table"
|
||||||
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
|
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
|
||||||
@@ -443,12 +472,12 @@ function DataBrowserSidebarContent({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<span>Delete Table</span>
|
<span>Delete Table</span>
|
||||||
</Dropdown.Item>,
|
</Dropdown.Item>
|
||||||
]
|
),
|
||||||
)}
|
]
|
||||||
</Dropdown.Content>
|
)}
|
||||||
</Dropdown.Root>
|
</Dropdown.Content>
|
||||||
)
|
</Dropdown.Root>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ListItem.Button
|
<ListItem.Button
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { useRouter } from 'next/router';
|
|||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
|
||||||
export interface EditColumnFormProps
|
export interface EditColumnFormProps
|
||||||
extends Pick<BaseColumnFormProps, 'onCancel'> {
|
extends Pick<BaseColumnFormProps, 'onCancel' | 'location'> {
|
||||||
/**
|
/**
|
||||||
* Column to be edited.
|
* Column to be edited.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ import { useState } from 'react';
|
|||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
|
||||||
export interface EditForeignKeyFormProps
|
export interface EditForeignKeyFormProps
|
||||||
extends Pick<BaseForeignKeyFormProps, 'onCancel' | 'availableColumns'> {
|
extends Pick<
|
||||||
|
BaseForeignKeyFormProps,
|
||||||
|
'onCancel' | 'availableColumns' | 'location'
|
||||||
|
> {
|
||||||
/**
|
/**
|
||||||
* Foreign key relation to be edited.
|
* Foreign key relation to be edited.
|
||||||
*/
|
*/
|
||||||
@@ -26,7 +29,7 @@ export interface EditForeignKeyFormProps
|
|||||||
/**
|
/**
|
||||||
* Function to be called when the form is submitted.
|
* Function to be called when the form is submitted.
|
||||||
*/
|
*/
|
||||||
onSubmit?: (values: BaseForeignKeyFormValues) => Promise<void>;
|
onSubmit?: (values: BaseForeignKeyFormValues) => Promise<void> | void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function EditForeignKeyForm({
|
export default function EditForeignKeyForm({
|
||||||
@@ -57,9 +60,7 @@ export default function EditForeignKeyForm({
|
|||||||
setError(undefined);
|
setError(undefined);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (onSubmit) {
|
await onSubmit?.(values);
|
||||||
await onSubmit(values);
|
|
||||||
}
|
|
||||||
} catch (submitError) {
|
} catch (submitError) {
|
||||||
if (submitError && submitError instanceof Error) {
|
if (submitError && submitError instanceof Error) {
|
||||||
setError(submitError);
|
setError(submitError);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import useMetadataQuery from '@/hooks/dataBrowser/useMetadataQuery';
|
|||||||
import useTableQuery from '@/hooks/dataBrowser/useTableQuery';
|
import useTableQuery from '@/hooks/dataBrowser/useTableQuery';
|
||||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||||
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
||||||
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import type {
|
import type {
|
||||||
DatabaseAccessLevel,
|
DatabaseAccessLevel,
|
||||||
DatabaseAction,
|
DatabaseAction,
|
||||||
@@ -30,7 +31,7 @@ import { twMerge } from 'tailwind-merge';
|
|||||||
import RolePermissionEditorForm from './RolePermissionEditorForm';
|
import RolePermissionEditorForm from './RolePermissionEditorForm';
|
||||||
import RolePermissionsRow from './RolePermissionsRow';
|
import RolePermissionsRow from './RolePermissionsRow';
|
||||||
|
|
||||||
export interface EditPermissionsFormProps {
|
export interface EditPermissionsFormProps extends DialogFormProps {
|
||||||
/**
|
/**
|
||||||
* Determines whether the form is disabled or not.
|
* Determines whether the form is disabled or not.
|
||||||
*/
|
*/
|
||||||
@@ -54,6 +55,7 @@ export default function EditPermissionsForm({
|
|||||||
schema,
|
schema,
|
||||||
table,
|
table,
|
||||||
onCancel,
|
onCancel,
|
||||||
|
location,
|
||||||
}: EditPermissionsFormProps) {
|
}: EditPermissionsFormProps) {
|
||||||
const [role, setRole] = useState<string>();
|
const [role, setRole] = useState<string>();
|
||||||
const [action, setAction] = useState<DatabaseAction>();
|
const [action, setAction] = useState<DatabaseAction>();
|
||||||
@@ -181,6 +183,7 @@ export default function EditPermissionsForm({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<RolePermissionEditorForm
|
<RolePermissionEditorForm
|
||||||
|
location={location}
|
||||||
resourceVersion={metadata?.resourceVersion}
|
resourceVersion={metadata?.resourceVersion}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
schema={schema}
|
schema={schema}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useDialog } from '@/components/common/DialogProvider';
|
|||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import HighlightedText from '@/components/common/HighlightedText';
|
import HighlightedText from '@/components/common/HighlightedText';
|
||||||
import useManagePermissionMutation from '@/hooks/dataBrowser/useManagePermissionMutation';
|
import useManagePermissionMutation from '@/hooks/dataBrowser/useManagePermissionMutation';
|
||||||
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import type {
|
import type {
|
||||||
DatabaseAction,
|
DatabaseAction,
|
||||||
HasuraMetadataPermission,
|
HasuraMetadataPermission,
|
||||||
@@ -72,7 +73,7 @@ export interface RolePermissionEditorFormValues {
|
|||||||
computedFields?: string[];
|
computedFields?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RolePermissionEditorFormProps {
|
export interface RolePermissionEditorFormProps extends DialogFormProps {
|
||||||
/**
|
/**
|
||||||
* Determines whether or not the form is disabled.
|
* Determines whether or not the form is disabled.
|
||||||
*/
|
*/
|
||||||
@@ -169,6 +170,7 @@ export default function RolePermissionEditorForm({
|
|||||||
onCancel,
|
onCancel,
|
||||||
permission,
|
permission,
|
||||||
disabled,
|
disabled,
|
||||||
|
location,
|
||||||
}: RolePermissionEditorFormProps) {
|
}: RolePermissionEditorFormProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const {
|
const {
|
||||||
@@ -214,8 +216,8 @@ export default function RolePermissionEditorForm({
|
|||||||
const isDirty = Object.keys(dirtyFields).length > 0;
|
const isDirty = Object.keys(dirtyFields).length > 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onDirtyStateChange(isDirty, 'drawer');
|
onDirtyStateChange(isDirty, location);
|
||||||
}, [isDirty, onDirtyStateChange]);
|
}, [isDirty, location, onDirtyStateChange]);
|
||||||
|
|
||||||
async function handleSubmit(values: RolePermissionEditorFormValues) {
|
async function handleSubmit(values: RolePermissionEditorFormValues) {
|
||||||
const managePermissionPromise = managePermission({
|
const managePermissionPromise = managePermission({
|
||||||
@@ -245,7 +247,7 @@ export default function RolePermissionEditorForm({
|
|||||||
: permission?.check,
|
: permission?.check,
|
||||||
backend_only: values.backendOnly,
|
backend_only: values.backendOnly,
|
||||||
computed_fields:
|
computed_fields:
|
||||||
permission?.computed_fields.length > 0
|
permission?.computed_fields?.length > 0
|
||||||
? permission?.computed_fields
|
? permission?.computed_fields
|
||||||
: null,
|
: null,
|
||||||
},
|
},
|
||||||
@@ -261,7 +263,7 @@ export default function RolePermissionEditorForm({
|
|||||||
getToastStyleProps(),
|
getToastStyleProps(),
|
||||||
);
|
);
|
||||||
|
|
||||||
onDirtyStateChange(false, 'drawer');
|
onDirtyStateChange(false, location);
|
||||||
onSubmit?.();
|
onSubmit?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,7 +272,7 @@ export default function RolePermissionEditorForm({
|
|||||||
openDirtyConfirmation({
|
openDirtyConfirmation({
|
||||||
props: {
|
props: {
|
||||||
onPrimaryAction: () => {
|
onPrimaryAction: () => {
|
||||||
onDirtyStateChange(false, 'drawer');
|
onDirtyStateChange(false, location);
|
||||||
onCancel?.();
|
onCancel?.();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -300,7 +302,7 @@ export default function RolePermissionEditorForm({
|
|||||||
getToastStyleProps(),
|
getToastStyleProps(),
|
||||||
);
|
);
|
||||||
|
|
||||||
onDirtyStateChange(false, 'drawer');
|
onDirtyStateChange(false, location);
|
||||||
onSubmit?.();
|
onSubmit?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Input from '@/ui/v2/Input';
|
|||||||
import Radio from '@/ui/v2/Radio';
|
import Radio from '@/ui/v2/Radio';
|
||||||
import RadioGroup from '@/ui/v2/RadioGroup';
|
import RadioGroup from '@/ui/v2/RadioGroup';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
|
import type { FocusEvent } from 'react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
import PermissionSettingsSection from './PermissionSettingsSection';
|
import PermissionSettingsSection from './PermissionSettingsSection';
|
||||||
@@ -130,7 +131,13 @@ export default function RowPermissionsSection({
|
|||||||
|
|
||||||
{action === 'select' && (
|
{action === 'select' && (
|
||||||
<Input
|
<Input
|
||||||
{...register('limit')}
|
{...register('limit', {
|
||||||
|
onBlur: (event: FocusEvent<HTMLInputElement>) => {
|
||||||
|
if (!event.target.value) {
|
||||||
|
setValue('limit', null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
id="limit"
|
id="limit"
|
||||||
type="number"
|
type="number"
|
||||||
|
|||||||
@@ -43,7 +43,10 @@ const baseValidationSchema = Yup.object().shape({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const selectValidationSchema = baseValidationSchema.shape({
|
const selectValidationSchema = baseValidationSchema.shape({
|
||||||
limit: Yup.number().min(0, 'Limit must not be negative.').nullable(true),
|
limit: Yup.number()
|
||||||
|
.label('Limit')
|
||||||
|
.min(0, 'Limit must not be negative.')
|
||||||
|
.nullable(true),
|
||||||
allowAggregations: Yup.boolean().nullable(true),
|
allowAggregations: Yup.boolean().nullable(true),
|
||||||
queryRootFields: Yup.array().of(Yup.string()).nullable(true),
|
queryRootFields: Yup.array().of(Yup.string()).nullable(true),
|
||||||
subscriptionRootFields: Yup.array().of(Yup.string()).nullable(true),
|
subscriptionRootFields: Yup.array().of(Yup.string()).nullable(true),
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
|
||||||
export interface EditTableFormProps
|
export interface EditTableFormProps
|
||||||
extends Pick<BaseTableFormProps, 'onCancel'> {
|
extends Pick<BaseTableFormProps, 'onCancel' | 'location'> {
|
||||||
/**
|
/**
|
||||||
* Schema where the table is located.
|
* Schema where the table is located.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import ColumnAutocomplete from '@/components/dataBrowser/ColumnAutocomplete';
|
|||||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||||
import type { HasuraOperator } from '@/types/dataBrowser';
|
import type { HasuraOperator } from '@/types/dataBrowser';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
|
import type { AutocompleteOption } from '@/ui/v2/Autocomplete';
|
||||||
import type { InputProps } from '@/ui/v2/Input';
|
import type { InputProps } from '@/ui/v2/Input';
|
||||||
import { inputClasses } from '@/ui/v2/Input';
|
import { inputClasses } from '@/ui/v2/Input';
|
||||||
import Option from '@/ui/v2/Option';
|
import Option from '@/ui/v2/Option';
|
||||||
@@ -211,12 +212,13 @@ export default function RuleValueInput({
|
|||||||
<ControlledAutocomplete
|
<ControlledAutocomplete
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
freeSolo={!isHasuraInput}
|
freeSolo={!isHasuraInput}
|
||||||
autoSelect={!isHasuraInput}
|
|
||||||
autoHighlight={isHasuraInput}
|
autoHighlight={isHasuraInput}
|
||||||
open
|
isOptionEqualToValue={(
|
||||||
isOptionEqualToValue={(option, value) => {
|
option,
|
||||||
if (typeof value === 'string') {
|
value: string | number | AutocompleteOption<string>,
|
||||||
return option.value.toLowerCase() === (value as string).toLowerCase();
|
) => {
|
||||||
|
if (typeof value !== 'object') {
|
||||||
|
return option.value.toLowerCase() === value?.toString().toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
return option.value.toLowerCase() === value.value.toLowerCase();
|
return option.value.toLowerCase() === value.value.toLowerCase();
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
|
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 { slugifyString } from '@/utils/helpers';
|
||||||
@@ -11,11 +13,12 @@ import {
|
|||||||
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';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
export interface EditWorkspaceNameFormProps {
|
export interface EditWorkspaceNameFormProps extends DialogFormProps {
|
||||||
/**
|
/**
|
||||||
* The current workspace name if this is an edit operation.
|
* The current workspace name if this is an edit operation.
|
||||||
*/
|
*/
|
||||||
@@ -44,14 +47,7 @@ export interface EditWorkspaceNameFormProps {
|
|||||||
onCancel?: VoidFunction;
|
onCancel?: VoidFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EditWorkspaceNameFormValues {
|
const validationSchema = Yup.object({
|
||||||
/**
|
|
||||||
* New workspace name.
|
|
||||||
*/
|
|
||||||
newWorkspaceName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const validationSchema = Yup.object().shape({
|
|
||||||
newWorkspaceName: Yup.string()
|
newWorkspaceName: Yup.string()
|
||||||
.required('Workspace name is required.')
|
.required('Workspace name is required.')
|
||||||
.min(4, 'The new Workspace name must be at least 4 characters.')
|
.min(4, 'The new Workspace name must be at least 4 characters.')
|
||||||
@@ -71,14 +67,20 @@ const validationSchema = Yup.object().shape({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function EditWorkspaceName({
|
export type EditWorkspaceNameFormValues = Yup.InferType<
|
||||||
|
typeof validationSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export default function EditWorkspaceNameForm({
|
||||||
disabled,
|
disabled,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
currentWorkspaceName,
|
currentWorkspaceName,
|
||||||
currentWorkspaceId,
|
currentWorkspaceId,
|
||||||
submitButtonText = 'Create',
|
submitButtonText = 'Create',
|
||||||
|
location,
|
||||||
}: EditWorkspaceNameFormProps) {
|
}: EditWorkspaceNameFormProps) {
|
||||||
|
const { onDirtyStateChange } = useDialog();
|
||||||
const currentUser = useUserData();
|
const currentUser = useUserData();
|
||||||
const [insertWorkspace, { client }] = useInsertWorkspaceMutation();
|
const [insertWorkspace, { client }] = useInsertWorkspaceMutation();
|
||||||
const [updateWorkspaceName] = useUpdateWorkspaceMutation({
|
const [updateWorkspaceName] = useUpdateWorkspaceMutation({
|
||||||
@@ -105,6 +107,10 @@ export default function EditWorkspaceName({
|
|||||||
} = form;
|
} = form;
|
||||||
const isDirty = Object.keys(dirtyFields).length > 0;
|
const isDirty = Object.keys(dirtyFields).length > 0;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onDirtyStateChange(isDirty, location);
|
||||||
|
}, [isDirty, location, onDirtyStateChange]);
|
||||||
|
|
||||||
async function handleSubmit({
|
async function handleSubmit({
|
||||||
newWorkspaceName,
|
newWorkspaceName,
|
||||||
}: EditWorkspaceNameFormValues) {
|
}: EditWorkspaceNameFormValues) {
|
||||||
@@ -112,6 +118,8 @@ export default function EditWorkspaceName({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (currentWorkspaceId) {
|
if (currentWorkspaceId) {
|
||||||
|
onDirtyStateChange(false, location);
|
||||||
|
|
||||||
// In this bit of code we spread the props of the current path (e.g. /workspace/...) and add one key-value pair: `mutating: true`.
|
// In this bit of code we spread the props of the current path (e.g. /workspace/...) and add one key-value pair: `mutating: true`.
|
||||||
// We want to indicate that the currently we're in the process of running a mutation state that will affect the routing behaviour of the website
|
// We want to indicate that the currently we're in the process of running a mutation state that will affect the routing behaviour of the website
|
||||||
// i.e. redirecting to 404 if there's no workspace/project with that slug.
|
// i.e. redirecting to 404 if there's no workspace/project with that slug.
|
||||||
@@ -186,6 +194,9 @@ export default function EditWorkspaceName({
|
|||||||
include: ['getOneUser'],
|
include: ['getOneUser'],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The form has been submitted, it's not dirty anymore
|
||||||
|
onDirtyStateChange(false, location);
|
||||||
|
|
||||||
await router.push(slug);
|
await router.push(slug);
|
||||||
onSubmit?.();
|
onSubmit?.();
|
||||||
}
|
}
|
||||||
@@ -194,9 +205,9 @@ export default function EditWorkspaceName({
|
|||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<Form
|
<Form
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
className="flex flex-col content-between flex-auto pt-2 pb-6 overflow-hidden"
|
className="flex flex-auto flex-col content-between overflow-hidden pt-2 pb-6"
|
||||||
>
|
>
|
||||||
<div className="flex-auto px-6 overflow-y-auto">
|
<div className="flex-auto overflow-y-auto px-6">
|
||||||
<Input
|
<Input
|
||||||
{...register('newWorkspaceName')}
|
{...register('newWorkspaceName')}
|
||||||
error={Boolean(errors.newWorkspaceName?.message)}
|
error={Boolean(errors.newWorkspaceName?.message)}
|
||||||
|
|||||||
@@ -58,16 +58,19 @@ export function InviteAnnounce() {
|
|||||||
error: null,
|
error: null,
|
||||||
loading: true,
|
loading: true,
|
||||||
});
|
});
|
||||||
const res = await nhost.functions.call('/accept-workspace-invite', {
|
const { res, error: acceptError } = await nhost.functions.call(
|
||||||
workspaceMemberInviteId: invite.id,
|
'/accept-workspace-invite',
|
||||||
isAccepted: true,
|
{
|
||||||
});
|
workspaceMemberInviteId: invite.id,
|
||||||
|
isAccepted: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (res?.res?.status !== 200) {
|
if (res?.status !== 200) {
|
||||||
triggerToast('An error occurred when trying to accept the invitation.');
|
triggerToast('An error occurred when trying to accept the invitation.');
|
||||||
|
|
||||||
return setSubmitState({
|
return setSubmitState({
|
||||||
error: new Error(res.error.message),
|
error: new Error(acceptError.message),
|
||||||
loading: false,
|
loading: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -90,7 +93,7 @@ export function InviteAnnounce() {
|
|||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await nhost.functions.call(
|
const { error: ignoreError } = await nhost.functions.call(
|
||||||
'/accept-workspace-invite',
|
'/accept-workspace-invite',
|
||||||
{
|
{
|
||||||
workspaceMemberInviteId: inviteId,
|
workspaceMemberInviteId: inviteId,
|
||||||
@@ -99,12 +102,12 @@ export function InviteAnnounce() {
|
|||||||
{ useAxios: false },
|
{ useAxios: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (res?.error) {
|
if (ignoreError) {
|
||||||
triggerToast('An error occurred when trying to ignore the invitation.');
|
triggerToast('An error occurred when trying to ignore the invitation.');
|
||||||
|
|
||||||
setIgnoreState({
|
setIgnoreState({
|
||||||
loading: false,
|
loading: false,
|
||||||
error: new Error(res.error.message),
|
error: new Error(ignoreError.message),
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
|
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 Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
@@ -26,7 +27,7 @@ export interface BaseEnvironmentVariableFormValues {
|
|||||||
prodValue: string;
|
prodValue: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseEnvironmentVariableFormProps {
|
export interface BaseEnvironmentVariableFormProps extends DialogFormProps {
|
||||||
/**
|
/**
|
||||||
* Determines the mode of the form.
|
* Determines the mode of the form.
|
||||||
*
|
*
|
||||||
@@ -89,6 +90,7 @@ export default function BaseEnvironmentVariableForm({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
submitButtonText = 'Save',
|
submitButtonText = 'Save',
|
||||||
|
location,
|
||||||
}: BaseEnvironmentVariableFormProps) {
|
}: BaseEnvironmentVariableFormProps) {
|
||||||
const { onDirtyStateChange } = useDialog();
|
const { onDirtyStateChange } = useDialog();
|
||||||
const form = useFormContext<BaseEnvironmentVariableFormValues>();
|
const form = useFormContext<BaseEnvironmentVariableFormValues>();
|
||||||
@@ -103,8 +105,8 @@ export default function BaseEnvironmentVariableForm({
|
|||||||
const isDirty = Object.keys(dirtyFields).length > 0;
|
const isDirty = Object.keys(dirtyFields).length > 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onDirtyStateChange(isDirty, 'dialog');
|
onDirtyStateChange(isDirty, location);
|
||||||
}, [isDirty, onDirtyStateChange]);
|
}, [isDirty, location, onDirtyStateChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-flow-row gap-6 px-6 pb-6">
|
<div className="grid grid-flow-row gap-6 px-6 pb-6">
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { FormProvider, useForm } from 'react-hook-form';
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
export interface CreateEnvironmentVariableFormProps
|
export interface CreateEnvironmentVariableFormProps
|
||||||
extends Pick<BaseEnvironmentVariableFormProps, 'onCancel'> {
|
extends Pick<BaseEnvironmentVariableFormProps, 'onCancel' | 'location'> {
|
||||||
/**
|
/**
|
||||||
* Function to be called when the form is submitted.
|
* Function to be called when the form is submitted.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { FormProvider, useForm } from 'react-hook-form';
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
export interface EditEnvironmentVariableFormProps
|
export interface EditEnvironmentVariableFormProps
|
||||||
extends Pick<BaseEnvironmentVariableFormProps, 'onCancel'> {
|
extends Pick<BaseEnvironmentVariableFormProps, 'onCancel' | 'location'> {
|
||||||
/**
|
/**
|
||||||
* The environment variable to edit.
|
* The environment variable to edit.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||||
|
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 { getToastStyleProps } from '@/utils/settings/settingsConstants';
|
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
|
||||||
@@ -14,7 +15,7 @@ import { FormProvider, useForm } from 'react-hook-form';
|
|||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
export interface EditJwtSecretFormProps {
|
export interface EditJwtSecretFormProps extends DialogFormProps {
|
||||||
/**
|
/**
|
||||||
* Initial JWT secret.
|
* Initial JWT secret.
|
||||||
*/
|
*/
|
||||||
@@ -39,14 +40,7 @@ export interface EditJwtSecretFormProps {
|
|||||||
onCancel?: VoidFunction;
|
onCancel?: VoidFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EditJwtSecretFormValues {
|
const validationSchema = Yup.object({
|
||||||
/**
|
|
||||||
* JWT secret.
|
|
||||||
*/
|
|
||||||
jwtSecret: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const validationSchema = Yup.object().shape({
|
|
||||||
jwtSecret: Yup.string()
|
jwtSecret: Yup.string()
|
||||||
.nullable()
|
.nullable()
|
||||||
.required('This field is required.')
|
.required('This field is required.')
|
||||||
@@ -60,12 +54,15 @@ const validationSchema = Yup.object().shape({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type EditJwtSecretFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function EditJwtSecretForm({
|
export default function EditJwtSecretForm({
|
||||||
disabled,
|
disabled,
|
||||||
jwtSecret,
|
jwtSecret,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
submitButtonText = 'Save',
|
submitButtonText = 'Save',
|
||||||
|
location,
|
||||||
}: EditJwtSecretFormProps) {
|
}: EditJwtSecretFormProps) {
|
||||||
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
||||||
const [updateApplication] = useUpdateApplicationMutation({
|
const [updateApplication] = useUpdateApplicationMutation({
|
||||||
@@ -89,8 +86,8 @@ export default function EditJwtSecretForm({
|
|||||||
const isDirty = Object.keys(dirtyFields).length > 0;
|
const isDirty = Object.keys(dirtyFields).length > 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onDirtyStateChange(isDirty, 'dialog');
|
onDirtyStateChange(isDirty, location);
|
||||||
}, [isDirty, onDirtyStateChange]);
|
}, [isDirty, location, onDirtyStateChange]);
|
||||||
|
|
||||||
async function handleSubmit(values: EditJwtSecretFormValues) {
|
async function handleSubmit(values: EditJwtSecretFormValues) {
|
||||||
const updateAppPromise = updateApplication({
|
const updateAppPromise = updateApplication({
|
||||||
@@ -121,7 +118,7 @@ export default function EditJwtSecretForm({
|
|||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
className="flex flex-auto flex-col content-between overflow-hidden pb-4"
|
className="flex flex-auto flex-col content-between overflow-hidden pb-4"
|
||||||
>
|
>
|
||||||
<div className="px-6 overflow-y-auto flex-auto">
|
<div className="flex-auto overflow-y-auto px-6">
|
||||||
<Input
|
<Input
|
||||||
{...register('jwtSecret')}
|
{...register('jwtSecret')}
|
||||||
error={Boolean(errors.jwtSecret?.message)}
|
error={Boolean(errors.jwtSecret?.message)}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import CreateEnvironmentVariableForm from '@/components/settings/environmentVariables/CreateEnvironmentVariableForm';
|
||||||
|
import EditEnvironmentVariableForm from '@/components/settings/environmentVariables/EditEnvironmentVariableForm';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||||
import type { EnvironmentVariable } from '@/types/application';
|
import type { EnvironmentVariable } from '@/types/application';
|
||||||
@@ -23,9 +25,9 @@ import { Fragment } from 'react';
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export interface PermissionVariableSettingsFormValues {
|
export interface EnvironmentVariableSettingsFormValues {
|
||||||
/**
|
/**
|
||||||
* Permission variables.
|
* Environment variables.
|
||||||
*/
|
*/
|
||||||
environmentVariables: EnvironmentVariable[];
|
environmentVariables: EnvironmentVariable[];
|
||||||
}
|
}
|
||||||
@@ -75,8 +77,9 @@ export default function EnvironmentVariableSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleOpenCreator() {
|
function handleOpenCreator() {
|
||||||
openDialog('CREATE_ENVIRONMENT_VARIABLE', {
|
openDialog({
|
||||||
title: 'Create Environment Variable',
|
title: 'Create Environment Variable',
|
||||||
|
component: <CreateEnvironmentVariableForm />,
|
||||||
props: {
|
props: {
|
||||||
titleProps: { className: '!pb-0' },
|
titleProps: { className: '!pb-0' },
|
||||||
PaperProps: { className: 'gap-2 max-w-sm' },
|
PaperProps: { className: 'gap-2 max-w-sm' },
|
||||||
@@ -85,9 +88,13 @@ export default function EnvironmentVariableSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleOpenEditor(originalVariable: EnvironmentVariable) {
|
function handleOpenEditor(originalVariable: EnvironmentVariable) {
|
||||||
openDialog('EDIT_ENVIRONMENT_VARIABLE', {
|
openDialog({
|
||||||
title: 'Edit Environment Variables',
|
title: 'Edit Environment Variable',
|
||||||
payload: { originalEnvironmentVariable: originalVariable },
|
component: (
|
||||||
|
<EditEnvironmentVariableForm
|
||||||
|
originalEnvironmentVariable={originalVariable}
|
||||||
|
/>
|
||||||
|
),
|
||||||
props: {
|
props: {
|
||||||
titleProps: { className: '!pb-0' },
|
titleProps: { className: '!pb-0' },
|
||||||
PaperProps: { className: 'gap-2 max-w-sm' },
|
PaperProps: { className: 'gap-2 max-w-sm' },
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import InlineCode from '@/components/common/InlineCode';
|
import InlineCode from '@/components/common/InlineCode';
|
||||||
|
import EditJwtSecretForm from '@/components/settings/environmentVariables/EditJwtSecretForm';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import useIsPlatform from '@/hooks/common/useIsPlatform';
|
import useIsPlatform from '@/hooks/common/useIsPlatform';
|
||||||
import { useAppClient } from '@/hooks/useAppClient';
|
import { useAppClient } from '@/hooks/useAppClient';
|
||||||
@@ -50,7 +51,7 @@ export default function SystemEnvironmentVariableSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showViewJwtSecretModal() {
|
function showViewJwtSecretModal() {
|
||||||
openDialog('EDIT_JWT_SECRET', {
|
openDialog({
|
||||||
title: (
|
title: (
|
||||||
<span className="grid grid-flow-row">
|
<span className="grid grid-flow-row">
|
||||||
<span>Auth JWT Secret</span>
|
<span>Auth JWT Secret</span>
|
||||||
@@ -61,15 +62,17 @@ export default function SystemEnvironmentVariableSettings() {
|
|||||||
</Text>
|
</Text>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
payload: {
|
component: (
|
||||||
disabled: true,
|
<EditJwtSecretForm
|
||||||
jwtSecret: data?.app?.hasuraGraphqlJwtSecret,
|
disabled
|
||||||
},
|
jwtSecret={data?.app?.hasuraGraphqlJwtSecret}
|
||||||
|
/>
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showEditJwtSecretModal() {
|
function showEditJwtSecretModal() {
|
||||||
openDialog('EDIT_JWT_SECRET', {
|
openDialog({
|
||||||
title: (
|
title: (
|
||||||
<span className="grid grid-flow-row">
|
<span className="grid grid-flow-row">
|
||||||
<span>Edit JWT Secret</span>
|
<span>Edit JWT Secret</span>
|
||||||
@@ -80,9 +83,9 @@ export default function SystemEnvironmentVariableSettings() {
|
|||||||
</Text>
|
</Text>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
payload: {
|
component: (
|
||||||
jwtSecret: data?.app?.hasuraGraphqlJwtSecret,
|
<EditJwtSecretForm jwtSecret={data?.app?.hasuraGraphqlJwtSecret} />
|
||||||
},
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
|
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 Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
@@ -7,18 +8,7 @@ import { useEffect } from 'react';
|
|||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
export interface BasePermissionVariableFormValues {
|
export interface BasePermissionVariableFormProps extends DialogFormProps {
|
||||||
/**
|
|
||||||
* Permission variable key.
|
|
||||||
*/
|
|
||||||
key: string;
|
|
||||||
/**
|
|
||||||
* Permission variable value.
|
|
||||||
*/
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BasePermissionVariableFormProps {
|
|
||||||
/**
|
/**
|
||||||
* Function to be called when the form is submitted.
|
* Function to be called when the form is submitted.
|
||||||
*/
|
*/
|
||||||
@@ -40,10 +30,15 @@ export const basePermissionVariableValidationSchema = Yup.object({
|
|||||||
value: Yup.string().required('This field is required.'),
|
value: Yup.string().required('This field is required.'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type BasePermissionVariableFormValues = Yup.InferType<
|
||||||
|
typeof basePermissionVariableValidationSchema
|
||||||
|
>;
|
||||||
|
|
||||||
export default function BasePermissionVariableForm({
|
export default function BasePermissionVariableForm({
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
submitButtonText = 'Save',
|
submitButtonText = 'Save',
|
||||||
|
location,
|
||||||
}: BasePermissionVariableFormProps) {
|
}: BasePermissionVariableFormProps) {
|
||||||
const { onDirtyStateChange } = useDialog();
|
const { onDirtyStateChange } = useDialog();
|
||||||
const form = useFormContext<BasePermissionVariableFormValues>();
|
const form = useFormContext<BasePermissionVariableFormValues>();
|
||||||
@@ -56,8 +51,8 @@ export default function BasePermissionVariableForm({
|
|||||||
const isDirty = Object.keys(dirtyFields).length > 0;
|
const isDirty = Object.keys(dirtyFields).length > 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onDirtyStateChange(isDirty, 'dialog');
|
onDirtyStateChange(isDirty, location);
|
||||||
}, [isDirty, onDirtyStateChange]);
|
}, [isDirty, location, onDirtyStateChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-flow-row gap-2 px-6 pb-6">
|
<div className="grid grid-flow-row gap-2 px-6 pb-6">
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { FormProvider, useForm } from 'react-hook-form';
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
export interface CreatePermissionVariableFormProps
|
export interface CreatePermissionVariableFormProps
|
||||||
extends Pick<BasePermissionVariableFormProps, 'onCancel'> {
|
extends Pick<BasePermissionVariableFormProps, 'onCancel' | 'location'> {
|
||||||
/**
|
/**
|
||||||
* Function to be called when the form is submitted.
|
* Function to be called when the form is submitted.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { FormProvider, useForm } from 'react-hook-form';
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
export interface EditPermissionVariableFormProps
|
export interface EditPermissionVariableFormProps
|
||||||
extends Pick<BasePermissionVariableFormProps, 'onCancel'> {
|
extends Pick<BasePermissionVariableFormProps, 'onCancel' | 'location'> {
|
||||||
/**
|
/**
|
||||||
* The permission variable to be edited.
|
* The permission variable to be edited.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import CreatePermissionVariableForm from '@/components/settings/permissions/CreatePermissionVariableForm';
|
||||||
|
import EditPermissionVariableForm from '@/components/settings/permissions/EditPermissionVariableForm';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||||
import type { CustomClaim } from '@/types/application';
|
import type { CustomClaim } from '@/types/application';
|
||||||
@@ -88,8 +90,9 @@ export default function PermissionVariableSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleOpenCreator() {
|
function handleOpenCreator() {
|
||||||
openDialog('CREATE_PERMISSION_VARIABLE', {
|
openDialog({
|
||||||
title: 'Create Permission Variable',
|
title: 'Create Permission Variable',
|
||||||
|
component: <CreatePermissionVariableForm />,
|
||||||
props: {
|
props: {
|
||||||
titleProps: { className: '!pb-0' },
|
titleProps: { className: '!pb-0' },
|
||||||
PaperProps: { className: 'max-w-sm' },
|
PaperProps: { className: 'max-w-sm' },
|
||||||
@@ -98,9 +101,11 @@ export default function PermissionVariableSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleOpenEditor(originalVariable: CustomClaim) {
|
function handleOpenEditor(originalVariable: CustomClaim) {
|
||||||
openDialog('EDIT_PERMISSION_VARIABLE', {
|
openDialog({
|
||||||
title: 'Edit Permission Variable',
|
title: 'Edit Permission Variable',
|
||||||
payload: { originalVariable },
|
component: (
|
||||||
|
<EditPermissionVariableForm originalVariable={originalVariable} />
|
||||||
|
),
|
||||||
props: {
|
props: {
|
||||||
titleProps: { className: '!pb-0' },
|
titleProps: { className: '!pb-0' },
|
||||||
PaperProps: { className: 'max-w-sm' },
|
PaperProps: { className: 'max-w-sm' },
|
||||||
@@ -136,7 +141,7 @@ export default function PermissionVariableSettings() {
|
|||||||
description="Permission variables are used to define permission rules in the GraphQL API."
|
description="Permission variables are used to define permission rules in the GraphQL API."
|
||||||
docsLink="https://docs.nhost.io/graphql/permissions"
|
docsLink="https://docs.nhost.io/graphql/permissions"
|
||||||
rootClassName="gap-0"
|
rootClassName="gap-0"
|
||||||
className="px-0 my-2"
|
className="my-2 px-0"
|
||||||
slotProps={{ submitButton: { className: 'invisible' } }}
|
slotProps={{ submitButton: { className: 'invisible' } }}
|
||||||
>
|
>
|
||||||
<Box className="grid grid-cols-2 border-b-1 px-4 py-3">
|
<Box className="grid grid-cols-2 border-b-1 px-4 py-3">
|
||||||
@@ -149,7 +154,7 @@ export default function PermissionVariableSettings() {
|
|||||||
{availablePermissionVariables.map((customClaim, index) => (
|
{availablePermissionVariables.map((customClaim, index) => (
|
||||||
<Fragment key={customClaim.key}>
|
<Fragment key={customClaim.key}>
|
||||||
<ListItem.Root
|
<ListItem.Root
|
||||||
className="px-4 grid grid-cols-2"
|
className="grid grid-cols-2 px-4"
|
||||||
secondaryAction={
|
secondaryAction={
|
||||||
<Dropdown.Root>
|
<Dropdown.Root>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@@ -215,7 +220,7 @@ export default function PermissionVariableSettings() {
|
|||||||
<>
|
<>
|
||||||
X-Hasura-{customClaim.key}{' '}
|
X-Hasura-{customClaim.key}{' '}
|
||||||
{customClaim.isSystemClaim && (
|
{customClaim.isSystemClaim && (
|
||||||
<LockIcon className="w-4 h-4" />
|
<LockIcon className="h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@@ -237,7 +242,7 @@ export default function PermissionVariableSettings() {
|
|||||||
</List>
|
</List>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
className="justify-self-start mx-4"
|
className="mx-4 justify-self-start"
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
startIcon={<PlusIcon />}
|
startIcon={<PlusIcon />}
|
||||||
onClick={handleOpenCreator}
|
onClick={handleOpenCreator}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import { Alert } from '@/ui/Alert';
|
import { Alert } from '@/ui/Alert';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
@@ -8,14 +9,7 @@ import { useEffect } from 'react';
|
|||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
export interface BaseRoleFormValues {
|
export interface BaseRoleFormProps extends DialogFormProps {
|
||||||
/**
|
|
||||||
* The name of the role.
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BaseRoleFormProps {
|
|
||||||
/**
|
/**
|
||||||
* Function to be called when the form is submitted.
|
* Function to be called when the form is submitted.
|
||||||
*/
|
*/
|
||||||
@@ -36,10 +30,15 @@ export const baseRoleFormValidationSchema = Yup.object({
|
|||||||
name: Yup.string().required('This field is required.'),
|
name: Yup.string().required('This field is required.'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type BaseRoleFormValues = Yup.InferType<
|
||||||
|
typeof baseRoleFormValidationSchema
|
||||||
|
>;
|
||||||
|
|
||||||
export default function BaseRoleForm({
|
export default function BaseRoleForm({
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
submitButtonText = 'Save',
|
submitButtonText = 'Save',
|
||||||
|
location,
|
||||||
}: BaseRoleFormProps) {
|
}: BaseRoleFormProps) {
|
||||||
const { onDirtyStateChange } = useDialog();
|
const { onDirtyStateChange } = useDialog();
|
||||||
const form = useFormContext<BaseRoleFormValues>();
|
const form = useFormContext<BaseRoleFormValues>();
|
||||||
@@ -52,8 +51,8 @@ export default function BaseRoleForm({
|
|||||||
const isDirty = Object.keys(dirtyFields).length > 0;
|
const isDirty = Object.keys(dirtyFields).length > 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onDirtyStateChange(isDirty, 'dialog');
|
onDirtyStateChange(isDirty, location);
|
||||||
}, [isDirty, onDirtyStateChange]);
|
}, [isDirty, location, onDirtyStateChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-flow-row gap-3 px-6 pb-6">
|
<div className="grid grid-flow-row gap-3 px-6 pb-6">
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { FormProvider, useForm } from 'react-hook-form';
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
export interface CreateRoleFormProps
|
export interface CreateRoleFormProps
|
||||||
extends Pick<BaseRoleFormProps, 'onCancel'> {
|
extends Pick<BaseRoleFormProps, 'onCancel' | 'location'> {
|
||||||
/**
|
/**
|
||||||
* Function to be called when the form is submitted.
|
* Function to be called when the form is submitted.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ import { yupResolver } from '@hookform/resolvers/yup';
|
|||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
export interface EditRoleFormProps extends Pick<BaseRoleFormProps, 'onCancel'> {
|
export interface EditRoleFormProps
|
||||||
|
extends Pick<BaseRoleFormProps, 'onCancel' | 'location'> {
|
||||||
/**
|
/**
|
||||||
* The role to be edited.
|
* The role to be edited.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import CreateRoleForm from '@/components/settings/roles/CreateRoleForm';
|
||||||
|
import EditRoleForm from '@/components/settings/roles/EditRoleForm';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||||
import type { Role } from '@/types/application';
|
import type { Role } from '@/types/application';
|
||||||
@@ -108,8 +110,9 @@ export default function RoleSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleOpenCreator() {
|
function handleOpenCreator() {
|
||||||
openDialog('CREATE_ROLE', {
|
openDialog({
|
||||||
title: 'Create Allowed Role',
|
title: 'Create Allowed Role',
|
||||||
|
component: <CreateRoleForm />,
|
||||||
props: {
|
props: {
|
||||||
titleProps: { className: '!pb-0' },
|
titleProps: { className: '!pb-0' },
|
||||||
PaperProps: { className: 'max-w-sm' },
|
PaperProps: { className: 'max-w-sm' },
|
||||||
@@ -118,9 +121,9 @@ export default function RoleSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleOpenEditor(originalRole: Role) {
|
function handleOpenEditor(originalRole: Role) {
|
||||||
openDialog('EDIT_ROLE', {
|
openDialog({
|
||||||
title: 'Edit Allowed Role',
|
title: 'Edit Allowed Role',
|
||||||
payload: { originalRole },
|
component: <EditRoleForm originalRole={originalRole} />,
|
||||||
props: {
|
props: {
|
||||||
titleProps: { className: '!pb-0' },
|
titleProps: { className: '!pb-0' },
|
||||||
PaperProps: { className: 'max-w-sm' },
|
PaperProps: { className: 'max-w-sm' },
|
||||||
|
|||||||
@@ -222,9 +222,9 @@ function Autocomplete(
|
|||||||
inputValue: inputValue || '',
|
inputValue: inputValue || '',
|
||||||
getOptionLabel: props.getOptionLabel
|
getOptionLabel: props.getOptionLabel
|
||||||
? props.getOptionLabel
|
? props.getOptionLabel
|
||||||
: (option) => {
|
: (option: string | number | AutocompleteOption<string>) => {
|
||||||
if (typeof option === 'string') {
|
if (typeof option !== 'object') {
|
||||||
return option;
|
return option.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return option.label ?? option.dropdownLabel;
|
return option.label ?? option.dropdownLabel;
|
||||||
@@ -284,33 +284,46 @@ function Autocomplete(
|
|||||||
}}
|
}}
|
||||||
PopperComponent={AutocompletePopper}
|
PopperComponent={AutocompletePopper}
|
||||||
popupIcon={<ChevronDownIcon sx={{ width: 12, height: 12 }} />}
|
popupIcon={<ChevronDownIcon sx={{ width: 12, height: 12 }} />}
|
||||||
getOptionLabel={(option) => {
|
getOptionLabel={(
|
||||||
if (typeof option === 'string') {
|
option: string | number | AutocompleteOption<string>,
|
||||||
return option;
|
) => {
|
||||||
|
if (!option) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof option !== 'object') {
|
||||||
|
return option.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return option.label ?? option.dropdownLabel;
|
return option.label ?? option.dropdownLabel;
|
||||||
}}
|
}}
|
||||||
isOptionEqualToValue={(option, value) => {
|
isOptionEqualToValue={(
|
||||||
|
option,
|
||||||
|
value: string | number | AutocompleteOption<string>,
|
||||||
|
) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === 'string') {
|
if (typeof value !== 'object') {
|
||||||
return option.value === value;
|
return option.value.toString() === value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return option.value === value.value && option.custom === value.custom;
|
return option.value === value.value && option.custom === value.custom;
|
||||||
}}
|
}}
|
||||||
renderTags={(value, getTagProps) =>
|
renderTags={(value, getTagProps) =>
|
||||||
value.map((option, index) => (
|
value.map(
|
||||||
<StyledTag
|
(option: string | number | AutocompleteOption<string>, index) => (
|
||||||
deleteIcon={<XIcon />}
|
<StyledTag
|
||||||
size="small"
|
deleteIcon={<XIcon />}
|
||||||
label={typeof option === 'string' ? option : option.value}
|
size="small"
|
||||||
{...getTagProps({ index })}
|
label={
|
||||||
/>
|
typeof option !== 'object' ? option.toString() : option.value
|
||||||
))
|
}
|
||||||
|
{...getTagProps({ index })}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
renderGroup={({ group, key, children }) =>
|
renderGroup={({ group, key, children }) =>
|
||||||
group ? (
|
group ? (
|
||||||
@@ -323,9 +336,12 @@ function Autocomplete(
|
|||||||
<div key={key}>{children}</div>
|
<div key={key}>{children}</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
renderOption={(optionProps, option) => {
|
renderOption={(
|
||||||
if (typeof option === 'string') {
|
optionProps,
|
||||||
return <OptionBase {...optionProps}>{option}</OptionBase>;
|
option: string | number | AutocompleteOption<string>,
|
||||||
|
) => {
|
||||||
|
if (typeof option !== 'object') {
|
||||||
|
return <OptionBase {...optionProps}>{option.toString()}</OptionBase>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import Backdrop from '@/ui/v2/Backdrop';
|
import Backdrop from '@/ui/v2/Backdrop';
|
||||||
|
import type { DialogTitleProps } from '@/ui/v2/Dialog';
|
||||||
import { DialogTitle } from '@/ui/v2/Dialog';
|
import { DialogTitle } from '@/ui/v2/Dialog';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import type { DrawerProps as MaterialDrawerProps } from '@mui/material/Drawer';
|
import type { DrawerProps as MaterialDrawerProps } from '@mui/material/Drawer';
|
||||||
@@ -10,6 +11,10 @@ export interface DrawerProps extends Omit<MaterialDrawerProps, 'title'> {
|
|||||||
* Title of the drawer.
|
* Title of the drawer.
|
||||||
*/
|
*/
|
||||||
title?: ReactNode;
|
title?: ReactNode;
|
||||||
|
/**
|
||||||
|
* Props to pass to the title component.
|
||||||
|
*/
|
||||||
|
titleProps?: DialogTitleProps;
|
||||||
/**
|
/**
|
||||||
* Determines whether or not a close button is hidden in the drawer.
|
* Determines whether or not a close button is hidden in the drawer.
|
||||||
*
|
*
|
||||||
@@ -33,13 +38,18 @@ function Drawer({
|
|||||||
children,
|
children,
|
||||||
onClose,
|
onClose,
|
||||||
title,
|
title,
|
||||||
|
titleProps: { sx: titleSx, ...titleProps } = {},
|
||||||
...props
|
...props
|
||||||
}: DrawerProps) {
|
}: DrawerProps) {
|
||||||
return (
|
return (
|
||||||
<StyledDrawer components={{ Backdrop }} onClose={onClose} {...props}>
|
<StyledDrawer components={{ Backdrop }} onClose={onClose} {...props}>
|
||||||
{onClose && !hideCloseButton && (
|
{onClose && !hideCloseButton && (
|
||||||
<DialogTitle
|
<DialogTitle
|
||||||
sx={{ padding: (theme) => theme.spacing(2.5, 3) }}
|
{...titleProps}
|
||||||
|
sx={[
|
||||||
|
...(Array.isArray(titleSx) ? titleSx : [titleSx]),
|
||||||
|
{ padding: (theme) => theme.spacing(2.5, 3) },
|
||||||
|
]}
|
||||||
onClose={(event) => onClose(event, 'escapeKeyDown')}
|
onClose={(event) => onClose(event, 'escapeKeyDown')}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||||
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import { Alert } from '@/ui/Alert';
|
import { Alert } from '@/ui/Alert';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
@@ -7,23 +9,12 @@ import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
|||||||
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
|
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import fetch from 'cross-fetch';
|
import fetch from 'cross-fetch';
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
export interface CreateUserFormValues {
|
export interface CreateUserFormProps extends DialogFormProps {
|
||||||
/**
|
|
||||||
* Email of the user to add to this project.
|
|
||||||
*/
|
|
||||||
email: string;
|
|
||||||
/**
|
|
||||||
* Password for the user.
|
|
||||||
*/
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateUserFormProps {
|
|
||||||
/**
|
/**
|
||||||
* Function to be called when the operation is cancelled.
|
* Function to be called when the operation is cancelled.
|
||||||
*/
|
*/
|
||||||
@@ -31,10 +22,10 @@ export interface CreateUserFormProps {
|
|||||||
/**
|
/**
|
||||||
* Function to be called when the submit is successful.
|
* Function to be called when the submit is successful.
|
||||||
*/
|
*/
|
||||||
onSuccess?: VoidFunction;
|
onSubmit?: VoidFunction | ((args?: any) => Promise<any>);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CreateUserFormValidationSchema = Yup.object({
|
export const validationSchema = Yup.object({
|
||||||
email: Yup.string()
|
email: Yup.string()
|
||||||
.min(5, 'Email must be at least 5 characters long.')
|
.min(5, 'Email must be at least 5 characters long.')
|
||||||
.email('Invalid email address')
|
.email('Invalid email address')
|
||||||
@@ -45,10 +36,14 @@ export const CreateUserFormValidationSchema = Yup.object({
|
|||||||
.required('This field is required.'),
|
.required('This field is required.'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type CreateUserFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function CreateUserForm({
|
export default function CreateUserForm({
|
||||||
onSuccess,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
|
location,
|
||||||
}: CreateUserFormProps) {
|
}: CreateUserFormProps) {
|
||||||
|
const { onDirtyStateChange } = useDialog();
|
||||||
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
||||||
const [createUserFormError, setCreateUserFormError] = useState<Error | null>(
|
const [createUserFormError, setCreateUserFormError] = useState<Error | null>(
|
||||||
null,
|
null,
|
||||||
@@ -57,15 +52,21 @@ export default function CreateUserForm({
|
|||||||
const form = useForm<CreateUserFormValues>({
|
const form = useForm<CreateUserFormValues>({
|
||||||
defaultValues: {},
|
defaultValues: {},
|
||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
resolver: yupResolver(CreateUserFormValidationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting, dirtyFields },
|
||||||
setError,
|
setError,
|
||||||
} = form;
|
} = form;
|
||||||
|
|
||||||
|
const isDirty = Object.keys(dirtyFields).length > 0;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onDirtyStateChange(isDirty, location);
|
||||||
|
}, [isDirty, location, onDirtyStateChange]);
|
||||||
|
|
||||||
const baseAuthUrl = generateAppServiceUrl(
|
const baseAuthUrl = generateAppServiceUrl(
|
||||||
currentApplication?.subdomain,
|
currentApplication?.subdomain,
|
||||||
currentApplication?.region?.awsName,
|
currentApplication?.region?.awsName,
|
||||||
@@ -81,26 +82,35 @@ export default function CreateUserForm({
|
|||||||
await toast.promise(
|
await toast.promise(
|
||||||
fetch(signUpUrl, {
|
fetch(signUpUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ email, password }),
|
body: JSON.stringify({ email, password }),
|
||||||
}).then((res) => res.json()),
|
}).then(async (res) => {
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.status === 409) {
|
||||||
|
setError('email', { message: data?.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(data?.message || 'Something went wrong.');
|
||||||
|
}),
|
||||||
{
|
{
|
||||||
loading: 'Creating user...',
|
loading: 'Creating user...',
|
||||||
success: 'User created successfully.',
|
success: 'User created successfully.',
|
||||||
error: 'An error occurred while trying to create the user.',
|
error: (arg) =>
|
||||||
|
arg?.message
|
||||||
|
? `Error: ${arg.message}`
|
||||||
|
: 'An error occurred while trying to create the user.',
|
||||||
},
|
},
|
||||||
getToastStyleProps(),
|
getToastStyleProps(),
|
||||||
);
|
);
|
||||||
onSuccess?.();
|
|
||||||
|
onSubmit?.();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.response?.status === 409) {
|
// Note: The error is already handled by the toast promise.
|
||||||
setError('email', {
|
|
||||||
message: error.response.data.message,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setCreateUserFormError(
|
|
||||||
new Error(error.response.data.message || 'Something went wrong.'),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +147,7 @@ export default function CreateUserForm({
|
|||||||
{createUserFormError && (
|
{createUserFormError && (
|
||||||
<Alert
|
<Alert
|
||||||
severity="error"
|
severity="error"
|
||||||
className="grid items-center justify-between grid-flow-col px-4 py-3"
|
className="grid grid-flow-col items-center justify-between px-4 py-3"
|
||||||
>
|
>
|
||||||
<span className="text-left">
|
<span className="text-left">
|
||||||
<strong>Error:</strong> {createUserFormError.message}
|
<strong>Error:</strong> {createUserFormError.message}
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import ControlledCheckbox from '@/components/common/ControlledCheckbox';
|
|||||||
import ControlledSelect from '@/components/common/ControlledSelect';
|
import ControlledSelect from '@/components/common/ControlledSelect';
|
||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
|
import EditUserPasswordForm from '@/components/users/EditUserPasswordForm';
|
||||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||||
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
||||||
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import Avatar from '@/ui/v2/Avatar';
|
import Avatar from '@/ui/v2/Avatar';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
@@ -20,6 +22,7 @@ import { copy } from '@/utils/copy';
|
|||||||
import getUserRoles from '@/utils/settings/getUserRoles';
|
import getUserRoles from '@/utils/settings/getUserRoles';
|
||||||
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
|
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
|
||||||
import {
|
import {
|
||||||
|
RemoteAppGetUsersDocument,
|
||||||
useGetRolesQuery,
|
useGetRolesQuery,
|
||||||
useUpdateRemoteAppUserMutation,
|
useUpdateRemoteAppUserMutation,
|
||||||
} from '@/utils/__generated__/graphql';
|
} from '@/utils/__generated__/graphql';
|
||||||
@@ -34,7 +37,7 @@ import { FormProvider, useForm } from 'react-hook-form';
|
|||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
export interface EditUserFormProps {
|
export interface EditUserFormProps extends DialogFormProps {
|
||||||
/**
|
/**
|
||||||
* This is the selected user from the user's table.
|
* This is the selected user from the user's table.
|
||||||
*/
|
*/
|
||||||
@@ -42,10 +45,7 @@ export interface EditUserFormProps {
|
|||||||
/**
|
/**
|
||||||
* Function to be called when the form is submitted.
|
* Function to be called when the form is submitted.
|
||||||
*/
|
*/
|
||||||
onEditUser?: (
|
onSubmit?: (values: EditUserFormValues) => Promise<void>;
|
||||||
values: EditUserFormValues,
|
|
||||||
user: RemoteAppUser,
|
|
||||||
) => Promise<void>;
|
|
||||||
/**
|
/**
|
||||||
* Function to be called when the operation is cancelled.
|
* Function to be called when the operation is cancelled.
|
||||||
*/
|
*/
|
||||||
@@ -53,19 +53,15 @@ export interface EditUserFormProps {
|
|||||||
/**
|
/**
|
||||||
* Function to be called when banning the user.
|
* Function to be called when banning the user.
|
||||||
*/
|
*/
|
||||||
onBanUser?: (user: RemoteAppUser) => Promise<void>;
|
onBanUser?: (user: RemoteAppUser) => Promise<void> | void;
|
||||||
/**
|
/**
|
||||||
* Function to be called when deleting the user.
|
* Function to be called when deleting the user.
|
||||||
*/
|
*/
|
||||||
onDeleteUser: (user: RemoteAppUser) => Promise<void>;
|
onDeleteUser: (user: RemoteAppUser) => Promise<void> | void;
|
||||||
/**
|
/**
|
||||||
* User roles
|
* User roles
|
||||||
*/
|
*/
|
||||||
roles: { [key: string]: boolean }[];
|
roles: { [key: string]: boolean }[];
|
||||||
/**
|
|
||||||
* Function to be called after a successful action.
|
|
||||||
*/
|
|
||||||
onSuccessfulAction?: () => Promise<void> | void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditUserFormValidationSchema = Yup.object({
|
export const EditUserFormValidationSchema = Yup.object({
|
||||||
@@ -87,12 +83,12 @@ export type EditUserFormValues = Yup.InferType<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
export default function EditUserForm({
|
export default function EditUserForm({
|
||||||
|
location,
|
||||||
user,
|
user,
|
||||||
onEditUser,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
onDeleteUser,
|
onDeleteUser,
|
||||||
roles,
|
roles,
|
||||||
onSuccessfulAction,
|
|
||||||
}: EditUserFormProps) {
|
}: EditUserFormProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { onDirtyStateChange, openDialog } = useDialog();
|
const { onDirtyStateChange, openDialog } = useDialog();
|
||||||
@@ -104,6 +100,7 @@ export default function EditUserForm({
|
|||||||
|
|
||||||
const [updateUser] = useUpdateRemoteAppUserMutation({
|
const [updateUser] = useUpdateRemoteAppUserMutation({
|
||||||
client: remoteProjectGQLClient,
|
client: remoteProjectGQLClient,
|
||||||
|
refetchQueries: [RemoteAppGetUsersDocument],
|
||||||
});
|
});
|
||||||
|
|
||||||
const form = useForm<EditUserFormValues>({
|
const form = useForm<EditUserFormValues>({
|
||||||
@@ -124,20 +121,19 @@ export default function EditUserForm({
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
|
||||||
formState: { errors, dirtyFields, isSubmitting, isValidating },
|
formState: { errors, dirtyFields, isSubmitting, isValidating },
|
||||||
} = form;
|
} = form;
|
||||||
|
|
||||||
const isDirty = Object.keys(dirtyFields).length > 0;
|
const isDirty = Object.keys(dirtyFields).length > 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onDirtyStateChange(isDirty, 'drawer');
|
onDirtyStateChange(isDirty, location);
|
||||||
}, [isDirty, onDirtyStateChange]);
|
}, [isDirty, location, onDirtyStateChange]);
|
||||||
|
|
||||||
function handleChangeUserPassword() {
|
function handleChangeUserPassword() {
|
||||||
openDialog('EDIT_USER_PASSWORD', {
|
openDialog({
|
||||||
title: 'Change Password',
|
title: 'Change Password',
|
||||||
payload: { user },
|
component: <EditUserPasswordForm user={user} />,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,11 +152,13 @@ export default function EditUserForm({
|
|||||||
* both having to refetch this single user from the database again or causing a re-render of the drawer.
|
* both having to refetch this single user from the database again or causing a re-render of the drawer.
|
||||||
*/
|
*/
|
||||||
async function handleUserDisabledStatus() {
|
async function handleUserDisabledStatus() {
|
||||||
|
const shouldBan = !isUserBanned;
|
||||||
|
|
||||||
const banUser = updateUser({
|
const banUser = updateUser({
|
||||||
variables: {
|
variables: {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
user: {
|
user: {
|
||||||
disabled: !isUserBanned,
|
disabled: shouldBan,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -168,26 +166,23 @@ export default function EditUserForm({
|
|||||||
await toast.promise(
|
await toast.promise(
|
||||||
banUser,
|
banUser,
|
||||||
{
|
{
|
||||||
loading: user.disabled ? 'Unbanning user...' : 'Banning user...',
|
loading: shouldBan ? 'Banning user...' : 'Unbanning user...',
|
||||||
success: user.disabled
|
success: shouldBan
|
||||||
? 'User unbanned successfully.'
|
? 'User banned successfully'
|
||||||
: 'User banned successfully',
|
: 'User unbanned successfully.',
|
||||||
error: user.disabled
|
error: shouldBan
|
||||||
? 'An error occurred while trying to unban the user.'
|
? 'An error occurred while trying to ban the user.'
|
||||||
: 'An error occurred while trying to ban the user.',
|
: 'An error occurred while trying to unban the user.',
|
||||||
},
|
},
|
||||||
getToastStyleProps(),
|
getToastStyleProps(),
|
||||||
);
|
);
|
||||||
await onSuccessfulAction();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<Form
|
<Form
|
||||||
className="flex flex-col overflow-hidden border-t-1 lg:flex-auto lg:content-between"
|
className="flex flex-col overflow-hidden border-t-1 lg:flex-auto lg:content-between"
|
||||||
onSubmit={handleSubmit(async (values) => {
|
onSubmit={onSubmit}
|
||||||
await onEditUser(values, user);
|
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
<Box className="flex-auto divide-y overflow-y-auto">
|
<Box className="flex-auto divide-y overflow-y-auto">
|
||||||
<Box
|
<Box
|
||||||
@@ -268,7 +263,7 @@ export default function EditUserForm({
|
|||||||
Created At
|
Created At
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Text className="col-span-3 font-medium">
|
<Text className="col-span-3 font-medium">
|
||||||
{format(new Date(user.createdAt), 'yyyy-MM-dd hh:mm:ss')}
|
{format(new Date(user.createdAt), 'yyyy-MM-dd HH:mm:ss')}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<InputLabel as="h3" className="col-span-1 self-center ">
|
<InputLabel as="h3" className="col-span-1 self-center ">
|
||||||
@@ -276,7 +271,7 @@ export default function EditUserForm({
|
|||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Text className="col-span-3 font-medium">
|
<Text className="col-span-3 font-medium">
|
||||||
{user.lastSeen
|
{user.lastSeen
|
||||||
? `${format(new Date(user.lastSeen), 'yyyy-mm-dd hh:mm:ss')}`
|
? `${format(new Date(user.lastSeen), 'yyyy-MM-dd HH:mm:ss')}`
|
||||||
: '-'}
|
: '-'}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
||||||
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import { Alert } from '@/ui/Alert';
|
import { Alert } from '@/ui/Alert';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
@@ -14,18 +15,7 @@ import { FormProvider, useForm } from 'react-hook-form';
|
|||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
export interface EditUserPasswordFormValues {
|
export interface EditUserPasswordFormProps extends DialogFormProps {
|
||||||
/**
|
|
||||||
* Password for the user.
|
|
||||||
*/
|
|
||||||
password: string;
|
|
||||||
/**
|
|
||||||
* Confirm Password for the user.
|
|
||||||
*/
|
|
||||||
cpassword: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EditUserPasswordFormProps {
|
|
||||||
/**
|
/**
|
||||||
* Function to be called when the operation is cancelled.
|
* Function to be called when the operation is cancelled.
|
||||||
*/
|
*/
|
||||||
@@ -36,7 +26,7 @@ export interface EditUserPasswordFormProps {
|
|||||||
user: RemoteAppGetUsersQuery['users'][0];
|
user: RemoteAppGetUsersQuery['users'][0];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditUserPasswordFormValidationSchema = Yup.object().shape({
|
export const validationSchema = Yup.object({
|
||||||
password: Yup.string()
|
password: Yup.string()
|
||||||
.label('Users Password')
|
.label('Users Password')
|
||||||
.min(8, 'Password must be at least 8 characters long.')
|
.min(8, 'Password must be at least 8 characters long.')
|
||||||
@@ -47,6 +37,8 @@ export const EditUserPasswordFormValidationSchema = Yup.object().shape({
|
|||||||
.oneOf([Yup.ref('password')], 'Passwords do not match'),
|
.oneOf([Yup.ref('password')], 'Passwords do not match'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type EditUserPasswordFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function EditUserPasswordForm({
|
export default function EditUserPasswordForm({
|
||||||
onCancel,
|
onCancel,
|
||||||
user,
|
user,
|
||||||
@@ -63,7 +55,7 @@ export default function EditUserPasswordForm({
|
|||||||
const form = useForm<EditUserPasswordFormValues>({
|
const form = useForm<EditUserPasswordFormValues>({
|
||||||
defaultValues: {},
|
defaultValues: {},
|
||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
resolver: yupResolver(EditUserPasswordFormValidationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = async ({ password }: EditUserPasswordFormValues) => {
|
const handleSubmit = async ({ password }: EditUserPasswordFormValues) => {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import FormActivityIndicator from '@/components/common/FormActivityIndicator';
|
||||||
import type { EditUserFormValues } from '@/components/users/EditUserForm';
|
import type { EditUserFormValues } from '@/components/users/EditUserForm';
|
||||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||||
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
||||||
@@ -17,7 +18,6 @@ import Text from '@/ui/v2/Text';
|
|||||||
import getReadableProviderName from '@/utils/common/getReadableProviderName';
|
import getReadableProviderName from '@/utils/common/getReadableProviderName';
|
||||||
import getUserRoles from '@/utils/settings/getUserRoles';
|
import getUserRoles from '@/utils/settings/getUserRoles';
|
||||||
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
|
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
|
||||||
import type { RemoteAppGetUsersQuery } from '@/utils/__generated__/graphql';
|
|
||||||
import {
|
import {
|
||||||
useDeleteRemoteAppUserRolesMutation,
|
useDeleteRemoteAppUserRolesMutation,
|
||||||
useGetRolesQuery,
|
useGetRolesQuery,
|
||||||
@@ -25,16 +25,21 @@ import {
|
|||||||
useRemoteAppDeleteUserMutation,
|
useRemoteAppDeleteUserMutation,
|
||||||
useUpdateRemoteAppUserMutation,
|
useUpdateRemoteAppUserMutation,
|
||||||
} from '@/utils/__generated__/graphql';
|
} from '@/utils/__generated__/graphql';
|
||||||
import type { ApolloQueryResult } from '@apollo/client';
|
|
||||||
import { useTheme } from '@mui/material';
|
import { useTheme } from '@mui/material';
|
||||||
import { formatDistance } from 'date-fns';
|
import { formatDistance } from 'date-fns';
|
||||||
import kebabCase from 'just-kebab-case';
|
import kebabCase from 'just-kebab-case';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import type { RemoteAppUser } from 'pages/[workspaceSlug]/[appSlug]/users';
|
import type { RemoteAppUser } from 'pages/[workspaceSlug]/[appSlug]/users';
|
||||||
import { Fragment, useMemo } from 'react';
|
import { Fragment, useMemo } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
export interface UsersBodyProps<T = {}> {
|
const EditUserForm = dynamic(() => import('@/components/users/EditUserForm'), {
|
||||||
|
ssr: false,
|
||||||
|
loading: () => <FormActivityIndicator />,
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface UsersBodyProps {
|
||||||
/**
|
/**
|
||||||
* The users fetched from entering the users page given a limit and offset.
|
* The users fetched from entering the users page given a limit and offset.
|
||||||
* @remark users will be an empty array if there are no users.
|
* @remark users will be an empty array if there are no users.
|
||||||
@@ -46,13 +51,10 @@ export interface UsersBodyProps<T = {}> {
|
|||||||
* @example onSuccessfulAction={() => refetch()}
|
* @example onSuccessfulAction={() => refetch()}
|
||||||
* @example onSuccessfulAction={() => router.reload()}
|
* @example onSuccessfulAction={() => router.reload()}
|
||||||
*/
|
*/
|
||||||
onSuccessfulAction?: () => Promise<void> | void | Promise<T>;
|
onSubmit?: () => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function UsersBody({
|
export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
||||||
users,
|
|
||||||
onSuccessfulAction,
|
|
||||||
}: UsersBodyProps<ApolloQueryResult<RemoteAppGetUsersQuery>>) {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { openAlertDialog, openDrawer, closeDrawer } = useDialog();
|
const { openAlertDialog, openDrawer, closeDrawer } = useDialog();
|
||||||
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
||||||
@@ -151,7 +153,8 @@ export default function UsersBody({
|
|||||||
},
|
},
|
||||||
getToastStyleProps(),
|
getToastStyleProps(),
|
||||||
);
|
);
|
||||||
await onSuccessfulAction?.();
|
|
||||||
|
await onSubmit?.();
|
||||||
|
|
||||||
closeDrawer();
|
closeDrawer();
|
||||||
}
|
}
|
||||||
@@ -181,7 +184,7 @@ export default function UsersBody({
|
|||||||
getToastStyleProps(),
|
getToastStyleProps(),
|
||||||
);
|
);
|
||||||
|
|
||||||
await onSuccessfulAction();
|
await onSubmit();
|
||||||
closeDrawer();
|
closeDrawer();
|
||||||
},
|
},
|
||||||
primaryButtonColor: 'error',
|
primaryButtonColor: 'error',
|
||||||
@@ -191,20 +194,20 @@ export default function UsersBody({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleViewUser(user: RemoteAppUser) {
|
function handleViewUser(user: RemoteAppUser) {
|
||||||
openDrawer('EDIT_USER', {
|
openDrawer({
|
||||||
title: 'User Details',
|
title: 'User Details',
|
||||||
|
component: (
|
||||||
payload: {
|
<EditUserForm
|
||||||
user,
|
user={user}
|
||||||
onEditUser: handleEditUser,
|
onSubmit={(values) => handleEditUser(values, user)}
|
||||||
onDeleteUser: handleDeleteUser,
|
onDeleteUser={handleDeleteUser}
|
||||||
onSuccessfulAction,
|
roles={allAvailableProjectRoles.map((role) => ({
|
||||||
roles: allAvailableProjectRoles.map((role) => ({
|
[role.name]: user.roles.some(
|
||||||
[role.name]: user.roles.some(
|
(userRole) => userRole.role === role.name,
|
||||||
(userRole) => userRole.role === role.name,
|
),
|
||||||
),
|
}))}
|
||||||
})),
|
/>
|
||||||
},
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import EditWorkspaceNameForm from '@/components/home/EditWorkspaceNameForm';
|
||||||
import RemoveWorkspaceModal from '@/components/workspace/RemoveWorkspaceModal';
|
import RemoveWorkspaceModal from '@/components/workspace/RemoveWorkspaceModal';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
import { useGetWorkspace } from '@/hooks/use-GetWorkspace';
|
import { useGetWorkspace } from '@/hooks/use-GetWorkspace';
|
||||||
@@ -114,7 +115,7 @@ export default function WorkspaceHeader() {
|
|||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
className="py-2"
|
className="py-2"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openDialog('EDIT_WORKSPACE_NAME', {
|
openDialog({
|
||||||
title: (
|
title: (
|
||||||
<span className="grid grid-flow-row">
|
<span className="grid grid-flow-row">
|
||||||
<span>Change Workspace Name</span>
|
<span>Change Workspace Name</span>
|
||||||
@@ -124,10 +125,12 @@ export default function WorkspaceHeader() {
|
|||||||
</Text>
|
</Text>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
payload: {
|
component: (
|
||||||
currentWorkspaceName: currentWorkspace.name,
|
<EditWorkspaceNameForm
|
||||||
currentWorkspaceId: currentWorkspace.id,
|
currentWorkspaceId={currentWorkspace.id}
|
||||||
},
|
currentWorkspaceName={currentWorkspace.name}
|
||||||
|
/>
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import EditWorkspaceNameForm from '@/components/home/EditWorkspaceNameForm';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import PlusCircleIcon from '@/ui/v2/icons/PlusCircleIcon';
|
import PlusCircleIcon from '@/ui/v2/icons/PlusCircleIcon';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
@@ -16,7 +17,7 @@ export function WorkspaceSection() {
|
|||||||
variant="borderless"
|
variant="borderless"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openDialog('EDIT_WORKSPACE_NAME', {
|
openDialog({
|
||||||
title: (
|
title: (
|
||||||
<span className="grid grid-flow-row">
|
<span className="grid grid-flow-row">
|
||||||
<span>New Workspace</span>
|
<span>New Workspace</span>
|
||||||
@@ -26,6 +27,7 @@ export function WorkspaceSection() {
|
|||||||
</Text>
|
</Text>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
|
component: <EditWorkspaceNameForm />,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
startIcon={<PlusCircleIcon />}
|
startIcon={<PlusCircleIcon />}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useDialog } from '@/components/common/DialogProvider';
|
|||||||
import Pagination from '@/components/common/Pagination';
|
import Pagination from '@/components/common/Pagination';
|
||||||
import Container from '@/components/layout/Container';
|
import Container from '@/components/layout/Container';
|
||||||
import ProjectLayout from '@/components/layout/ProjectLayout';
|
import ProjectLayout from '@/components/layout/ProjectLayout';
|
||||||
|
import CreateUserForm from '@/components/users/CreateUserForm';
|
||||||
import UsersBody from '@/components/users/UsersBody';
|
import UsersBody from '@/components/users/UsersBody';
|
||||||
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
@@ -25,7 +26,7 @@ export type RemoteAppUser = Exclude<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
export default function UsersPage() {
|
export default function UsersPage() {
|
||||||
const { openDialog, closeDialog } = useDialog();
|
const { openDialog } = useDialog();
|
||||||
const remoteProjectGQLClient = useRemoteApplicationGQLClient();
|
const remoteProjectGQLClient = useRemoteApplicationGQLClient();
|
||||||
const [searchString, setSearchString] = useState<string>('');
|
const [searchString, setSearchString] = useState<string>('');
|
||||||
|
|
||||||
@@ -200,14 +201,9 @@ export default function UsersPage() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
function openCreateUserDialog() {
|
function openCreateUserDialog() {
|
||||||
openDialog('CREATE_USER', {
|
openDialog({
|
||||||
title: 'Create User',
|
title: 'Create User',
|
||||||
payload: {
|
component: <CreateUserForm onSubmit={refetchProjectUsers} />,
|
||||||
onSuccess: async () => {
|
|
||||||
await refetchProjectUsers();
|
|
||||||
closeDialog();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,16 +224,16 @@ export default function UsersPage() {
|
|||||||
if (loadingRemoteAppUsersQuery) {
|
if (loadingRemoteAppUsersQuery) {
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
className="flex flex-col max-w-9xl h-full"
|
className="flex h-full max-w-9xl flex-col"
|
||||||
rootClassName="h-full"
|
rootClassName="h-full"
|
||||||
>
|
>
|
||||||
<div className="flex flex-row place-content-between shrink-0 grow-0">
|
<div className="flex shrink-0 grow-0 flex-row place-content-between">
|
||||||
<Input
|
<Input
|
||||||
className="rounded-sm"
|
className="rounded-sm"
|
||||||
placeholder="Search users"
|
placeholder="Search users"
|
||||||
startAdornment={
|
startAdornment={
|
||||||
<SearchIcon
|
<SearchIcon
|
||||||
className="w-4 h-4 ml-2 -mr-1 shrink-0"
|
className="ml-2 -mr-1 h-4 w-4 shrink-0"
|
||||||
sx={{ color: 'text.disabled' }}
|
sx={{ color: 'text.disabled' }}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -245,14 +241,14 @@ export default function UsersPage() {
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={openCreateUserDialog}
|
onClick={openCreateUserDialog}
|
||||||
startIcon={<PlusIcon className="w-4 h-4" />}
|
startIcon={<PlusIcon className="h-4 w-4" />}
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
Create User
|
Create User
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="overflow-hidden flex items-center justify-center flex-auto">
|
<div className="flex flex-auto items-center justify-center overflow-hidden">
|
||||||
<ActivityIndicator label="Loading users..." />
|
<ActivityIndicator label="Loading users..." />
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
@@ -260,14 +256,14 @@ export default function UsersPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="mx-auto space-y-5 overflow-x-hidden max-w-9xl">
|
<Container className="mx-auto max-w-9xl space-y-5 overflow-x-hidden">
|
||||||
<div className="flex flex-row place-content-between">
|
<div className="flex flex-row place-content-between">
|
||||||
<Input
|
<Input
|
||||||
className="rounded-sm"
|
className="rounded-sm"
|
||||||
placeholder="Search users"
|
placeholder="Search users"
|
||||||
startAdornment={
|
startAdornment={
|
||||||
<SearchIcon
|
<SearchIcon
|
||||||
className="w-4 h-4 ml-2 -mr-1 shrink-0"
|
className="ml-2 -mr-1 h-4 w-4 shrink-0"
|
||||||
sx={{ color: 'text.disabled' }}
|
sx={{ color: 'text.disabled' }}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -275,21 +271,21 @@ export default function UsersPage() {
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={openCreateUserDialog}
|
onClick={openCreateUserDialog}
|
||||||
startIcon={<PlusIcon className="w-4 h-4" />}
|
startIcon={<PlusIcon className="h-4 w-4" />}
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
Create User
|
Create User
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{usersCount === 0 ? (
|
{usersCount === 0 ? (
|
||||||
<Box className="flex flex-col items-center justify-center px-48 py-12 space-y-5 border rounded-lg shadow-sm">
|
<Box className="flex flex-col items-center justify-center space-y-5 rounded-lg border px-48 py-12 shadow-sm">
|
||||||
<UserIcon
|
<UserIcon
|
||||||
strokeWidth={1}
|
strokeWidth={1}
|
||||||
className="w-10 h-10"
|
className="h-10 w-10"
|
||||||
sx={{ color: 'text.disabled' }}
|
sx={{ color: 'text.disabled' }}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-1">
|
||||||
<Text className="font-medium text-center" variant="h3">
|
<Text className="text-center font-medium" variant="h3">
|
||||||
There are no users yet
|
There are no users yet
|
||||||
</Text>
|
</Text>
|
||||||
<Text variant="subtitle1" className="text-center">
|
<Text variant="subtitle1" className="text-center">
|
||||||
@@ -302,34 +298,34 @@ export default function UsersPage() {
|
|||||||
color="primary"
|
color="primary"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
onClick={openCreateUserDialog}
|
onClick={openCreateUserDialog}
|
||||||
startIcon={<PlusIcon className="w-4 h-4" />}
|
startIcon={<PlusIcon className="h-4 w-4" />}
|
||||||
>
|
>
|
||||||
Create User
|
Create User
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-flow-row gap-2 lg:w-9xl">
|
<div className="lg:w-9xl grid grid-flow-row gap-2">
|
||||||
<div className="grid w-full h-full grid-flow-row pb-4 overflow-hidden">
|
<div className="grid h-full w-full grid-flow-row overflow-hidden pb-4">
|
||||||
<Box className="grid w-full p-2 border-b md:grid-cols-6">
|
<Box className="grid w-full border-b p-2 md:grid-cols-6">
|
||||||
<Text className="font-medium md:col-span-2">Name</Text>
|
<Text className="font-medium md:col-span-2">Name</Text>
|
||||||
<Text className="hidden font-medium md:block">Signed up at</Text>
|
<Text className="hidden font-medium md:block">Signed up at</Text>
|
||||||
<Text className="hidden font-medium md:block">Last Seen</Text>
|
<Text className="hidden font-medium md:block">Last Seen</Text>
|
||||||
<Text className="hidden col-span-2 font-medium md:block">
|
<Text className="col-span-2 hidden font-medium md:block">
|
||||||
OAuth Providers
|
OAuth Providers
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
{dataRemoteAppUsers?.filteredUsersAggreggate.aggregate.count ===
|
{dataRemoteAppUsers?.filteredUsersAggreggate.aggregate.count ===
|
||||||
0 &&
|
0 &&
|
||||||
usersCount !== 0 && (
|
usersCount !== 0 && (
|
||||||
<Box className="flex flex-col items-center justify-center px-48 py-12 space-y-5 border-b border-x">
|
<Box className="flex flex-col items-center justify-center space-y-5 border-x border-b px-48 py-12">
|
||||||
<UserIcon
|
<UserIcon
|
||||||
strokeWidth={1}
|
strokeWidth={1}
|
||||||
className="w-10 h-10"
|
className="h-10 w-10"
|
||||||
sx={{ color: 'text.disabled' }}
|
sx={{ color: 'text.disabled' }}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-1">
|
||||||
<Text className="font-medium text-center" variant="h3">
|
<Text className="text-center font-medium" variant="h3">
|
||||||
No results for "{searchString}"
|
No results for "{searchString}"
|
||||||
</Text>
|
</Text>
|
||||||
<Text variant="subtitle1" className="text-center">
|
<Text variant="subtitle1" className="text-center">
|
||||||
@@ -340,10 +336,7 @@ export default function UsersPage() {
|
|||||||
)}
|
)}
|
||||||
{thereAreUsers && (
|
{thereAreUsers && (
|
||||||
<div className="grid grid-flow-row gap-4">
|
<div className="grid grid-flow-row gap-4">
|
||||||
<UsersBody
|
<UsersBody users={users} onSubmit={refetchProjectUsers} />
|
||||||
users={users}
|
|
||||||
onSuccessfulAction={refetchProjectUsers}
|
|
||||||
/>
|
|
||||||
<Pagination
|
<Pagination
|
||||||
className="px-2"
|
className="px-2"
|
||||||
totalNrOfPages={nrOfPages}
|
totalNrOfPages={nrOfPages}
|
||||||
|
|||||||
10
dashboard/src/types/common.ts
Normal file
10
dashboard/src/types/common.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* This interface is used to define the basic properties of a form that is
|
||||||
|
* rendered inside a drawer or a dialog.
|
||||||
|
*/
|
||||||
|
export interface DialogFormProps {
|
||||||
|
/**
|
||||||
|
* Determines whether the form is rendered inside a drawer or a dialog.
|
||||||
|
*/
|
||||||
|
location?: 'drawer' | 'dialog';
|
||||||
|
}
|
||||||
@@ -1,5 +1,26 @@
|
|||||||
# @nhost-examples/codegen-react-apollo
|
# @nhost-examples/codegen-react-apollo
|
||||||
|
|
||||||
|
## 0.1.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 01318860: fix(nhost-js): use correct URL for functions requests
|
||||||
|
- Updated dependencies [01318860]
|
||||||
|
- @nhost/react-apollo@5.0.5
|
||||||
|
- @nhost/react@2.0.4
|
||||||
|
|
||||||
|
## 0.1.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/react` to 2.0.3
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/react-apollo` to 5.0.4
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- @nhost/react-apollo@5.0.4
|
||||||
|
- @nhost/react@2.0.3
|
||||||
|
|
||||||
## 0.1.5
|
## 0.1.5
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/codegen-react-apollo",
|
"name": "@nhost-examples/codegen-react-apollo",
|
||||||
"version": "0.1.5",
|
"version": "0.1.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"codegen": "graphql-codegen",
|
"codegen": "graphql-codegen",
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
# @nhost-examples/codegen-react-query
|
# @nhost-examples/codegen-react-query
|
||||||
|
|
||||||
|
## 0.1.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 01318860: fix(nhost-js): use correct URL for functions requests
|
||||||
|
- Updated dependencies [01318860]
|
||||||
|
- @nhost/react@2.0.4
|
||||||
|
|
||||||
|
## 0.1.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/react` to 2.0.3
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- @nhost/react@2.0.3
|
||||||
|
|
||||||
## 0.1.5
|
## 0.1.5
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/codegen-react-query",
|
"name": "@nhost-examples/codegen-react-query",
|
||||||
"version": "0.1.5",
|
"version": "0.1.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"codegen": "graphql-codegen",
|
"codegen": "graphql-codegen",
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
# @nhost-examples/react-urql
|
# @nhost-examples/react-urql
|
||||||
|
|
||||||
|
## 0.0.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 01318860: fix(nhost-js): use correct URL for functions requests
|
||||||
|
- Updated dependencies [01318860]
|
||||||
|
- @nhost/react-urql@2.0.4
|
||||||
|
- @nhost/react@2.0.4
|
||||||
|
|
||||||
|
## 0.0.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/react` to 2.0.3
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- @nhost/react-urql@2.0.3
|
||||||
|
- @nhost/react@2.0.3
|
||||||
|
|
||||||
## 0.0.2
|
## 0.0.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/codegen-react-urql",
|
"name": "@nhost-examples/codegen-react-urql",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.2",
|
"version": "0.0.4",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost-examples/docker-compose
|
# @nhost-examples/docker-compose
|
||||||
|
|
||||||
|
## 0.0.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 01318860: fix(nhost-js): use correct URL for functions requests
|
||||||
|
|
||||||
## 0.0.4
|
## 0.0.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/docker-compose",
|
"name": "@nhost-examples/docker-compose",
|
||||||
"version": "0.0.4",
|
"version": "0.0.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"e2e": "vitest run"
|
"e2e": "vitest run"
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
# @nhost-examples/multi-tenant-one-to-many
|
# @nhost-examples/multi-tenant-one-to-many
|
||||||
|
|
||||||
|
## 1.0.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 01318860: fix(nhost-js): use correct URL for functions requests
|
||||||
|
- Updated dependencies [01318860]
|
||||||
|
- @nhost/nhost-js@2.0.4
|
||||||
|
|
||||||
|
## 1.0.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/nhost-js` version to 2.0.3
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- @nhost/nhost-js@2.0.3
|
||||||
|
|
||||||
## 1.0.1
|
## 1.0.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/multi-tenant-one-to-many",
|
"name": "@nhost-examples/multi-tenant-one-to-many",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.1",
|
"version": "1.0.3",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
|
|||||||
@@ -1,5 +1,29 @@
|
|||||||
# @nhost-examples/nextjs
|
# @nhost-examples/nextjs
|
||||||
|
|
||||||
|
## 0.1.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 01318860: fix(nhost-js): use correct URL for functions requests
|
||||||
|
- Updated dependencies [01318860]
|
||||||
|
- @nhost/react-apollo@5.0.5
|
||||||
|
- @nhost/nextjs@1.13.10
|
||||||
|
- @nhost/react@2.0.4
|
||||||
|
|
||||||
|
## 0.1.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/react` to 2.0.3
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/react-apollo` to 5.0.4
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/nextjs` to 1.13.9
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- @nhost/react-apollo@5.0.4
|
||||||
|
- @nhost/nextjs@1.13.9
|
||||||
|
- @nhost/react@2.0.3
|
||||||
|
|
||||||
## 0.1.5
|
## 0.1.5
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/nextjs",
|
"name": "@nhost-examples/nextjs",
|
||||||
"version": "0.1.5",
|
"version": "0.1.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
@@ -1,5 +1,26 @@
|
|||||||
# @nhost-examples/react-apollo
|
# @nhost-examples/react-apollo
|
||||||
|
|
||||||
|
## 0.1.9
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 01318860: fix(nhost-js): use correct URL for functions requests
|
||||||
|
- Updated dependencies [01318860]
|
||||||
|
- @nhost/react-apollo@5.0.5
|
||||||
|
- @nhost/react@2.0.4
|
||||||
|
|
||||||
|
## 0.1.8
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/react` to 2.0.3
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/react-apollo` to 5.0.4
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- @nhost/react-apollo@5.0.4
|
||||||
|
- @nhost/react@2.0.3
|
||||||
|
|
||||||
## 0.1.7
|
## 0.1.7
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/react-apollo",
|
"name": "@nhost-examples/react-apollo",
|
||||||
"version": "0.1.7",
|
"version": "0.1.9",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.6.9",
|
"@apollo/client": "^3.6.9",
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
# @nhost-examples/react-gqty
|
# @nhost-examples/react-gqty
|
||||||
|
|
||||||
|
## 0.0.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 01318860: fix(nhost-js): use correct URL for functions requests
|
||||||
|
- Updated dependencies [01318860]
|
||||||
|
- @nhost/react@2.0.4
|
||||||
|
|
||||||
|
## 0.0.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/react` to 2.0.3
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- @nhost/react@2.0.3
|
||||||
|
|
||||||
## 0.0.4
|
## 0.0.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/react-gqty",
|
"name": "@nhost-examples/react-gqty",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.4",
|
"version": "0.0.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
7
examples/seed-data-storage/CHANGELOG.md
Normal file
7
examples/seed-data-storage/CHANGELOG.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# @nhost-examples/seed-data-storage
|
||||||
|
|
||||||
|
## 0.0.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 01318860: fix(nhost-js): use correct URL for functions requests
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/seed-data-storage",
|
"name": "@nhost-examples/seed-data-storage",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.1",
|
"version": "0.0.2",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"seed-storage": "./seed-storage.sh"
|
"seed-storage": "./seed-storage.sh"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# @nhost-examples/serverless-functions
|
# @nhost-examples/serverless-functions
|
||||||
|
|
||||||
|
## 0.0.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 01318860: fix(nhost-js): use correct URL for functions requests
|
||||||
|
- Updated dependencies [01318860]
|
||||||
|
- @nhost/stripe-graphql-js@1.0.2
|
||||||
|
|
||||||
## 0.0.6
|
## 0.0.6
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/serverless-functions",
|
"name": "@nhost-examples/serverless-functions",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.6",
|
"version": "0.0.7",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^4.17.13"
|
"@types/express": "^4.17.13"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@graphql-yoga/node": "^2.13.13",
|
"@graphql-yoga/node": "^2.13.13",
|
||||||
"@nhost/stripe-graphql-js": "^1.0.1",
|
"@nhost/stripe-graphql-js": "^1.0.2",
|
||||||
"@pothos/core": "^3.21.0",
|
"@pothos/core": "^3.21.0",
|
||||||
"cross-fetch": "^3.1.5",
|
"cross-fetch": "^3.1.5",
|
||||||
"graphql": "15.7.2",
|
"graphql": "15.7.2",
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
# @nhost-examples/vue-apollo
|
# @nhost-examples/vue-apollo
|
||||||
|
|
||||||
|
## 0.0.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 01318860: fix(nhost-js): use correct URL for functions requests
|
||||||
|
- Updated dependencies [01318860]
|
||||||
|
- @nhost/apollo@5.0.4
|
||||||
|
- @nhost/vue@1.13.10
|
||||||
|
|
||||||
|
## 0.0.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/apollo` to 5.0.3
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- @nhost/apollo@5.0.3
|
||||||
|
- @nhost/vue@1.13.9
|
||||||
|
|
||||||
## 0.0.5
|
## 0.0.5
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/vue-apollo",
|
"name": "@nhost-examples/vue-apollo",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.5",
|
"version": "0.0.7",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
# @nhost-examples/vue-quickstart
|
# @nhost-examples/vue-quickstart
|
||||||
|
|
||||||
|
## 0.0.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 01318860: fix(nhost-js): use correct URL for functions requests
|
||||||
|
- Updated dependencies [01318860]
|
||||||
|
- @nhost/apollo@5.0.4
|
||||||
|
- @nhost/vue@1.13.10
|
||||||
|
|
||||||
|
## 0.0.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/apollo` to 5.0.3
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- @nhost/apollo@5.0.3
|
||||||
|
- @nhost/vue@1.13.9
|
||||||
|
|
||||||
## 0.0.4
|
## 0.0.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/vue-quickstart",
|
"name": "@nhost-examples/vue-quickstart",
|
||||||
"version": "0.0.4",
|
"version": "0.0.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
|||||||
@@ -1,5 +1,41 @@
|
|||||||
# @nhost/apollo
|
# @nhost/apollo
|
||||||
|
|
||||||
|
## 5.0.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [3c7cf92e]
|
||||||
|
- @nhost/nhost-js@2.0.5
|
||||||
|
|
||||||
|
## 5.0.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 01318860: fix(nhost-js): use correct URL for functions requests
|
||||||
|
- Updated dependencies [01318860]
|
||||||
|
- @nhost/nhost-js@2.0.4
|
||||||
|
|
||||||
|
## 5.0.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/nhost-js` version to 2.0.3
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- @nhost/nhost-js@2.0.3
|
||||||
|
|
||||||
|
## 5.0.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [2d9145f9]
|
||||||
|
- @nhost/nhost-js@2.0.2
|
||||||
|
|
||||||
|
## 5.0.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@2.0.1
|
||||||
|
|
||||||
## 5.0.0
|
## 5.0.0
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/apollo",
|
"name": "@nhost/apollo",
|
||||||
"version": "5.0.0",
|
"version": "5.0.5",
|
||||||
"description": "Nhost Apollo Client library",
|
"description": "Nhost Apollo Client library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost/google-translation
|
# @nhost/google-translation
|
||||||
|
|
||||||
|
## 0.0.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 01318860: fix(nhost-js): use correct URL for functions requests
|
||||||
|
|
||||||
## 0.0.2
|
## 0.0.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/google-translation",
|
"name": "@nhost/google-translation",
|
||||||
"version": "0.0.2",
|
"version": "0.0.3",
|
||||||
"description": "Google Translation GraphQL API",
|
"description": "Google Translation GraphQL API",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,45 @@
|
|||||||
# @nhost/react-apollo
|
# @nhost/react-apollo
|
||||||
|
|
||||||
|
## 5.0.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/apollo@5.0.5
|
||||||
|
- @nhost/react@2.0.5
|
||||||
|
|
||||||
|
## 5.0.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 01318860: fix(nhost-js): use correct URL for functions requests
|
||||||
|
- Updated dependencies [01318860]
|
||||||
|
- @nhost/apollo@5.0.4
|
||||||
|
- @nhost/react@2.0.4
|
||||||
|
|
||||||
|
## 5.0.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/react` to 2.0.3
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/apollo` to 5.0.3
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- @nhost/apollo@5.0.3
|
||||||
|
- @nhost/react@2.0.3
|
||||||
|
|
||||||
|
## 5.0.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/apollo@5.0.2
|
||||||
|
- @nhost/react@2.0.2
|
||||||
|
|
||||||
|
## 5.0.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/apollo@5.0.1
|
||||||
|
- @nhost/react@2.0.1
|
||||||
|
|
||||||
## 5.0.1
|
## 5.0.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/react-apollo",
|
"name": "@nhost/react-apollo",
|
||||||
"version": "5.0.1",
|
"version": "5.0.6",
|
||||||
"description": "Nhost React Apollo client",
|
"description": "Nhost React Apollo client",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,39 @@
|
|||||||
# @nhost/react-urql
|
# @nhost/react-urql
|
||||||
|
|
||||||
|
## 2.0.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@2.0.5
|
||||||
|
|
||||||
|
## 2.0.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 01318860: fix(nhost-js): use correct URL for functions requests
|
||||||
|
- Updated dependencies [01318860]
|
||||||
|
- @nhost/react@2.0.4
|
||||||
|
|
||||||
|
## 2.0.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 445d8ef4: chore(deps): bump `@nhost/react` to 2.0.3
|
||||||
|
- Updated dependencies [445d8ef4]
|
||||||
|
- @nhost/react@2.0.3
|
||||||
|
|
||||||
|
## 2.0.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@2.0.2
|
||||||
|
|
||||||
|
## 2.0.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@2.0.1
|
||||||
|
|
||||||
## 2.0.0
|
## 2.0.0
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/react-urql",
|
"name": "@nhost/react-urql",
|
||||||
"version": "2.0.0",
|
"version": "2.0.5",
|
||||||
"description": "Nhost React URQL client",
|
"description": "Nhost React URQL client",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost/stripe-graphql-js
|
# @nhost/stripe-graphql-js
|
||||||
|
|
||||||
|
## 1.0.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 01318860: fix(nhost-js): use correct URL for functions requests
|
||||||
|
|
||||||
## 1.0.1
|
## 1.0.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/stripe-graphql-js",
|
"name": "@nhost/stripe-graphql-js",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"description": "Stripe GraphQL API",
|
"description": "Stripe GraphQL API",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
14
packages/graphql-js/CHANGELOG.md
Normal file
14
packages/graphql-js/CHANGELOG.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# @nhost/graphql-js
|
||||||
|
|
||||||
|
## 0.0.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 2d9145f9: chore(deps): revert GraphQL client
|
||||||
|
|
||||||
|
## 0.0.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 2200a0ed: Correct type inference on snake case operations
|
||||||
|
- 3b48a627: Improve readme instructions
|
||||||
@@ -11,130 +11,6 @@
|
|||||||
|
|
||||||
Nhost GraphQL client.
|
Nhost GraphQL client.
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
First, install `graphql-codegen` and the Nhost Typescript plugin:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install @nhost/graphql-js
|
|
||||||
npm install -D @graphql-codegen/cli @graphql-codegen/typescript-nhost
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configure the code generator
|
|
||||||
|
|
||||||
Configure the code generator by adding a `graphql.config.yaml` file:
|
|
||||||
|
|
||||||
```yaml filename="graphql.config.yaml"
|
|
||||||
schema:
|
|
||||||
- http://localhost:1337/v1/graphql:
|
|
||||||
headers:
|
|
||||||
x-hasura-admin-secret: nhost-admin-secret
|
|
||||||
generates:
|
|
||||||
./src/schema.ts:
|
|
||||||
plugins:
|
|
||||||
- typescript-nhost
|
|
||||||
```
|
|
||||||
|
|
||||||
Generate the schema:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
yarn graphql-codegen
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```ts filename="./src/main.ts"
|
|
||||||
import { NhostGraphqlClient } from '@nhost/graphql-js'
|
|
||||||
import schema from './schema.ts'
|
|
||||||
|
|
||||||
const client = new NhostGraphqlClient({ url: 'http://localhost:1337/v1/graphql', schema })
|
|
||||||
```
|
|
||||||
|
|
||||||
### Basic GraphQL requests
|
|
||||||
|
|
||||||
### Queries
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const todos = await client.query.todos({
|
|
||||||
select: { createdAt: true, contents: true, user: { select: { displayName: true } } }
|
|
||||||
})
|
|
||||||
|
|
||||||
todos.map(({ createdAt, contents, user: { displayName } }) => {
|
|
||||||
console.log(`${displayName} created the following todo at ${createdAt}: ${contents}`)
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Select all scalar fields:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const todos = await client.query.todos()
|
|
||||||
|
|
||||||
todos.map(({ createdAt, contents }) => {
|
|
||||||
console.log(`Todo created at ${createdAt}: ${contents}`)
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Pass on parameters:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const todos = await client.query.todos({
|
|
||||||
variables: { where: { contents: { _eq: 'document the sdk' } } },
|
|
||||||
select: { createdAt: true, contents: true, user: { select: { displayName: true } } }
|
|
||||||
})
|
|
||||||
|
|
||||||
todos.map(({ createdAt, contents }) => {
|
|
||||||
console.log(`${displayName} created the following todo at ${createdAt}: ${contents}`)
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mutations
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const { id } = await client.mutation.insertTodo({
|
|
||||||
select: { id: true },
|
|
||||||
variables: { contents: 'document the sdk', userId: 'xxx-yyy-zzz' }
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Enums
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const todos = await client.query.todos({
|
|
||||||
variables: {
|
|
||||||
where: {
|
|
||||||
category: { _eq: 'essay' } // the client detects 'essay' is a GraphQL enum value
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contents: true,
|
|
||||||
category: true
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Unions
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const giraffes = await client.query
|
|
||||||
.giraffeFacts({
|
|
||||||
_on: {
|
|
||||||
GiraffeNumericFact: { value: true },
|
|
||||||
GiraffeStringFact: { fact: true }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.run()
|
|
||||||
|
|
||||||
giraffes.forEach((giraffe) => {
|
|
||||||
if (giraffe.__typename === 'GiraffeNumericFact') {
|
|
||||||
// * We are in the GiraffeNumericFact fragment: only `value` is available
|
|
||||||
console.log('Value:', giraffe.value)
|
|
||||||
} else {
|
|
||||||
// * We are in the GiraffeStringFact fragment: only `fact` is available
|
|
||||||
console.log('Fact:', giraffe.fact)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Interfaces
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
[https://docs.nhost.io/reference/javascript/auth](https://docs.nhost.io/reference/javascript/graphql)
|
[https://docs.nhost.io/reference/javascript/graphql](https://docs.nhost.io/reference/javascript/graphql)
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
import SchemaBuilder from '@pothos/core'
|
|
||||||
import { createYoga } from 'graphql-yoga'
|
|
||||||
|
|
||||||
class Human {
|
|
||||||
public pets: Pet[] = []
|
|
||||||
constructor(public phoneNumber: string, public firstName: string) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Pet {
|
|
||||||
name: string
|
|
||||||
diet: Diet
|
|
||||||
owner: Human
|
|
||||||
constructor(name: string, diet: Diet, owner: Human) {
|
|
||||||
this.name = name
|
|
||||||
this.diet = diet
|
|
||||||
this.owner = owner
|
|
||||||
}
|
|
||||||
// constructor(public name: string, public diet: Diet, public owner: Human) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Diet {
|
|
||||||
HERBIVOROUS,
|
|
||||||
CARNIVOROUS,
|
|
||||||
OMNIVORIOUS
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Dog extends Pet {
|
|
||||||
constructor(name: string, owner: Human, public barks: boolean) {
|
|
||||||
super(name, Diet.CARNIVOROUS, owner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Hamster extends Pet {
|
|
||||||
constructor(name: string, owner: Human, public squeaks: boolean) {
|
|
||||||
super(name, Diet.HERBIVOROUS, owner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const human1 = new Human('123-456-7890', 'John')
|
|
||||||
const dog1 = new Dog('Fido', human1, false)
|
|
||||||
const dog2 = new Dog('Rover', human1, true)
|
|
||||||
const hamster1 = new Hamster('Hammy', human1, true)
|
|
||||||
human1.pets = [dog1, dog2, hamster1]
|
|
||||||
|
|
||||||
const builder = new SchemaBuilder<{
|
|
||||||
Objects: {
|
|
||||||
Pet: Pet
|
|
||||||
Human: Human
|
|
||||||
Dog: Dog
|
|
||||||
}
|
|
||||||
}>({})
|
|
||||||
|
|
||||||
const HumanObject = builder.objectType('Human', {
|
|
||||||
fields: (t) => ({
|
|
||||||
phoneNumber: t.exposeString('phoneNumber', {}),
|
|
||||||
firstName: t.exposeString('firstName', {}),
|
|
||||||
pets: t.field({
|
|
||||||
type: [Pet],
|
|
||||||
resolve: (h) => h.pets
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const PetObject = builder.interfaceType(Pet, {
|
|
||||||
name: 'Pet',
|
|
||||||
fields: (t) => ({
|
|
||||||
name: t.exposeString('name', {}),
|
|
||||||
owner: t.field({ type: HumanObject, resolve: (p) => p.owner }),
|
|
||||||
diet: t.expose('diet', {
|
|
||||||
type: Diet
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const DogObject = builder.objectType('Dog', {
|
|
||||||
interfaces: [Pet],
|
|
||||||
isTypeOf: (value) => value instanceof Dog,
|
|
||||||
fields: (t) => ({
|
|
||||||
barks: t.exposeBoolean('barks', {})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const HamsterObject = builder.objectType(Hamster, {
|
|
||||||
name: 'Hamster',
|
|
||||||
interfaces: [Pet],
|
|
||||||
isTypeOf: (value) => value instanceof Hamster,
|
|
||||||
fields: (t) => ({
|
|
||||||
squeaks: t.exposeBoolean('squeaks', {})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
builder.enumType(Diet, {
|
|
||||||
name: 'Diet'
|
|
||||||
})
|
|
||||||
|
|
||||||
const Anyone = builder.unionType('Anyone', {
|
|
||||||
types: [DogObject, HamsterObject, HumanObject],
|
|
||||||
resolveType: (fact) => {
|
|
||||||
if (fact instanceof Human) {
|
|
||||||
return HumanObject
|
|
||||||
}
|
|
||||||
if (fact instanceof Dog) {
|
|
||||||
return DogObject
|
|
||||||
}
|
|
||||||
if (fact instanceof Hamster) {
|
|
||||||
return HamsterObject
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
builder.queryField('everyone', (t) =>
|
|
||||||
t.field({
|
|
||||||
type: [Anyone],
|
|
||||||
resolve: () => {
|
|
||||||
return [human1, dog1, dog2, hamster1]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
builder.queryField('pets', (t) =>
|
|
||||||
t.field({ type: [PetObject], resolve: () => [dog1, dog2, hamster1] })
|
|
||||||
)
|
|
||||||
builder.queryField('dogs', (t) => t.field({ type: [DogObject], resolve: () => [dog1, dog2] }))
|
|
||||||
builder.queryField('hamsters', (t) => t.field({ type: [HamsterObject], resolve: () => [hamster1] }))
|
|
||||||
|
|
||||||
builder.queryType({})
|
|
||||||
|
|
||||||
export default createYoga({
|
|
||||||
schema: builder.toSchema(),
|
|
||||||
graphqlEndpoint: '/'
|
|
||||||
})
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "functions",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"@pothos/core": "^3.24.0",
|
|
||||||
"@types/node": "^18.11.18",
|
|
||||||
"graphql-yoga": "^3.3.0",
|
|
||||||
"graphql": "^16.6.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
|
||||||
"compilerOptions": {
|
|
||||||
"declaration": true,
|
|
||||||
"declarationMap": true,
|
|
||||||
"strict": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"target": "ES6",
|
|
||||||
"module": "CommonJS",
|
|
||||||
"types": [
|
|
||||||
"node"
|
|
||||||
],
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"sourceMap": true
|
|
||||||
},
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
"dist"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,366 +0,0 @@
|
|||||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
|
||||||
# yarn lockfile v1
|
|
||||||
|
|
||||||
|
|
||||||
"@envelop/core@3.0.4":
|
|
||||||
version "3.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@envelop/core/-/core-3.0.4.tgz#6801049bed24487599b4ffa0f836f70cb62714fc"
|
|
||||||
integrity sha512-AybIZxQsDlFQTWHy6YtX/MSQPVuw+eOFtTW90JsHn6EbmcQnD6N3edQfSiTGjggPRHLoC0+0cuYXp2Ly2r3vrQ==
|
|
||||||
dependencies:
|
|
||||||
"@envelop/types" "3.0.1"
|
|
||||||
tslib "2.4.0"
|
|
||||||
|
|
||||||
"@envelop/parser-cache@^5.0.4":
|
|
||||||
version "5.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@envelop/parser-cache/-/parser-cache-5.0.4.tgz#4ff19c16c601c6137a6774fc5660f2e18768c05c"
|
|
||||||
integrity sha512-+kp6nzCVLYI2WQExQcE3FSy6n9ZGB5GYi+ntyjYdxaXU41U1f8RVwiLdyh0Ewn5D/s/zaLin09xkFKITVSAKDw==
|
|
||||||
dependencies:
|
|
||||||
lru-cache "^6.0.0"
|
|
||||||
tslib "^2.4.0"
|
|
||||||
|
|
||||||
"@envelop/types@3.0.1":
|
|
||||||
version "3.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@envelop/types/-/types-3.0.1.tgz#0afec3b3f1ab282bc828e6c42c5e26d76ffe363c"
|
|
||||||
integrity sha512-Ok62K1K+rlS+wQw77k8Pis8+1/h7+/9Wk5Fgcc2U6M5haEWsLFAHcHsk8rYlnJdEUl2Y3yJcCSOYbt1dyTaU5w==
|
|
||||||
dependencies:
|
|
||||||
tslib "^2.4.0"
|
|
||||||
|
|
||||||
"@envelop/validation-cache@^5.0.5":
|
|
||||||
version "5.0.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@envelop/validation-cache/-/validation-cache-5.0.5.tgz#9be1c1ba178460dcaf6d277136a381833cc4f931"
|
|
||||||
integrity sha512-69sq5H7hvxE+7VV60i0bgnOiV1PX9GEJHKrBrVvyEZAXqYojKO3DP9jnLGryiPgVaBjN5yw12ge0l0s2gXbolQ==
|
|
||||||
dependencies:
|
|
||||||
lru-cache "^6.0.0"
|
|
||||||
tslib "^2.4.0"
|
|
||||||
|
|
||||||
"@graphql-tools/executor@0.0.12":
|
|
||||||
version "0.0.12"
|
|
||||||
resolved "https://registry.yarnpkg.com/@graphql-tools/executor/-/executor-0.0.12.tgz#d885c7fa98a8aaeaa771163b71fb98ce9f52f9bd"
|
|
||||||
integrity sha512-bWpZcYRo81jDoTVONTnxS9dDHhEkNVjxzvFCH4CRpuyzD3uL+5w3MhtxIh24QyWm4LvQ4f+Bz3eMV2xU2I5+FA==
|
|
||||||
dependencies:
|
|
||||||
"@graphql-tools/utils" "9.1.4"
|
|
||||||
"@graphql-typed-document-node/core" "3.1.1"
|
|
||||||
"@repeaterjs/repeater" "3.0.4"
|
|
||||||
tslib "^2.4.0"
|
|
||||||
value-or-promise "1.0.12"
|
|
||||||
|
|
||||||
"@graphql-tools/merge@8.3.16":
|
|
||||||
version "8.3.16"
|
|
||||||
resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.3.16.tgz#fede610687b148e34ff861e8b038dcd71e20039b"
|
|
||||||
integrity sha512-In0kcOZcPIpYOKaqdrJ3thdLPE7TutFnL9tbrHUy2zCinR2O/blpRC48jPckcs0HHrUQ0pGT4HqvzMkZUeEBAw==
|
|
||||||
dependencies:
|
|
||||||
"@graphql-tools/utils" "9.1.4"
|
|
||||||
tslib "^2.4.0"
|
|
||||||
|
|
||||||
"@graphql-tools/schema@^9.0.0":
|
|
||||||
version "9.0.14"
|
|
||||||
resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-9.0.14.tgz#9a658ab82d5a7d4db73f68a44900d4c88a98f0bc"
|
|
||||||
integrity sha512-U6k+HY3Git+dsOEhq+dtWQwYg2CAgue8qBvnBXoKu5eEeH284wymMUoNm0e4IycOgMCJANVhClGEBIkLRu3FQQ==
|
|
||||||
dependencies:
|
|
||||||
"@graphql-tools/merge" "8.3.16"
|
|
||||||
"@graphql-tools/utils" "9.1.4"
|
|
||||||
tslib "^2.4.0"
|
|
||||||
value-or-promise "1.0.12"
|
|
||||||
|
|
||||||
"@graphql-tools/utils@9.1.4", "@graphql-tools/utils@^9.0.1":
|
|
||||||
version "9.1.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-9.1.4.tgz#2c9e0aefc9655dd73247667befe3c850ec014f3f"
|
|
||||||
integrity sha512-hgIeLt95h9nQgQuzbbdhuZmh+8WV7RZ/6GbTj6t3IU4Zd2zs9yYJ2jgW/krO587GMOY8zCwrjNOMzD40u3l7Vg==
|
|
||||||
dependencies:
|
|
||||||
tslib "^2.4.0"
|
|
||||||
|
|
||||||
"@graphql-typed-document-node/core@3.1.1":
|
|
||||||
version "3.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.1.tgz#076d78ce99822258cf813ecc1e7fa460fa74d052"
|
|
||||||
integrity sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==
|
|
||||||
|
|
||||||
"@graphql-yoga/subscription@^3.1.0":
|
|
||||||
version "3.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@graphql-yoga/subscription/-/subscription-3.1.0.tgz#4a0bb0b9db2602d02c68f9828603e1e40329140b"
|
|
||||||
integrity sha512-Vc9lh8KzIHyS3n4jBlCbz7zCjcbtQnOBpsymcRvHhFr2cuH+knmRn0EmzimMQ58jQ8kxoRXXC3KJS3RIxSdPIg==
|
|
||||||
dependencies:
|
|
||||||
"@graphql-yoga/typed-event-target" "^1.0.0"
|
|
||||||
"@repeaterjs/repeater" "^3.0.4"
|
|
||||||
"@whatwg-node/events" "0.0.2"
|
|
||||||
tslib "^2.3.1"
|
|
||||||
|
|
||||||
"@graphql-yoga/typed-event-target@^1.0.0":
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@graphql-yoga/typed-event-target/-/typed-event-target-1.0.0.tgz#dae3c0146f08a4dc30b5b890f8bab706c2b62199"
|
|
||||||
integrity sha512-Mqni6AEvl3VbpMtKw+TIjc9qS9a8hKhiAjFtqX488yq5oJtj9TkNlFTIacAVS3vnPiswNsmDiQqvwUOcJgi1DA==
|
|
||||||
dependencies:
|
|
||||||
"@repeaterjs/repeater" "^3.0.4"
|
|
||||||
tslib "^2.3.1"
|
|
||||||
|
|
||||||
"@peculiar/asn1-schema@^2.1.6", "@peculiar/asn1-schema@^2.3.0":
|
|
||||||
version "2.3.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.3.3.tgz#21418e1f3819e0b353ceff0c2dad8ccb61acd777"
|
|
||||||
integrity sha512-6GptMYDMyWBHTUKndHaDsRZUO/XMSgIns2krxcm2L7SEExRHwawFvSwNBhqNPR9HJwv3MruAiF1bhN0we6j6GQ==
|
|
||||||
dependencies:
|
|
||||||
asn1js "^3.0.5"
|
|
||||||
pvtsutils "^1.3.2"
|
|
||||||
tslib "^2.4.0"
|
|
||||||
|
|
||||||
"@peculiar/json-schema@^1.1.12":
|
|
||||||
version "1.1.12"
|
|
||||||
resolved "https://registry.yarnpkg.com/@peculiar/json-schema/-/json-schema-1.1.12.tgz#fe61e85259e3b5ba5ad566cb62ca75b3d3cd5339"
|
|
||||||
integrity sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==
|
|
||||||
dependencies:
|
|
||||||
tslib "^2.0.0"
|
|
||||||
|
|
||||||
"@peculiar/webcrypto@^1.4.0":
|
|
||||||
version "1.4.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.4.1.tgz#821493bd5ad0f05939bd5f53b28536f68158360a"
|
|
||||||
integrity sha512-eK4C6WTNYxoI7JOabMoZICiyqRRtJB220bh0Mbj5RwRycleZf9BPyZoxsTvpP0FpmVS2aS13NKOuh5/tN3sIRw==
|
|
||||||
dependencies:
|
|
||||||
"@peculiar/asn1-schema" "^2.3.0"
|
|
||||||
"@peculiar/json-schema" "^1.1.12"
|
|
||||||
pvtsutils "^1.3.2"
|
|
||||||
tslib "^2.4.1"
|
|
||||||
webcrypto-core "^1.7.4"
|
|
||||||
|
|
||||||
"@pothos/core@^3.24.0":
|
|
||||||
version "3.24.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@pothos/core/-/core-3.24.0.tgz#1a555c501c9fc09c588196f83e6b0075571a59c5"
|
|
||||||
integrity sha512-LfWzUrmjhg9WQNUntQMJWOfMLb51AMunqBOC66zWEIi2GR4IcAQCPwzy77713Zd/awtIInIuHv4x5/1whAWeeA==
|
|
||||||
|
|
||||||
"@repeaterjs/repeater@3.0.4", "@repeaterjs/repeater@^3.0.4":
|
|
||||||
version "3.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@repeaterjs/repeater/-/repeater-3.0.4.tgz#a04d63f4d1bf5540a41b01a921c9a7fddc3bd1ca"
|
|
||||||
integrity sha512-AW8PKd6iX3vAZ0vA43nOUOnbq/X5ihgU+mSXXqunMkeQADGiqw/PY0JNeYtD5sr0PAy51YPgAPbDoeapv9r8WA==
|
|
||||||
|
|
||||||
"@types/node@^18.11.18":
|
|
||||||
version "18.11.18"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f"
|
|
||||||
integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==
|
|
||||||
|
|
||||||
"@whatwg-node/events@0.0.2":
|
|
||||||
version "0.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/@whatwg-node/events/-/events-0.0.2.tgz#7b7107268d2982fc7b7aff5ee6803c64018f84dd"
|
|
||||||
integrity sha512-WKj/lI4QjnLuPrim0cfO7i+HsDSXHxNv1y0CrJhdntuO3hxWZmnXCwNDnwOvry11OjRin6cgWNF+j/9Pn8TN4w==
|
|
||||||
|
|
||||||
"@whatwg-node/fetch@0.6.2":
|
|
||||||
version "0.6.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/@whatwg-node/fetch/-/fetch-0.6.2.tgz#fe4837505f6fc91bcfd6e12cdcec66f4aecfeecc"
|
|
||||||
integrity sha512-fCUycF1W+bI6XzwJFnbdDuxIldfKM3w8+AzVCLGlucm0D+AQ8ZMm2j84hdcIhfV6ZdE4Y1HFVrHosAxdDZ+nPw==
|
|
||||||
dependencies:
|
|
||||||
"@peculiar/webcrypto" "^1.4.0"
|
|
||||||
abort-controller "^3.0.0"
|
|
||||||
busboy "^1.6.0"
|
|
||||||
form-data-encoder "^1.7.1"
|
|
||||||
formdata-node "^4.3.1"
|
|
||||||
node-fetch "^2.6.7"
|
|
||||||
undici "^5.12.0"
|
|
||||||
urlpattern-polyfill "^6.0.2"
|
|
||||||
web-streams-polyfill "^3.2.0"
|
|
||||||
|
|
||||||
"@whatwg-node/server@0.5.8":
|
|
||||||
version "0.5.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/@whatwg-node/server/-/server-0.5.8.tgz#e47aa4535431b6703652809dc4f7e5ebac6bffdb"
|
|
||||||
integrity sha512-29f2Ijk663Hr6hF5GU5a8ELGQVbNMMDBWF1lTdpIKGyLrLJTKixarp6COEyEN5H9tGzIRUQar9Z76A+Jb9DyzQ==
|
|
||||||
dependencies:
|
|
||||||
"@whatwg-node/fetch" "0.6.2"
|
|
||||||
tslib "^2.3.1"
|
|
||||||
|
|
||||||
abort-controller@^3.0.0:
|
|
||||||
version "3.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
|
|
||||||
integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
|
|
||||||
dependencies:
|
|
||||||
event-target-shim "^5.0.0"
|
|
||||||
|
|
||||||
asn1js@^3.0.1, asn1js@^3.0.5:
|
|
||||||
version "3.0.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.5.tgz#5ea36820443dbefb51cc7f88a2ebb5b462114f38"
|
|
||||||
integrity sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==
|
|
||||||
dependencies:
|
|
||||||
pvtsutils "^1.3.2"
|
|
||||||
pvutils "^1.1.3"
|
|
||||||
tslib "^2.4.0"
|
|
||||||
|
|
||||||
braces@^3.0.2:
|
|
||||||
version "3.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
|
||||||
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
|
|
||||||
dependencies:
|
|
||||||
fill-range "^7.0.1"
|
|
||||||
|
|
||||||
busboy@^1.6.0:
|
|
||||||
version "1.6.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
|
|
||||||
integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
|
|
||||||
dependencies:
|
|
||||||
streamsearch "^1.1.0"
|
|
||||||
|
|
||||||
dset@^3.1.1:
|
|
||||||
version "3.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.2.tgz#89c436ca6450398396dc6538ea00abc0c54cd45a"
|
|
||||||
integrity sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==
|
|
||||||
|
|
||||||
event-target-shim@^5.0.0:
|
|
||||||
version "5.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
|
|
||||||
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
|
|
||||||
|
|
||||||
fill-range@^7.0.1:
|
|
||||||
version "7.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
|
|
||||||
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
|
|
||||||
dependencies:
|
|
||||||
to-regex-range "^5.0.1"
|
|
||||||
|
|
||||||
form-data-encoder@^1.7.1:
|
|
||||||
version "1.7.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040"
|
|
||||||
integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==
|
|
||||||
|
|
||||||
formdata-node@^4.3.1:
|
|
||||||
version "4.4.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2"
|
|
||||||
integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==
|
|
||||||
dependencies:
|
|
||||||
node-domexception "1.0.0"
|
|
||||||
web-streams-polyfill "4.0.0-beta.3"
|
|
||||||
|
|
||||||
graphql-yoga@^3.3.0:
|
|
||||||
version "3.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/graphql-yoga/-/graphql-yoga-3.4.0.tgz#9ab03552edb1f9ec0d877566ee7f0ecb23726dd7"
|
|
||||||
integrity sha512-Cjx60mmpoK1qL/sLdM285VdAOQyJBKLuC6oMZrfO8QleneNtu0nDOM6Efv5m0IrRYSONEMtIYA7eNr0u/cCBfg==
|
|
||||||
dependencies:
|
|
||||||
"@envelop/core" "3.0.4"
|
|
||||||
"@envelop/parser-cache" "^5.0.4"
|
|
||||||
"@envelop/validation-cache" "^5.0.5"
|
|
||||||
"@graphql-tools/executor" "0.0.12"
|
|
||||||
"@graphql-tools/schema" "^9.0.0"
|
|
||||||
"@graphql-tools/utils" "^9.0.1"
|
|
||||||
"@graphql-yoga/subscription" "^3.1.0"
|
|
||||||
"@whatwg-node/fetch" "0.6.2"
|
|
||||||
"@whatwg-node/server" "0.5.8"
|
|
||||||
dset "^3.1.1"
|
|
||||||
tslib "^2.3.1"
|
|
||||||
|
|
||||||
graphql@^16.6.0:
|
|
||||||
version "16.6.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb"
|
|
||||||
integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==
|
|
||||||
|
|
||||||
is-number@^7.0.0:
|
|
||||||
version "7.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
|
||||||
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
|
||||||
|
|
||||||
lru-cache@^6.0.0:
|
|
||||||
version "6.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
|
||||||
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
|
|
||||||
dependencies:
|
|
||||||
yallist "^4.0.0"
|
|
||||||
|
|
||||||
node-domexception@1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
|
|
||||||
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
|
|
||||||
|
|
||||||
node-fetch@^2.6.7:
|
|
||||||
version "2.6.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.8.tgz#a68d30b162bc1d8fd71a367e81b997e1f4d4937e"
|
|
||||||
integrity sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==
|
|
||||||
dependencies:
|
|
||||||
whatwg-url "^5.0.0"
|
|
||||||
|
|
||||||
pvtsutils@^1.3.2:
|
|
||||||
version "1.3.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.3.2.tgz#9f8570d132cdd3c27ab7d51a2799239bf8d8d5de"
|
|
||||||
integrity sha512-+Ipe2iNUyrZz+8K/2IOo+kKikdtfhRKzNpQbruF2URmqPtoqAs8g3xS7TJvFF2GcPXjh7DkqMnpVveRFq4PgEQ==
|
|
||||||
dependencies:
|
|
||||||
tslib "^2.4.0"
|
|
||||||
|
|
||||||
pvutils@^1.1.3:
|
|
||||||
version "1.1.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.1.3.tgz#f35fc1d27e7cd3dfbd39c0826d173e806a03f5a3"
|
|
||||||
integrity sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==
|
|
||||||
|
|
||||||
streamsearch@^1.1.0:
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
|
|
||||||
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
|
|
||||||
|
|
||||||
to-regex-range@^5.0.1:
|
|
||||||
version "5.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
|
|
||||||
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
|
|
||||||
dependencies:
|
|
||||||
is-number "^7.0.0"
|
|
||||||
|
|
||||||
tr46@~0.0.3:
|
|
||||||
version "0.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
|
||||||
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
|
|
||||||
|
|
||||||
tslib@2.4.0:
|
|
||||||
version "2.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
|
|
||||||
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
|
|
||||||
|
|
||||||
tslib@^2.0.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.4.1:
|
|
||||||
version "2.5.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
|
|
||||||
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
|
|
||||||
|
|
||||||
undici@^5.12.0:
|
|
||||||
version "5.16.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/undici/-/undici-5.16.0.tgz#6b64f9b890de85489ac6332bd45ca67e4f7d9943"
|
|
||||||
integrity sha512-KWBOXNv6VX+oJQhchXieUznEmnJMqgXMbs0xxH2t8q/FUAWSJvOSr/rMaZKnX5RIVq7JDn0JbP4BOnKG2SGXLQ==
|
|
||||||
dependencies:
|
|
||||||
busboy "^1.6.0"
|
|
||||||
|
|
||||||
urlpattern-polyfill@^6.0.2:
|
|
||||||
version "6.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/urlpattern-polyfill/-/urlpattern-polyfill-6.0.2.tgz#a193fe773459865a2a5c93b246bb794b13d07256"
|
|
||||||
integrity sha512-5vZjFlH9ofROmuWmXM9yj2wljYKgWstGwe8YTyiqM7hVum/g9LyCizPZtb3UqsuppVwety9QJmfc42VggLpTgg==
|
|
||||||
dependencies:
|
|
||||||
braces "^3.0.2"
|
|
||||||
|
|
||||||
value-or-promise@1.0.12:
|
|
||||||
version "1.0.12"
|
|
||||||
resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.12.tgz#0e5abfeec70148c78460a849f6b003ea7986f15c"
|
|
||||||
integrity sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==
|
|
||||||
|
|
||||||
web-streams-polyfill@4.0.0-beta.3:
|
|
||||||
version "4.0.0-beta.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38"
|
|
||||||
integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==
|
|
||||||
|
|
||||||
web-streams-polyfill@^3.2.0:
|
|
||||||
version "3.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
|
|
||||||
integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
|
|
||||||
|
|
||||||
webcrypto-core@^1.7.4:
|
|
||||||
version "1.7.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.7.5.tgz#c02104c953ca7107557f9c165d194c6316587ca4"
|
|
||||||
integrity sha512-gaExY2/3EHQlRNNNVSrbG2Cg94Rutl7fAaKILS1w8ZDhGxdFOaw6EbCfHIxPy9vt/xwp5o0VQAx9aySPF6hU1A==
|
|
||||||
dependencies:
|
|
||||||
"@peculiar/asn1-schema" "^2.1.6"
|
|
||||||
"@peculiar/json-schema" "^1.1.12"
|
|
||||||
asn1js "^3.0.1"
|
|
||||||
pvtsutils "^1.3.2"
|
|
||||||
tslib "^2.4.0"
|
|
||||||
|
|
||||||
webidl-conversions@^3.0.0:
|
|
||||||
version "3.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
|
||||||
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
|
|
||||||
|
|
||||||
whatwg-url@^5.0.0:
|
|
||||||
version "5.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
|
|
||||||
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
|
|
||||||
dependencies:
|
|
||||||
tr46 "~0.0.3"
|
|
||||||
webidl-conversions "^3.0.0"
|
|
||||||
|
|
||||||
yallist@^4.0.0:
|
|
||||||
version "4.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
|
||||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
metadata_directory: metadata
|
|
||||||
services:
|
|
||||||
hasura:
|
|
||||||
image: hasura/graphql-engine:v2.15.2
|
|
||||||
environment:
|
|
||||||
hasura_graphql_enable_remote_schema_permissions: false
|
|
||||||
auth:
|
|
||||||
image: nhost/hasura-auth:0.16.2
|
|
||||||
storage:
|
|
||||||
image: nhost/hasura-storage:0.3.0
|
|
||||||
auth:
|
|
||||||
access_control:
|
|
||||||
email:
|
|
||||||
allowed_email_domains: ''
|
|
||||||
allowed_emails: ''
|
|
||||||
blocked_email_domains: ''
|
|
||||||
blocked_emails: ''
|
|
||||||
allowed_redirect_urls: ''
|
|
||||||
anonymous_users_enabled: true
|
|
||||||
client_url: http://localhost:3000
|
|
||||||
disable_new_users: false
|
|
||||||
email:
|
|
||||||
signin_email_verified_required: false
|
|
||||||
passwordless:
|
|
||||||
enabled: true
|
|
||||||
template_fetch_url: ''
|
|
||||||
gravatar:
|
|
||||||
default: ''
|
|
||||||
enabled: true
|
|
||||||
rating: ''
|
|
||||||
locale:
|
|
||||||
allowed: en
|
|
||||||
default: en
|
|
||||||
password:
|
|
||||||
hibp_enabled: false
|
|
||||||
min_length: 3
|
|
||||||
provider:
|
|
||||||
apple:
|
|
||||||
client_id: ''
|
|
||||||
enabled: false
|
|
||||||
key_id: ''
|
|
||||||
private_key: ''
|
|
||||||
scope: name,email
|
|
||||||
team_id: ''
|
|
||||||
bitbucket:
|
|
||||||
client_id: ''
|
|
||||||
client_secret: ''
|
|
||||||
enabled: false
|
|
||||||
facebook:
|
|
||||||
client_id: ''
|
|
||||||
client_secret: ''
|
|
||||||
enabled: false
|
|
||||||
scope: email,photos,displayName
|
|
||||||
github:
|
|
||||||
enabled: false
|
|
||||||
client_id: ''
|
|
||||||
client_secret: ''
|
|
||||||
scope: user:email
|
|
||||||
token_url: ''
|
|
||||||
user_profile_url: ''
|
|
||||||
gitlab:
|
|
||||||
base_url: ''
|
|
||||||
client_id: ''
|
|
||||||
client_secret: ''
|
|
||||||
enabled: false
|
|
||||||
scope: read_user
|
|
||||||
google:
|
|
||||||
client_id: ''
|
|
||||||
client_secret: ''
|
|
||||||
enabled: false
|
|
||||||
scope: email,profile
|
|
||||||
linkedin:
|
|
||||||
client_id: ''
|
|
||||||
client_secret: ''
|
|
||||||
enabled: false
|
|
||||||
scope: r_emailaddress,r_liteprofile
|
|
||||||
spotify:
|
|
||||||
client_id: ''
|
|
||||||
client_secret: ''
|
|
||||||
enabled: false
|
|
||||||
scope: user-read-email,user-read-private
|
|
||||||
strava:
|
|
||||||
client_id: ''
|
|
||||||
client_secret: ''
|
|
||||||
enabled: false
|
|
||||||
twilio:
|
|
||||||
account_sid: ''
|
|
||||||
auth_token: ''
|
|
||||||
enabled: false
|
|
||||||
messaging_service_id: ''
|
|
||||||
twitter:
|
|
||||||
consumer_key: ''
|
|
||||||
consumer_secret: ''
|
|
||||||
enabled: false
|
|
||||||
windows_live:
|
|
||||||
client_id: ''
|
|
||||||
client_secret: ''
|
|
||||||
enabled: false
|
|
||||||
scope: wl.basic,wl.emails,wl.contacts_emails
|
|
||||||
sms:
|
|
||||||
enabled: false
|
|
||||||
passwordless:
|
|
||||||
enabled: false
|
|
||||||
provider:
|
|
||||||
twilio:
|
|
||||||
account_sid: ''
|
|
||||||
auth_token: ''
|
|
||||||
from: ''
|
|
||||||
messaging_service_id: ''
|
|
||||||
smtp:
|
|
||||||
host: mailhog
|
|
||||||
method: ''
|
|
||||||
pass: password
|
|
||||||
port: 1025
|
|
||||||
secure: false
|
|
||||||
sender: hasura-auth@example.com
|
|
||||||
user: user
|
|
||||||
token:
|
|
||||||
access:
|
|
||||||
expires_in: 900
|
|
||||||
refresh:
|
|
||||||
expires_in: 43200
|
|
||||||
user:
|
|
||||||
allowed_roles: user,me
|
|
||||||
default_allowed_roles: user,me
|
|
||||||
default_role: user
|
|
||||||
mfa:
|
|
||||||
enabled: true
|
|
||||||
issuer: nhost
|
|
||||||
storage:
|
|
||||||
force_download_for_content_types: text/html,application/javascript
|
|
||||||
version: 3
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h2>Confirm Email Change</h2>
|
|
||||||
<p>Use this link to confirm changing email:</p>
|
|
||||||
<p>
|
|
||||||
<a href="${link}"> Change email </a>
|
|
||||||
</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Change your email address
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h2>Verify Email</h2>
|
|
||||||
<p>Use this link to verify your email:</p>
|
|
||||||
<p>
|
|
||||||
<a href="${link}"> Verify Email </a>
|
|
||||||
</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Verify your email
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user