Compare commits
55 Commits
@nhost/nho
...
@nhost/das
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe658231b4 | ||
|
|
a1188b7d98 | ||
|
|
cd4bdc581d | ||
|
|
4e2f8ccd52 | ||
|
|
8a6d8c7534 | ||
|
|
fa75409f09 | ||
|
|
74662052ae | ||
|
|
37ab5fe878 | ||
|
|
be9af96fa7 | ||
|
|
31abbe5f30 | ||
|
|
268b461d5b | ||
|
|
58af592cfa | ||
|
|
0e9d623c69 | ||
|
|
412a290646 | ||
|
|
123add38a4 | ||
|
|
5bdd31ad36 | ||
|
|
5121851c8b | ||
|
|
8ca1f92491 | ||
|
|
5535b9085b | ||
|
|
bc51122b25 | ||
|
|
b060e5e550 | ||
|
|
6a906b22e2 | ||
|
|
860c9d1be4 | ||
|
|
9eec3e58f5 | ||
|
|
4e01a43e94 | ||
|
|
c126b20dcf | ||
|
|
b727a24a5f | ||
|
|
ecadd7e1b9 | ||
|
|
2d661174a8 | ||
|
|
fcb3e5192f | ||
|
|
66fdc63f38 | ||
|
|
fa37cb6171 | ||
|
|
c1bea1294d | ||
|
|
8af2f6e9dd | ||
|
|
e3d0b96917 | ||
|
|
36c3519cf8 | ||
|
|
9b52e9bf13 | ||
|
|
07c8d90053 | ||
|
|
a2621e40a4 | ||
|
|
61120a137a | ||
|
|
faea8feb2e | ||
|
|
552e31a4f0 | ||
|
|
c4561cae38 | ||
|
|
599387934c | ||
|
|
04cea41111 | ||
|
|
dc3723306d | ||
|
|
d7fa572ab6 | ||
|
|
c21118257f | ||
|
|
4712b7ff68 | ||
|
|
4f305a8985 | ||
|
|
cd7d133ba3 | ||
|
|
2927a9ac31 | ||
|
|
695eaa77ca | ||
|
|
a29d21e194 | ||
|
|
cd20bd4ef2 |
@@ -1,5 +1,36 @@
|
|||||||
# @nhost/dashboard
|
# @nhost/dashboard
|
||||||
|
|
||||||
|
## 0.9.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 4e2f8ccd: fix(dashboard): don't break Auth page in local mode
|
||||||
|
|
||||||
|
## 0.9.9
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 31abbe5f: fix(dashboard): enable toggle when settings are filled in
|
||||||
|
|
||||||
|
## 0.9.8
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 5bdd31ad: chore(dashboard): list fewer images per page on the Storage page
|
||||||
|
- 5121851c: fix(dashboard): don't throw validation error for valid permission rules
|
||||||
|
|
||||||
|
## 0.9.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- c126b20d: fix(dashboard): correct redeployment button
|
||||||
|
|
||||||
|
## 0.9.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 36c3519c: feat(dashboard): retrigger deployments
|
||||||
|
|
||||||
## 0.9.5
|
## 0.9.5
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Nhost Dashboard
|
# Nhost Dashboard
|
||||||
|
|
||||||
This is the Nhost Dashboard, a web application that allows you to manage your Nhost project.
|
This is the Nhost Dashboard, a web application that allows you to manage your Nhost projects.
|
||||||
To get started, you need to have an Nhost project. If you don't have one, you can [create a project here](https://app.nhost.io).
|
To get started, you need to have an Nhost project. If you don't have one, you can [create a project here](https://app.nhost.io).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/dashboard",
|
"name": "@nhost/dashboard",
|
||||||
"version": "0.9.5",
|
"version": "0.9.10",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
|
|||||||
@@ -1,21 +1,14 @@
|
|||||||
import type { DeploymentRowFragment } from '@/generated/graphql';
|
import DeploymentListItem from '@/components/deployments/DeploymentListItem';
|
||||||
import { useGetDeploymentsSubSubscription } from '@/generated/graphql';
|
|
||||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
|
||||||
import { Avatar } from '@/ui/Avatar';
|
|
||||||
import DelayedLoading from '@/ui/DelayedLoading';
|
|
||||||
import Status, { StatusEnum } from '@/ui/Status';
|
|
||||||
import type { DeploymentStatus } from '@/ui/StatusCircle';
|
|
||||||
import { StatusCircle } from '@/ui/StatusCircle';
|
|
||||||
import { getLastLiveDeployment } from '@/utils/helpers';
|
|
||||||
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid';
|
|
||||||
import {
|
import {
|
||||||
differenceInSeconds,
|
useGetDeploymentsSubSubscription,
|
||||||
formatDistanceToNowStrict,
|
useLatestLiveDeploymentSubSubscription,
|
||||||
parseISO,
|
useScheduledOrPendingDeploymentsSubSubscription,
|
||||||
} from 'date-fns';
|
} from '@/generated/graphql';
|
||||||
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
|
import List from '@/ui/v2/List';
|
||||||
|
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
type AppDeploymentsProps = {
|
type AppDeploymentsProps = {
|
||||||
appId: string;
|
appId: string;
|
||||||
@@ -66,146 +59,8 @@ function NextPrevPageLink(props: NextPrevPageLinkProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppDeploymentDurationProps = {
|
|
||||||
startedAt: string;
|
|
||||||
endedAt: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function AppDeploymentDuration({
|
|
||||||
startedAt,
|
|
||||||
endedAt,
|
|
||||||
}: AppDeploymentDurationProps) {
|
|
||||||
const [currentTime, setCurrentTime] = useState(new Date());
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let interval: NodeJS.Timeout;
|
|
||||||
|
|
||||||
if (!endedAt) {
|
|
||||||
interval = setInterval(() => {
|
|
||||||
setCurrentTime(new Date());
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
clearInterval(interval);
|
|
||||||
};
|
|
||||||
}, [endedAt]);
|
|
||||||
|
|
||||||
const totalDurationInSeconds = differenceInSeconds(
|
|
||||||
endedAt ? parseISO(endedAt) : currentTime,
|
|
||||||
parseISO(startedAt),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (totalDurationInSeconds > 1200) {
|
|
||||||
return <div>20+m</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const durationMins = Math.floor(totalDurationInSeconds / 60);
|
|
||||||
const durationSecs = totalDurationInSeconds % 60;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontVariantNumeric: 'tabular-nums',
|
|
||||||
}}
|
|
||||||
className="self-center font-display text-sm+ text-greyscaleDark"
|
|
||||||
>
|
|
||||||
{durationMins}m {durationSecs}s
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type AppDeploymentRowProps = {
|
|
||||||
deployment: DeploymentRowFragment;
|
|
||||||
isDeploymentLive: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function AppDeploymentRow({
|
|
||||||
deployment,
|
|
||||||
isDeploymentLive,
|
|
||||||
}: AppDeploymentRowProps) {
|
|
||||||
const { currentWorkspace, currentApplication } =
|
|
||||||
useCurrentWorkspaceAndApplication();
|
|
||||||
|
|
||||||
const { commitMessage } = deployment;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-row items-center px-2 py-4">
|
|
||||||
<div className="mr-2 flex items-center justify-center">
|
|
||||||
<Avatar
|
|
||||||
name={deployment.commitUserName}
|
|
||||||
avatarUrl={deployment.commitUserAvatarUrl}
|
|
||||||
className="h-8 w-8"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mx-4 w-full">
|
|
||||||
<Link
|
|
||||||
href={`/${currentWorkspace.slug}/${currentApplication.slug}/deployments/${deployment.id}`}
|
|
||||||
passHref
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href={`/${currentWorkspace.slug}/${currentApplication.slug}/deployments/${deployment.id}`}
|
|
||||||
>
|
|
||||||
<div className="max-w-md truncate text-sm+ font-normal text-greyscaleDark">
|
|
||||||
{commitMessage?.trim() || (
|
|
||||||
<span className="pr-1 font-normal italic">
|
|
||||||
No commit message
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="text-sm+ text-greyscaleGrey">
|
|
||||||
{formatDistanceToNowStrict(
|
|
||||||
parseISO(deployment.deploymentStartedAt),
|
|
||||||
{
|
|
||||||
addSuffix: true,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-row">
|
|
||||||
{isDeploymentLive && (
|
|
||||||
<div className="flex self-center align-middle">
|
|
||||||
<Status status={StatusEnum.Live}>Live</Status>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="w-28 self-center text-right font-mono text-sm- font-medium">
|
|
||||||
<a
|
|
||||||
className="font-mono font-medium text-greyscaleDark"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
href={`https://github.com/${currentApplication.githubRepository?.fullName}/commit/${deployment.commitSHA}`}
|
|
||||||
>
|
|
||||||
{deployment.commitSHA.substring(0, 7)}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="mx-4 w-28 text-right">
|
|
||||||
<AppDeploymentDuration
|
|
||||||
startedAt={deployment.deploymentStartedAt}
|
|
||||||
endedAt={deployment.deploymentEndedAt}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mx-3 self-center">
|
|
||||||
<StatusCircle
|
|
||||||
status={deployment.deploymentStatus as DeploymentStatus}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="self-center">
|
|
||||||
<Link
|
|
||||||
href={`/${currentWorkspace.slug}/${currentApplication.slug}/deployments/${deployment.id}`}
|
|
||||||
passHref
|
|
||||||
>
|
|
||||||
<ChevronRightIcon className="ml-2 h-4 w-4 cursor-pointer self-center" />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AppDeployments(props: AppDeploymentsProps) {
|
export default function AppDeployments(props: AppDeploymentsProps) {
|
||||||
const { appId } = props;
|
const { appId } = props;
|
||||||
const [idOfLiveDeployment, setIdOfLiveDeployment] = useState('');
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -216,36 +71,59 @@ export default function AppDeployments(props: AppDeploymentsProps) {
|
|||||||
const limit = 10;
|
const limit = 10;
|
||||||
const offset = (page - 1) * limit;
|
const offset = (page - 1) * limit;
|
||||||
|
|
||||||
// @TODO: Should query for all deployments, then subscribe to new ones.
|
const {
|
||||||
|
data: deploymentPageData,
|
||||||
const { data, loading, error } = useGetDeploymentsSubSubscription({
|
loading: deploymentPageLoading,
|
||||||
variables: {
|
error,
|
||||||
id: appId,
|
} = useGetDeploymentsSubSubscription({
|
||||||
limit,
|
variables: { id: appId, limit, offset },
|
||||||
offset,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
const { data: latestDeploymentData, loading: latestDeploymentLoading } =
|
||||||
if (!data) {
|
useGetDeploymentsSubSubscription({
|
||||||
return;
|
variables: { id: appId, limit: 1, offset: 0 },
|
||||||
}
|
});
|
||||||
|
|
||||||
if (page === 1) {
|
const {
|
||||||
setIdOfLiveDeployment(getLastLiveDeployment(data.deployments));
|
data: latestLiveDeploymentData,
|
||||||
}
|
loading: latestLiveDeploymentLoading,
|
||||||
}, [data, idOfLiveDeployment, loading, page]);
|
} = useLatestLiveDeploymentSubSubscription({ variables: { appId } });
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: scheduledOrPendingDeploymentsData,
|
||||||
|
loading: scheduledOrPendingDeploymentsLoading,
|
||||||
|
} = useScheduledOrPendingDeploymentsSubSubscription({ variables: { appId } });
|
||||||
|
|
||||||
|
const loading =
|
||||||
|
deploymentPageLoading ||
|
||||||
|
scheduledOrPendingDeploymentsLoading ||
|
||||||
|
latestDeploymentLoading ||
|
||||||
|
latestLiveDeploymentLoading;
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <DelayedLoading delay={500} className="mt-12" />;
|
return (
|
||||||
|
<ActivityIndicator
|
||||||
|
delay={500}
|
||||||
|
className="mt-12"
|
||||||
|
label="Loading deployments..."
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nrOfDeployments = data.deployments.length;
|
const { deployments } = deploymentPageData || {};
|
||||||
|
const { deployments: scheduledOrPendingDeployments } =
|
||||||
|
scheduledOrPendingDeploymentsData || {};
|
||||||
|
|
||||||
|
const latestDeployment = latestDeploymentData?.deployments[0];
|
||||||
|
const latestLiveDeployment = latestLiveDeploymentData?.deployments[0];
|
||||||
|
|
||||||
|
const nrOfDeployments = deployments?.length || 0;
|
||||||
const nextAllowed = !(nrOfDeployments < limit);
|
const nextAllowed = !(nrOfDeployments < limit);
|
||||||
|
const liveDeploymentId = latestLiveDeployment?.id || '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
@@ -253,15 +131,17 @@ export default function AppDeployments(props: AppDeploymentsProps) {
|
|||||||
<p className="text-sm text-greyscaleGrey">No deployments yet.</p>
|
<p className="text-sm text-greyscaleGrey">No deployments yet.</p>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<div className="mt-3 divide-y-1 border-t border-b">
|
<List className="mt-3 divide-y-1 border-t border-b">
|
||||||
{data.deployments.map((deployment) => (
|
{deployments.map((deployment) => (
|
||||||
<AppDeploymentRow
|
<DeploymentListItem
|
||||||
deployment={deployment}
|
|
||||||
key={deployment.id}
|
key={deployment.id}
|
||||||
isDeploymentLive={idOfLiveDeployment === deployment.id}
|
deployment={deployment}
|
||||||
|
isLive={liveDeploymentId === deployment.id}
|
||||||
|
showRedeploy={latestDeployment.id === deployment.id}
|
||||||
|
disableRedeploy={scheduledOrPendingDeployments.length > 0}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</List>
|
||||||
<div className="mt-8 flex w-full justify-center">
|
<div className="mt-8 flex w-full justify-center">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<NextPrevPageLink
|
<NextPrevPageLink
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ export function Repo({ repo, setSelectedRepoId }: RepoProps) {
|
|||||||
const [updateApp, { loading, error }] = useUpdateAppMutation({
|
const [updateApp, { loading, error }] = useUpdateAppMutation({
|
||||||
refetchQueries: [
|
refetchQueries: [
|
||||||
refetchGetAppByWorkspaceAndNameQuery({
|
refetchGetAppByWorkspaceAndNameQuery({
|
||||||
workspace: currentWorkspace.slug,
|
workspace: currentWorkspace?.slug,
|
||||||
slug: currentApplication.slug,
|
slug: currentApplication?.slug,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const { githubRepository } = currentApplication;
|
const { githubRepository } = currentApplication || {};
|
||||||
|
|
||||||
const isThisRepositoryAlreadyConnected =
|
const isThisRepositoryAlreadyConnected =
|
||||||
githubRepository?.fullName && githubRepository.fullName === repo.fullName;
|
githubRepository?.fullName && githubRepository.fullName === repo.fullName;
|
||||||
|
|||||||
@@ -4,7 +4,17 @@ import * as Yup from 'yup';
|
|||||||
const ruleSchema = Yup.object().shape({
|
const ruleSchema = Yup.object().shape({
|
||||||
column: Yup.string().nullable().required('Please select a column.'),
|
column: Yup.string().nullable().required('Please select a column.'),
|
||||||
operator: Yup.string().nullable().required('Please select an operator.'),
|
operator: Yup.string().nullable().required('Please select an operator.'),
|
||||||
value: Yup.string().nullable().required('Please enter a value.'),
|
value: Yup.mixed()
|
||||||
|
.test(
|
||||||
|
'isArray',
|
||||||
|
'Please enter a valid value.',
|
||||||
|
(value) =>
|
||||||
|
typeof value === 'string' ||
|
||||||
|
(Array.isArray(value) &&
|
||||||
|
value.every((item) => typeof item === 'string')),
|
||||||
|
)
|
||||||
|
.nullable()
|
||||||
|
.required('Please enter a value.'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const ruleGroupSchema = Yup.object().shape({
|
const ruleGroupSchema = Yup.object().shape({
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { differenceInSeconds, parseISO } from 'date-fns';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export interface AppDeploymentDurationProps {
|
||||||
|
/**
|
||||||
|
* Start date of the deployment.
|
||||||
|
*/
|
||||||
|
startedAt: string;
|
||||||
|
/**
|
||||||
|
* End date of the deployment.
|
||||||
|
*/
|
||||||
|
endedAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AppDeploymentDuration({
|
||||||
|
startedAt,
|
||||||
|
endedAt,
|
||||||
|
}: AppDeploymentDurationProps) {
|
||||||
|
const [currentTime, setCurrentTime] = useState(new Date());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let interval: NodeJS.Timeout;
|
||||||
|
|
||||||
|
if (!endedAt) {
|
||||||
|
interval = setInterval(() => {
|
||||||
|
setCurrentTime(new Date());
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, [endedAt]);
|
||||||
|
|
||||||
|
const totalDurationInSeconds = differenceInSeconds(
|
||||||
|
endedAt ? parseISO(endedAt) : currentTime,
|
||||||
|
parseISO(startedAt),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (totalDurationInSeconds > 1200) {
|
||||||
|
return <div>20+m</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const durationMins = Math.floor(totalDurationInSeconds / 60);
|
||||||
|
const durationSecs = totalDurationInSeconds % 60;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{ fontVariantNumeric: 'tabular-nums' }}
|
||||||
|
className="self-center font-display text-sm+ text-greyscaleDark"
|
||||||
|
>
|
||||||
|
{Number.isNaN(durationMins) || Number.isNaN(durationSecs) ? (
|
||||||
|
<span>0m 0s</span>
|
||||||
|
) : (
|
||||||
|
<span>
|
||||||
|
{durationMins}m {durationSecs}s
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './AppDeploymentDuration';
|
||||||
|
export { default } from './AppDeploymentDuration';
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
import NavLink from '@/components/common/NavLink';
|
||||||
|
import AppDeploymentDuration from '@/components/deployments/AppDeploymentDuration';
|
||||||
|
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||||
|
import { Avatar } from '@/ui/Avatar';
|
||||||
|
import Status, { StatusEnum } from '@/ui/Status';
|
||||||
|
import type { DeploymentStatus } from '@/ui/StatusCircle';
|
||||||
|
import { StatusCircle } from '@/ui/StatusCircle';
|
||||||
|
import Button from '@/ui/v2/Button';
|
||||||
|
import ArrowCounterclockwiseIcon from '@/ui/v2/icons/ArrowCounterclockwiseIcon';
|
||||||
|
import ChevronRightIcon from '@/ui/v2/icons/ChevronRightIcon';
|
||||||
|
import { ListItem } from '@/ui/v2/ListItem';
|
||||||
|
import Tooltip from '@/ui/v2/Tooltip';
|
||||||
|
import { toastStyleProps } from '@/utils/settings/settingsConstants';
|
||||||
|
import type { DeploymentRowFragment } from '@/utils/__generated__/graphql';
|
||||||
|
import { useInsertDeploymentMutation } from '@/utils/__generated__/graphql';
|
||||||
|
import { formatDistanceToNowStrict, parseISO } from 'date-fns';
|
||||||
|
import { toast } from 'react-hot-toast';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
export interface DeploymentListItemProps {
|
||||||
|
/**
|
||||||
|
* Deployment data.
|
||||||
|
*/
|
||||||
|
deployment: DeploymentRowFragment;
|
||||||
|
/**
|
||||||
|
* Determines whether or not the deployment is live.
|
||||||
|
*/
|
||||||
|
isLive?: boolean;
|
||||||
|
/**
|
||||||
|
* Determines whether or not the redeploy button should be shown for the
|
||||||
|
* deployment.
|
||||||
|
*/
|
||||||
|
showRedeploy?: boolean;
|
||||||
|
/**
|
||||||
|
* Determines whether or not the redeploy button is disabled.
|
||||||
|
*/
|
||||||
|
disableRedeploy?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DeploymentListItem({
|
||||||
|
deployment,
|
||||||
|
isLive,
|
||||||
|
showRedeploy,
|
||||||
|
disableRedeploy,
|
||||||
|
}: DeploymentListItemProps) {
|
||||||
|
const { currentWorkspace, currentApplication } =
|
||||||
|
useCurrentWorkspaceAndApplication();
|
||||||
|
|
||||||
|
const relativeDateOfDeployment = deployment.deploymentStartedAt
|
||||||
|
? formatDistanceToNowStrict(parseISO(deployment.deploymentStartedAt), {
|
||||||
|
addSuffix: true,
|
||||||
|
})
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const [insertDeployment, { loading }] = useInsertDeploymentMutation();
|
||||||
|
const { commitMessage } = deployment;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItem.Root>
|
||||||
|
<ListItem.Button
|
||||||
|
className="grid grid-flow-col items-center justify-between gap-2 px-2 py-2"
|
||||||
|
component={NavLink}
|
||||||
|
href={`/${currentWorkspace.slug}/${currentApplication.slug}/deployments/${deployment.id}`}
|
||||||
|
>
|
||||||
|
<div className="flex cursor-pointer flex-row items-center justify-center space-x-2 self-center">
|
||||||
|
<ListItem.Avatar>
|
||||||
|
<Avatar
|
||||||
|
name={deployment.commitUserName}
|
||||||
|
avatarUrl={deployment.commitUserAvatarUrl}
|
||||||
|
className="h-8 w-8 shrink-0"
|
||||||
|
/>
|
||||||
|
</ListItem.Avatar>
|
||||||
|
|
||||||
|
<ListItem.Text
|
||||||
|
primary={
|
||||||
|
commitMessage?.trim() || (
|
||||||
|
<span className="truncate pr-1 font-normal italic">
|
||||||
|
No commit message
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
secondary={relativeDateOfDeployment}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-flow-col gap-2 items-center">
|
||||||
|
{showRedeploy && (
|
||||||
|
<Tooltip
|
||||||
|
title="Deployments cannot be re-triggered when a deployment is in progress."
|
||||||
|
hasDisabledChildren={disableRedeploy || loading}
|
||||||
|
disableHoverListener={!disableRedeploy}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
disabled={disableRedeploy || loading}
|
||||||
|
size="small"
|
||||||
|
color="secondary"
|
||||||
|
variant="outlined"
|
||||||
|
onClick={async (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const insertDeploymentPromise = insertDeployment({
|
||||||
|
variables: {
|
||||||
|
object: {
|
||||||
|
appId: currentApplication?.id,
|
||||||
|
commitMessage: deployment.commitMessage,
|
||||||
|
commitSHA: deployment.commitSHA,
|
||||||
|
commitUserAvatarUrl: deployment.commitUserAvatarUrl,
|
||||||
|
commitUserName: deployment.commitUserName,
|
||||||
|
deploymentStatus: 'SCHEDULED',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await toast.promise(
|
||||||
|
insertDeploymentPromise,
|
||||||
|
{
|
||||||
|
loading: 'Scheduling deployment...',
|
||||||
|
success: 'Deployment has been scheduled successfully.',
|
||||||
|
error: 'An error occurred when scheduling deployment.',
|
||||||
|
},
|
||||||
|
toastStyleProps,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
startIcon={
|
||||||
|
<ArrowCounterclockwiseIcon className={twMerge('w-4 h-4')} />
|
||||||
|
}
|
||||||
|
className="rounded-full py-1 px-2 text-xs"
|
||||||
|
>
|
||||||
|
Redeploy
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isLive && (
|
||||||
|
<div className="w-12 flex justify-end">
|
||||||
|
<Status status={StatusEnum.Live}>Live</Status>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="w-16 text-right font-mono text-sm- font-medium">
|
||||||
|
{deployment.commitSHA.substring(0, 7)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-[80px] text-right font-mono text-sm- font-medium">
|
||||||
|
<AppDeploymentDuration
|
||||||
|
startedAt={deployment.deploymentStartedAt}
|
||||||
|
endedAt={deployment.deploymentEndedAt}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<StatusCircle
|
||||||
|
status={deployment.deploymentStatus as DeploymentStatus}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ChevronRightIcon className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
</ListItem.Button>
|
||||||
|
</ListItem.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './DeploymentListItem';
|
||||||
|
export { default } from './DeploymentListItem';
|
||||||
@@ -37,7 +37,7 @@ export default function FilesDataGrid(props: FilesDataGridProps) {
|
|||||||
parseInt(router.query.page as string, 10) - 1 || 0,
|
parseInt(router.query.page as string, 10) - 1 || 0,
|
||||||
);
|
);
|
||||||
const [sortBy, setSortBy] = useState<SortingRule<StoredFile>[]>();
|
const [sortBy, setSortBy] = useState<SortingRule<StoredFile>[]>();
|
||||||
const limit = 25;
|
const limit = 10;
|
||||||
const emptyStateMessage = searchString
|
const emptyStateMessage = searchString
|
||||||
? 'No search results found.'
|
? 'No search results found.'
|
||||||
: 'No files are uploaded yet.';
|
: 'No files are uploaded yet.';
|
||||||
|
|||||||
@@ -1,25 +1,21 @@
|
|||||||
import { AppDeploymentDuration } from '@/components/applications/AppDeployments';
|
|
||||||
import { EditRepositorySettings } from '@/components/applications/github/EditRepositorySettings';
|
import { EditRepositorySettings } from '@/components/applications/github/EditRepositorySettings';
|
||||||
import useGitHubModal from '@/components/applications/github/useGitHubModal';
|
import useGitHubModal from '@/components/applications/github/useGitHubModal';
|
||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import NavLink from '@/components/common/NavLink';
|
import NavLink from '@/components/common/NavLink';
|
||||||
|
import DeploymentListItem from '@/components/deployments/DeploymentListItem';
|
||||||
import GithubIcon from '@/components/icons/GithubIcon';
|
import GithubIcon from '@/components/icons/GithubIcon';
|
||||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||||
import { Avatar } from '@/ui/Avatar';
|
|
||||||
import Status, { StatusEnum } from '@/ui/Status';
|
|
||||||
import type { DeploymentStatus } from '@/ui/StatusCircle';
|
|
||||||
import { StatusCircle } from '@/ui/StatusCircle';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import RocketIcon from '@/ui/v2/icons/RocketIcon';
|
import RocketIcon from '@/ui/v2/icons/RocketIcon';
|
||||||
import type { ListItemRootProps } from '@/ui/v2/ListItem';
|
import List from '@/ui/v2/List';
|
||||||
import { ListItem } from '@/ui/v2/ListItem';
|
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
import { getLastLiveDeployment } from '@/utils/helpers';
|
import { getLastLiveDeployment } from '@/utils/helpers';
|
||||||
import type { DeploymentRowFragment } from '@/utils/__generated__/graphql';
|
import {
|
||||||
import { useGetDeploymentsSubSubscription } from '@/utils/__generated__/graphql';
|
useGetDeploymentsSubSubscription,
|
||||||
|
useScheduledOrPendingDeploymentsSubSubscription,
|
||||||
|
} from '@/utils/__generated__/graphql';
|
||||||
import { ChevronRightIcon } from '@heroicons/react/solid';
|
import { ChevronRightIcon } from '@heroicons/react/solid';
|
||||||
import { formatDistanceToNowStrict, parseISO } from 'date-fns';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
function OverviewDeploymentsTopBar() {
|
function OverviewDeploymentsTopBar() {
|
||||||
@@ -54,92 +50,6 @@ function OverviewDeploymentsTopBar() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OverviewDeployProps extends ListItemRootProps {
|
|
||||||
/**
|
|
||||||
* Deployment metadata to display.
|
|
||||||
*/
|
|
||||||
deployment: DeploymentRowFragment;
|
|
||||||
/**
|
|
||||||
* Determines to show a status badge showing the live status of a deployment reflecting the latest state of the application.
|
|
||||||
*/
|
|
||||||
isDeploymentLive: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
function OverviewDeployment({
|
|
||||||
deployment,
|
|
||||||
isDeploymentLive,
|
|
||||||
className,
|
|
||||||
}: OverviewDeployProps) {
|
|
||||||
const { currentWorkspace, currentApplication } =
|
|
||||||
useCurrentWorkspaceAndApplication();
|
|
||||||
|
|
||||||
const relativeDateOfDeployment = formatDistanceToNowStrict(
|
|
||||||
parseISO(deployment.deploymentStartedAt),
|
|
||||||
{
|
|
||||||
addSuffix: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const { commitMessage } = deployment;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ListItem.Root className={twMerge('grid grid-flow-row', className)}>
|
|
||||||
<ListItem.Button
|
|
||||||
className="grid grid-flow-col items-center justify-between gap-2 px-2 py-2"
|
|
||||||
component={NavLink}
|
|
||||||
href={`/${currentWorkspace.slug}/${currentApplication.slug}/deployments/${deployment.id}`}
|
|
||||||
>
|
|
||||||
<div className="flex cursor-pointer flex-row items-center justify-center space-x-2 self-center">
|
|
||||||
<div>
|
|
||||||
<Avatar
|
|
||||||
name={deployment.commitUserName}
|
|
||||||
avatarUrl={deployment.commitUserAvatarUrl}
|
|
||||||
className="h-8 w-8"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-flow-row truncate text-sm+ font-medium">
|
|
||||||
<Text className="inline cursor-pointer truncate font-medium leading-snug text-greyscaleDark">
|
|
||||||
{commitMessage?.trim() || (
|
|
||||||
<span className="truncate pr-1 font-normal italic">
|
|
||||||
No commit message
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
<Text className="text-sm font-normal leading-[1.375rem] text-greyscaleGrey">
|
|
||||||
{relativeDateOfDeployment}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-flow-col items-center self-center">
|
|
||||||
{isDeploymentLive && (
|
|
||||||
<div className="flex self-center align-middle">
|
|
||||||
<Status status={StatusEnum.Live}>Live</Status>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="w-20 self-center text-right align-middle font-mono text-sm- font-medium">
|
|
||||||
{deployment.commitSHA.substring(0, 7)}
|
|
||||||
</div>
|
|
||||||
<div className="w-20 self-center text-right align-middle font-mono text-sm-">
|
|
||||||
<AppDeploymentDuration
|
|
||||||
startedAt={deployment.deploymentStartedAt}
|
|
||||||
endedAt={deployment.deploymentEndedAt}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mx-3 self-center">
|
|
||||||
<StatusCircle
|
|
||||||
status={deployment.deploymentStatus as DeploymentStatus}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="self-center">
|
|
||||||
<ChevronRightIcon className="ml-2 h-4 w-4 cursor-pointer self-center" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ListItem.Button>
|
|
||||||
</ListItem.Root>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OverviewDeploymentsProps {
|
interface OverviewDeploymentsProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
githubRepository: { fullName: string };
|
githubRepository: { fullName: string };
|
||||||
@@ -159,9 +69,18 @@ function OverviewDeployments({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (loading) {
|
const {
|
||||||
|
data: scheduledOrPendingDeploymentsData,
|
||||||
|
loading: scheduledOrPendingDeploymentsLoading,
|
||||||
|
} = useScheduledOrPendingDeploymentsSubSubscription({
|
||||||
|
variables: {
|
||||||
|
appId: projectId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (loading || scheduledOrPendingDeploymentsLoading) {
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '240px' }}>
|
<div className="h-60">
|
||||||
<ActivityIndicator label="Loading deployments..." />
|
<ActivityIndicator label="Loading deployments..." />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -218,22 +137,22 @@ function OverviewDeployments({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getLastLiveDeploymentId = getLastLiveDeployment(deployments);
|
const liveDeploymentId = getLastLiveDeployment(deployments);
|
||||||
|
const { deployments: scheduledOrPendingDeployments } =
|
||||||
|
scheduledOrPendingDeploymentsData;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-x-lg flex flex-col divide-y-1 divide-gray-200 rounded-lg border border-veryLightGray">
|
<List className="rounded-x-lg flex flex-col divide-y-1 divide-gray-200 rounded-lg border border-veryLightGray">
|
||||||
{deployments.map((deployment) => {
|
{deployments.map((deployment, index) => (
|
||||||
const isDeploymentLive = deployment.id === getLastLiveDeploymentId;
|
<DeploymentListItem
|
||||||
|
key={deployment.id}
|
||||||
return (
|
deployment={deployment}
|
||||||
<OverviewDeployment
|
isLive={deployment.id === liveDeploymentId}
|
||||||
key={deployment.id}
|
showRedeploy={index === 0}
|
||||||
deployment={deployment}
|
disableRedeploy={scheduledOrPendingDeployments.length > 0}
|
||||||
isDeploymentLive={isDeploymentLive}
|
/>
|
||||||
/>
|
))}
|
||||||
);
|
</List>
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,16 @@ import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAn
|
|||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import { toastStyleProps } from '@/utils/settings/settingsConstants';
|
import { toastStyleProps } from '@/utils/settings/settingsConstants';
|
||||||
import { useState } from 'react';
|
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 { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export interface AllowedEmailSettingsFormValues {
|
export interface AllowedEmailSettingsFormValues {
|
||||||
|
/**
|
||||||
|
* Determines whether or not the allowed email settings are enabled.
|
||||||
|
*/
|
||||||
|
enabled: boolean;
|
||||||
/**
|
/**
|
||||||
* Set of email that are allowed to be used for project's users authentication.
|
* Set of email that are allowed to be used for project's users authentication.
|
||||||
*/
|
*/
|
||||||
@@ -25,7 +29,6 @@ export interface AllowedEmailSettingsFormValues {
|
|||||||
export default function AllowedEmailDomainsSettings() {
|
export default function AllowedEmailDomainsSettings() {
|
||||||
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
||||||
const [updateApp] = useUpdateAppMutation();
|
const [updateApp] = useUpdateAppMutation();
|
||||||
const [enabled, setEnabled] = useState(false);
|
|
||||||
|
|
||||||
const { data, loading, error } = useGetAppQuery({
|
const { data, loading, error } = useGetAppQuery({
|
||||||
variables: {
|
variables: {
|
||||||
@@ -36,12 +39,30 @@ export default function AllowedEmailDomainsSettings() {
|
|||||||
const form = useForm<AllowedEmailSettingsFormValues>({
|
const form = useForm<AllowedEmailSettingsFormValues>({
|
||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
enabled:
|
||||||
|
Boolean(data?.app?.authAccessControlAllowedEmails) ||
|
||||||
|
Boolean(data?.app?.authAccessControlAllowedEmailDomains),
|
||||||
authAccessControlAllowedEmails: data?.app?.authAccessControlAllowedEmails,
|
authAccessControlAllowedEmails: data?.app?.authAccessControlAllowedEmails,
|
||||||
authAccessControlAllowedEmailDomains:
|
authAccessControlAllowedEmailDomains:
|
||||||
data?.app?.authAccessControlAllowedEmailDomains,
|
data?.app?.authAccessControlAllowedEmailDomains,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { register, formState, setValue, watch } = form;
|
||||||
|
const enabled = watch('enabled');
|
||||||
|
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
!data.app?.authAccessControlAllowedEmails &&
|
||||||
|
!data.app?.authAccessControlAllowedEmailDomains
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue('enabled', true, { shouldDirty: false });
|
||||||
|
}, [data.app, setValue]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -56,8 +77,6 @@ export default function AllowedEmailDomainsSettings() {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { register, formState } = form;
|
|
||||||
|
|
||||||
const handleAllowedEmailDomainsChange = async (
|
const handleAllowedEmailDomainsChange = async (
|
||||||
values: AllowedEmailSettingsFormValues,
|
values: AllowedEmailSettingsFormValues,
|
||||||
) => {
|
) => {
|
||||||
@@ -65,7 +84,12 @@ export default function AllowedEmailDomainsSettings() {
|
|||||||
variables: {
|
variables: {
|
||||||
id: currentApplication.id,
|
id: currentApplication.id,
|
||||||
app: {
|
app: {
|
||||||
...values,
|
authAccessControlAllowedEmails: values.enabled
|
||||||
|
? values.authAccessControlAllowedEmails
|
||||||
|
: '',
|
||||||
|
authAccessControlAllowedEmailDomains: values.enabled
|
||||||
|
? values.authAccessControlAllowedEmailDomains
|
||||||
|
: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -89,13 +113,17 @@ export default function AllowedEmailDomainsSettings() {
|
|||||||
<SettingsContainer
|
<SettingsContainer
|
||||||
title="Allowed Emails and Domains"
|
title="Allowed Emails and Domains"
|
||||||
description="Allow specific email addresses and domains to sign up."
|
description="Allow specific email addresses and domains to sign up."
|
||||||
primaryActionButtonProps={{
|
slotProps={{
|
||||||
disabled: !formState.isValid || !formState.isDirty,
|
submitButton: {
|
||||||
loading: formState.isSubmitting,
|
disabled: !formState.isValid || !isDirty,
|
||||||
|
loading: formState.isSubmitting,
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/platform/authentication"
|
docsLink="https://docs.nhost.io/platform/authentication"
|
||||||
enabled={enabled}
|
enabled={enabled}
|
||||||
onEnabledChange={setEnabled}
|
onEnabledChange={(switchEnabled) =>
|
||||||
|
setValue('enabled', switchEnabled, { shouldDirty: true })
|
||||||
|
}
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'row-span-2 grid grid-flow-row gap-4 px-4 lg:grid-cols-3',
|
'row-span-2 grid grid-flow-row gap-4 px-4 lg:grid-cols-3',
|
||||||
|
|||||||
@@ -5,12 +5,16 @@ import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAn
|
|||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import { toastStyleProps } from '@/utils/settings/settingsConstants';
|
import { toastStyleProps } from '@/utils/settings/settingsConstants';
|
||||||
import { useState } from 'react';
|
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 { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export interface BlockedEmailFormValues {
|
export interface BlockedEmailFormValues {
|
||||||
|
/**
|
||||||
|
* Determines whether or not the blocked email settings are enabled.
|
||||||
|
*/
|
||||||
|
enabled: boolean;
|
||||||
/**
|
/**
|
||||||
* Set of emails that are blocked from registering to the user's project.
|
* Set of emails that are blocked from registering to the user's project.
|
||||||
*/
|
*/
|
||||||
@@ -24,7 +28,6 @@ export interface BlockedEmailFormValues {
|
|||||||
export default function BlockedEmailSettings() {
|
export default function BlockedEmailSettings() {
|
||||||
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
||||||
const [updateApp] = useUpdateAppMutation();
|
const [updateApp] = useUpdateAppMutation();
|
||||||
const [enabled, setEnabled] = useState(false);
|
|
||||||
|
|
||||||
const { data, loading, error } = useGetAppQuery({
|
const { data, loading, error } = useGetAppQuery({
|
||||||
variables: {
|
variables: {
|
||||||
@@ -35,12 +38,30 @@ export default function BlockedEmailSettings() {
|
|||||||
const form = useForm<BlockedEmailFormValues>({
|
const form = useForm<BlockedEmailFormValues>({
|
||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
enabled:
|
||||||
|
Boolean(data?.app?.authAccessControlBlockedEmails) ||
|
||||||
|
Boolean(data?.app?.authAccessControlBlockedEmailDomains),
|
||||||
authAccessControlBlockedEmails: data?.app?.authAccessControlBlockedEmails,
|
authAccessControlBlockedEmails: data?.app?.authAccessControlBlockedEmails,
|
||||||
authAccessControlBlockedEmailDomains:
|
authAccessControlBlockedEmailDomains:
|
||||||
data?.app?.authAccessControlBlockedEmailDomains,
|
data?.app?.authAccessControlBlockedEmailDomains,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { register, formState, setValue, watch } = form;
|
||||||
|
const enabled = watch('enabled');
|
||||||
|
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
!data.app?.authAccessControlBlockedEmails &&
|
||||||
|
!data.app?.authAccessControlBlockedEmailDomains
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue('enabled', true, { shouldDirty: false });
|
||||||
|
}, [data.app, setValue]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
@@ -55,8 +76,6 @@ export default function BlockedEmailSettings() {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { register, formState } = form;
|
|
||||||
|
|
||||||
const handleAllowedEmailDomainsChange = async (
|
const handleAllowedEmailDomainsChange = async (
|
||||||
values: BlockedEmailFormValues,
|
values: BlockedEmailFormValues,
|
||||||
) => {
|
) => {
|
||||||
@@ -64,7 +83,12 @@ export default function BlockedEmailSettings() {
|
|||||||
variables: {
|
variables: {
|
||||||
id: currentApplication.id,
|
id: currentApplication.id,
|
||||||
app: {
|
app: {
|
||||||
...values,
|
authAccessControlBlockedEmails: values.enabled
|
||||||
|
? values.authAccessControlBlockedEmails
|
||||||
|
: '',
|
||||||
|
authAccessControlBlockedEmailDomains: values.enabled
|
||||||
|
? values.authAccessControlBlockedEmailDomains
|
||||||
|
: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -88,13 +112,17 @@ export default function BlockedEmailSettings() {
|
|||||||
<SettingsContainer
|
<SettingsContainer
|
||||||
title="Blocked Emails and Domains"
|
title="Blocked Emails and Domains"
|
||||||
description="Block specific email addresses and domains to sign up."
|
description="Block specific email addresses and domains to sign up."
|
||||||
primaryActionButtonProps={{
|
slotProps={{
|
||||||
disabled: !formState.isValid || !formState.isDirty,
|
submitButton: {
|
||||||
loading: formState.isSubmitting,
|
disabled: !formState.isValid || !isDirty,
|
||||||
|
loading: formState.isSubmitting,
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
docsLink="https://docs.nhost.io/platform/authentication"
|
docsLink="https://docs.nhost.io/platform/authentication"
|
||||||
enabled={enabled}
|
enabled={enabled}
|
||||||
onEnabledChange={setEnabled}
|
onEnabledChange={(switchEnabled) =>
|
||||||
|
setValue('enabled', switchEnabled, { shouldDirty: true })
|
||||||
|
}
|
||||||
showSwitch
|
showSwitch
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'row-span-2 grid grid-flow-row gap-4 px-4 lg:grid-cols-3',
|
'row-span-2 grid grid-flow-row gap-4 px-4 lg:grid-cols-3',
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import clsx from 'clsx';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export type DeploymentStatus =
|
export type DeploymentStatus =
|
||||||
| 'DEPLOYING'
|
| 'DEPLOYING'
|
||||||
| 'DEPLOYED'
|
| 'DEPLOYED'
|
||||||
| 'FAILED'
|
| 'FAILED'
|
||||||
|
| 'PENDING'
|
||||||
|
| 'SCHEDULED'
|
||||||
| undefined
|
| undefined
|
||||||
| null;
|
| null;
|
||||||
|
|
||||||
@@ -12,32 +14,30 @@ type StatusCircleProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function StatusCircle(props: StatusCircleProps) {
|
export function StatusCircle({ status, className }: StatusCircleProps) {
|
||||||
const { status, className } = props;
|
|
||||||
|
|
||||||
const baseClasses = 'w-1.5 h-1.5 rounded-full';
|
const baseClasses = 'w-1.5 h-1.5 rounded-full';
|
||||||
|
|
||||||
if (!status) {
|
if (status === 'DEPLOYING' || status === 'PENDING') {
|
||||||
const classes = clsx(baseClasses, 'bg-gray-300', className);
|
return (
|
||||||
return <div className={classes} />;
|
<div
|
||||||
}
|
className={twMerge(
|
||||||
|
baseClasses,
|
||||||
if (status === 'DEPLOYING') {
|
'bg-yellow-300 animate-pulse',
|
||||||
const classes = clsx(baseClasses, 'bg-yellow-300', className);
|
className,
|
||||||
return <div className={classes} />;
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 'DEPLOYED') {
|
if (status === 'DEPLOYED') {
|
||||||
const classes = clsx(baseClasses, 'bg-green-300', className);
|
return <div className={twMerge(baseClasses, 'bg-green-300', className)} />;
|
||||||
return <div className={classes} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 'FAILED') {
|
if (status === 'FAILED') {
|
||||||
const classes = clsx(baseClasses, 'bg-red', className);
|
return <div className={twMerge(baseClasses, 'bg-red', className)} />;
|
||||||
return <div className={classes} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return <div className={twMerge(baseClasses, 'bg-gray-300', className)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default StatusCircle;
|
export default StatusCircle;
|
||||||
|
|||||||
17
dashboard/src/components/ui/v2/ListItem/ListItemAvatar.tsx
Normal file
17
dashboard/src/components/ui/v2/ListItem/ListItemAvatar.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { styled } from '@mui/material';
|
||||||
|
import type { ListItemAvatarProps as MaterialListItemAvatarProps } from '@mui/material/ListItemAvatar';
|
||||||
|
import MaterialListItemAvatar from '@mui/material/ListItemAvatar';
|
||||||
|
|
||||||
|
export interface ListItemAvatarProps extends MaterialListItemAvatarProps {}
|
||||||
|
|
||||||
|
const StyledListItemAvatar = styled(MaterialListItemAvatar)({
|
||||||
|
minWidth: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
function ListItemAvatar({ children, ...props }: ListItemAvatarProps) {
|
||||||
|
return <StyledListItemAvatar {...props}>{children}</StyledListItemAvatar>;
|
||||||
|
}
|
||||||
|
|
||||||
|
ListItemAvatar.displayName = 'NhostListItemAvatar';
|
||||||
|
|
||||||
|
export default ListItemAvatar;
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import ListItemAvatar from './ListItemAvatar';
|
||||||
import ListItemButton from './ListItemButton';
|
import ListItemButton from './ListItemButton';
|
||||||
import ListItemIcon from './ListItemIcon';
|
import ListItemIcon from './ListItemIcon';
|
||||||
import ListItemRoot from './ListItemRoot';
|
import ListItemRoot from './ListItemRoot';
|
||||||
import ListItemText from './ListItemText';
|
import ListItemText from './ListItemText';
|
||||||
|
|
||||||
|
export * from './ListItemAvatar';
|
||||||
|
export { default as ListItemAvatar } from './ListItemAvatar';
|
||||||
export * from './ListItemButton';
|
export * from './ListItemButton';
|
||||||
export { default as ListItemButton } from './ListItemButton';
|
export { default as ListItemButton } from './ListItemButton';
|
||||||
export * from './ListItemIcon';
|
export * from './ListItemIcon';
|
||||||
@@ -13,6 +16,7 @@ export * from './ListItemText';
|
|||||||
export { default as ListItemText } from './ListItemText';
|
export { default as ListItemText } from './ListItemText';
|
||||||
|
|
||||||
export const ListItem = {
|
export const ListItem = {
|
||||||
|
Avatar: ListItemAvatar,
|
||||||
Root: ListItemRoot,
|
Root: ListItemRoot,
|
||||||
Button: ListItemButton,
|
Button: ListItemButton,
|
||||||
Icon: ListItemIcon,
|
Icon: ListItemIcon,
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import type { IconProps } from '@/ui/v2/icons';
|
||||||
|
import SvgIcon from '@mui/material/SvgIcon';
|
||||||
|
|
||||||
|
function ArrowCounterclockwiseIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<SvgIcon
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
aria-label="A counterclockwise arrow"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M4.99 6.232h-3v-3"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M4.11 11.89a5.5 5.5 0 1 0 0-7.78L1.99 6.233"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrowCounterclockwiseIcon.displayName = 'NhostArrowCounterclockwiseIcon';
|
||||||
|
|
||||||
|
export default ArrowCounterclockwiseIcon;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './ArrowCounterclockwiseIcon';
|
||||||
@@ -67,8 +67,8 @@ export default function CreateUserForm({
|
|||||||
} = form;
|
} = form;
|
||||||
|
|
||||||
const baseAuthUrl = generateAppServiceUrl(
|
const baseAuthUrl = generateAppServiceUrl(
|
||||||
currentApplication.subdomain,
|
currentApplication?.subdomain,
|
||||||
currentApplication.region.awsName,
|
currentApplication?.region?.awsName,
|
||||||
'auth',
|
'auth',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -8,18 +8,18 @@ import Button from '@/ui/v2/Button';
|
|||||||
import Chip from '@/ui/v2/Chip';
|
import Chip from '@/ui/v2/Chip';
|
||||||
import { Dropdown } from '@/ui/v2/Dropdown';
|
import { Dropdown } from '@/ui/v2/Dropdown';
|
||||||
import IconButton from '@/ui/v2/IconButton';
|
import IconButton from '@/ui/v2/IconButton';
|
||||||
|
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import InputLabel from '@/ui/v2/InputLabel';
|
import InputLabel from '@/ui/v2/InputLabel';
|
||||||
import Option from '@/ui/v2/Option';
|
import Option from '@/ui/v2/Option';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
import { copy } from '@/utils/copy';
|
||||||
|
import getUserRoles from '@/utils/settings/getUserRoles';
|
||||||
|
import { toastStyleProps } from '@/utils/settings/settingsConstants';
|
||||||
import {
|
import {
|
||||||
useGetRolesQuery,
|
useGetRolesQuery,
|
||||||
useUpdateRemoteAppUserMutation,
|
useUpdateRemoteAppUserMutation,
|
||||||
} from '@/utils/__generated__/graphql';
|
} from '@/utils/__generated__/graphql';
|
||||||
import { copy } from '@/utils/copy';
|
|
||||||
import getUserRoles from '@/utils/settings/getUserRoles';
|
|
||||||
import { toastStyleProps } from '@/utils/settings/settingsConstants';
|
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { Avatar } from '@mui/material';
|
import { Avatar } from '@mui/material';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
@@ -137,7 +137,7 @@ export default function EditUserForm({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { data: dataRoles } = useGetRolesQuery({
|
const { data: dataRoles } = useGetRolesQuery({
|
||||||
variables: { id: currentApplication.id },
|
variables: { id: currentApplication?.id },
|
||||||
});
|
});
|
||||||
|
|
||||||
const allAvailableProjectRoles = getUserRoles(
|
const allAvailableProjectRoles = getUserRoles(
|
||||||
@@ -206,11 +206,7 @@ export default function EditUserForm({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Dropdown.Root>
|
<Dropdown.Root>
|
||||||
<Dropdown.Trigger
|
<Dropdown.Trigger autoFocus={false} asChild className="gap-2">
|
||||||
autoFocus={false}
|
|
||||||
asChild
|
|
||||||
className="gap-2"
|
|
||||||
>
|
|
||||||
<Button variant="outlined" color="secondary">
|
<Button variant="outlined" color="secondary">
|
||||||
Actions
|
Actions
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ import Chip from '@/ui/v2/Chip';
|
|||||||
import Divider from '@/ui/v2/Divider';
|
import Divider from '@/ui/v2/Divider';
|
||||||
import { Dropdown } from '@/ui/v2/Dropdown';
|
import { Dropdown } from '@/ui/v2/Dropdown';
|
||||||
import IconButton from '@/ui/v2/IconButton';
|
import IconButton from '@/ui/v2/IconButton';
|
||||||
import List from '@/ui/v2/List';
|
|
||||||
import { ListItem } from '@/ui/v2/ListItem';
|
|
||||||
import Text from '@/ui/v2/Text';
|
|
||||||
import DotsHorizontalIcon from '@/ui/v2/icons/DotsHorizontalIcon';
|
import DotsHorizontalIcon from '@/ui/v2/icons/DotsHorizontalIcon';
|
||||||
import TrashIcon from '@/ui/v2/icons/TrashIcon';
|
import TrashIcon from '@/ui/v2/icons/TrashIcon';
|
||||||
import UserIcon from '@/ui/v2/icons/UserIcon';
|
import UserIcon from '@/ui/v2/icons/UserIcon';
|
||||||
|
import List from '@/ui/v2/List';
|
||||||
|
import { ListItem } from '@/ui/v2/ListItem';
|
||||||
|
import Text from '@/ui/v2/Text';
|
||||||
|
import getUserRoles from '@/utils/settings/getUserRoles';
|
||||||
|
import { toastStyleProps } from '@/utils/settings/settingsConstants';
|
||||||
import type { RemoteAppGetUsersQuery } from '@/utils/__generated__/graphql';
|
import type { RemoteAppGetUsersQuery } from '@/utils/__generated__/graphql';
|
||||||
import {
|
import {
|
||||||
useDeleteRemoteAppUserRolesMutation,
|
useDeleteRemoteAppUserRolesMutation,
|
||||||
@@ -21,9 +23,6 @@ import {
|
|||||||
useRemoteAppDeleteUserMutation,
|
useRemoteAppDeleteUserMutation,
|
||||||
useUpdateRemoteAppUserMutation,
|
useUpdateRemoteAppUserMutation,
|
||||||
} from '@/utils/__generated__/graphql';
|
} from '@/utils/__generated__/graphql';
|
||||||
|
|
||||||
import getUserRoles from '@/utils/settings/getUserRoles';
|
|
||||||
import { toastStyleProps } from '@/utils/settings/settingsConstants';
|
|
||||||
import type { ApolloQueryResult } from '@apollo/client';
|
import type { ApolloQueryResult } from '@apollo/client';
|
||||||
import { Avatar } from '@mui/material';
|
import { Avatar } from '@mui/material';
|
||||||
import { formatDistance } from 'date-fns';
|
import { formatDistance } from 'date-fns';
|
||||||
@@ -77,7 +76,7 @@ export default function UsersBody({
|
|||||||
* in the drawer form.
|
* in the drawer form.
|
||||||
*/
|
*/
|
||||||
const { data: dataRoles } = useGetRolesQuery({
|
const { data: dataRoles } = useGetRolesQuery({
|
||||||
variables: { id: currentApplication.id },
|
variables: { id: currentApplication?.id },
|
||||||
});
|
});
|
||||||
|
|
||||||
const allAvailableProjectRoles = useMemo(
|
const allAvailableProjectRoles = useMemo(
|
||||||
|
|||||||
@@ -20,6 +20,34 @@ query getDeployments($id: uuid!, $limit: Int!, $offset: Int!) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subscription ScheduledOrPendingDeploymentsSub($appId: uuid!) {
|
||||||
|
deployments(
|
||||||
|
where: {
|
||||||
|
deploymentStatus: { _in: ["PENDING", "SCHEDULED"] }
|
||||||
|
appId: { _eq: $appId }
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
...DeploymentRow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subscription LatestLiveDeploymentSub($appId: uuid!) {
|
||||||
|
deployments(
|
||||||
|
where: { deploymentStatus: { _eq: "DEPLOYED" }, appId: { _eq: $appId } }
|
||||||
|
order_by: { deploymentEndedAt: desc }
|
||||||
|
limit: 1
|
||||||
|
offset: 0
|
||||||
|
) {
|
||||||
|
...DeploymentRow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation InsertDeployment($object: deployments_insert_input!) {
|
||||||
|
insertDeployment(object: $object) {
|
||||||
|
...DeploymentRow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
subscription getDeploymentsSub($id: uuid!, $limit: Int!, $offset: Int!) {
|
subscription getDeploymentsSub($id: uuid!, $limit: Int!, $offset: Int!) {
|
||||||
deployments(
|
deployments(
|
||||||
where: { appId: { _eq: $id } }
|
where: { appId: { _eq: $id } }
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export default function useFiles({
|
|||||||
currentApplication.subdomain,
|
currentApplication.subdomain,
|
||||||
currentApplication.region.awsName,
|
currentApplication.region.awsName,
|
||||||
'storage',
|
'storage',
|
||||||
)}/${file.id}`;
|
)}/files/${file.id}`;
|
||||||
|
|
||||||
const fetchParams = new URLSearchParams();
|
const fetchParams = new URLSearchParams();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AppDeploymentDuration } from '@/components/applications/AppDeployments';
|
import AppDeploymentDuration from '@/components/deployments/AppDeploymentDuration';
|
||||||
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 { useDeploymentSubSubscription } from '@/generated/graphql';
|
import { useDeploymentSubSubscription } from '@/generated/graphql';
|
||||||
@@ -8,6 +8,7 @@ import DelayedLoading from '@/ui/DelayedLoading';
|
|||||||
import type { DeploymentStatus } from '@/ui/StatusCircle';
|
import type { DeploymentStatus } from '@/ui/StatusCircle';
|
||||||
import { StatusCircle } from '@/ui/StatusCircle';
|
import { StatusCircle } from '@/ui/StatusCircle';
|
||||||
import { Text } from '@/ui/Text';
|
import { Text } from '@/ui/Text';
|
||||||
|
import Link from '@/ui/v2/Link';
|
||||||
import { format, formatDistanceToNowStrict, parseISO } from 'date-fns';
|
import { format, formatDistanceToNowStrict, parseISO } from 'date-fns';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
@@ -50,6 +51,12 @@ export default function DeploymentDetailsPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const relativeDateOfDeployment = deployment.deploymentStartedAt
|
||||||
|
? formatDistanceToNowStrict(parseISO(deployment.deploymentStartedAt), {
|
||||||
|
addSuffix: true,
|
||||||
|
})
|
||||||
|
: '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
@@ -104,24 +111,20 @@ export default function DeploymentDetailsPage() {
|
|||||||
{deployment.commitMessage}
|
{deployment.commitMessage}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm+ text-greyscaleGrey">
|
<div className="text-sm+ text-greyscaleGrey">
|
||||||
{formatDistanceToNowStrict(
|
{relativeDateOfDeployment}
|
||||||
parseISO(deployment.deploymentStartedAt),
|
|
||||||
{
|
|
||||||
addSuffix: true,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className=" flex items-center">
|
<div className=" flex items-center">
|
||||||
<a
|
<Link
|
||||||
className="self-center font-mono text-sm- font-medium text-greyscaleDark"
|
className="self-center font-mono text-sm- font-medium"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
href={`https://github.com/${currentApplication.githubRepository?.fullName}/commit/${deployment.commitSHA}`}
|
href={`https://github.com/${currentApplication.githubRepository?.fullName}/commit/${deployment.commitSHA}`}
|
||||||
|
underline="hover"
|
||||||
>
|
>
|
||||||
{deployment.commitSHA.substring(0, 7)}
|
{deployment.commitSHA.substring(0, 7)}
|
||||||
</a>
|
</Link>
|
||||||
|
|
||||||
<div className="w-20 text-right">
|
<div className="w-20 text-right">
|
||||||
<AppDeploymentDuration
|
<AppDeploymentDuration
|
||||||
@@ -133,6 +136,10 @@ export default function DeploymentDetailsPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="rounded-lg bg-verydark p-4 text-sm- text-white">
|
<div className="rounded-lg bg-verydark p-4 text-sm- text-white">
|
||||||
|
{deployment.deploymentLogs.length === 0 && (
|
||||||
|
<span className="font-mono">No message.</span>
|
||||||
|
)}
|
||||||
|
|
||||||
{deployment.deploymentLogs.map((log) => (
|
{deployment.deploymentLogs.map((log) => (
|
||||||
<div key={log.id} className="flex font-mono">
|
<div key={log.id} className="flex font-mono">
|
||||||
<div className=" mr-2 flex-shrink-0">
|
<div className=" mr-2 flex-shrink-0">
|
||||||
|
|||||||
121
dashboard/src/utils/__generated__/graphql.ts
generated
121
dashboard/src/utils/__generated__/graphql.ts
generated
@@ -17215,6 +17215,27 @@ export type GetDeploymentsQueryVariables = Exact<{
|
|||||||
|
|
||||||
export type GetDeploymentsQuery = { __typename?: 'query_root', deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, deploymentStatus?: string | null, commitUserName?: string | null, commitUserAvatarUrl?: string | null, commitMessage?: string | null }> };
|
export type GetDeploymentsQuery = { __typename?: 'query_root', deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, deploymentStatus?: string | null, commitUserName?: string | null, commitUserAvatarUrl?: string | null, commitMessage?: string | null }> };
|
||||||
|
|
||||||
|
export type ScheduledOrPendingDeploymentsSubSubscriptionVariables = Exact<{
|
||||||
|
appId: Scalars['uuid'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ScheduledOrPendingDeploymentsSubSubscription = { __typename?: 'subscription_root', deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, deploymentStatus?: string | null, commitUserName?: string | null, commitUserAvatarUrl?: string | null, commitMessage?: string | null }> };
|
||||||
|
|
||||||
|
export type LatestLiveDeploymentSubSubscriptionVariables = Exact<{
|
||||||
|
appId: Scalars['uuid'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type LatestLiveDeploymentSubSubscription = { __typename?: 'subscription_root', deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, deploymentStatus?: string | null, commitUserName?: string | null, commitUserAvatarUrl?: string | null, commitMessage?: string | null }> };
|
||||||
|
|
||||||
|
export type InsertDeploymentMutationVariables = Exact<{
|
||||||
|
object: Deployments_Insert_Input;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type InsertDeploymentMutation = { __typename?: 'mutation_root', insertDeployment?: { __typename?: 'deployments', id: any, commitSHA: string, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, deploymentStatus?: string | null, commitUserName?: string | null, commitUserAvatarUrl?: string | null, commitMessage?: string | null } | null };
|
||||||
|
|
||||||
export type GetDeploymentsSubSubscriptionVariables = Exact<{
|
export type GetDeploymentsSubSubscriptionVariables = Exact<{
|
||||||
id: Scalars['uuid'];
|
id: Scalars['uuid'];
|
||||||
limit: Scalars['Int'];
|
limit: Scalars['Int'];
|
||||||
@@ -19147,6 +19168,106 @@ export type GetDeploymentsQueryResult = Apollo.QueryResult<GetDeploymentsQuery,
|
|||||||
export function refetchGetDeploymentsQuery(variables: GetDeploymentsQueryVariables) {
|
export function refetchGetDeploymentsQuery(variables: GetDeploymentsQueryVariables) {
|
||||||
return { query: GetDeploymentsDocument, variables: variables }
|
return { query: GetDeploymentsDocument, variables: variables }
|
||||||
}
|
}
|
||||||
|
export const ScheduledOrPendingDeploymentsSubDocument = gql`
|
||||||
|
subscription ScheduledOrPendingDeploymentsSub($appId: uuid!) {
|
||||||
|
deployments(
|
||||||
|
where: {deploymentStatus: {_in: ["PENDING", "SCHEDULED"]}, appId: {_eq: $appId}}
|
||||||
|
) {
|
||||||
|
...DeploymentRow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${DeploymentRowFragmentDoc}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useScheduledOrPendingDeploymentsSubSubscription__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useScheduledOrPendingDeploymentsSubSubscription` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useScheduledOrPendingDeploymentsSubSubscription` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useScheduledOrPendingDeploymentsSubSubscription({
|
||||||
|
* variables: {
|
||||||
|
* appId: // value for 'appId'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useScheduledOrPendingDeploymentsSubSubscription(baseOptions: Apollo.SubscriptionHookOptions<ScheduledOrPendingDeploymentsSubSubscription, ScheduledOrPendingDeploymentsSubSubscriptionVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useSubscription<ScheduledOrPendingDeploymentsSubSubscription, ScheduledOrPendingDeploymentsSubSubscriptionVariables>(ScheduledOrPendingDeploymentsSubDocument, options);
|
||||||
|
}
|
||||||
|
export type ScheduledOrPendingDeploymentsSubSubscriptionHookResult = ReturnType<typeof useScheduledOrPendingDeploymentsSubSubscription>;
|
||||||
|
export type ScheduledOrPendingDeploymentsSubSubscriptionResult = Apollo.SubscriptionResult<ScheduledOrPendingDeploymentsSubSubscription>;
|
||||||
|
export const LatestLiveDeploymentSubDocument = gql`
|
||||||
|
subscription LatestLiveDeploymentSub($appId: uuid!) {
|
||||||
|
deployments(
|
||||||
|
where: {deploymentStatus: {_eq: "DEPLOYED"}, appId: {_eq: $appId}}
|
||||||
|
order_by: {deploymentEndedAt: desc}
|
||||||
|
limit: 1
|
||||||
|
offset: 0
|
||||||
|
) {
|
||||||
|
...DeploymentRow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${DeploymentRowFragmentDoc}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useLatestLiveDeploymentSubSubscription__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useLatestLiveDeploymentSubSubscription` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useLatestLiveDeploymentSubSubscription` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useLatestLiveDeploymentSubSubscription({
|
||||||
|
* variables: {
|
||||||
|
* appId: // value for 'appId'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useLatestLiveDeploymentSubSubscription(baseOptions: Apollo.SubscriptionHookOptions<LatestLiveDeploymentSubSubscription, LatestLiveDeploymentSubSubscriptionVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useSubscription<LatestLiveDeploymentSubSubscription, LatestLiveDeploymentSubSubscriptionVariables>(LatestLiveDeploymentSubDocument, options);
|
||||||
|
}
|
||||||
|
export type LatestLiveDeploymentSubSubscriptionHookResult = ReturnType<typeof useLatestLiveDeploymentSubSubscription>;
|
||||||
|
export type LatestLiveDeploymentSubSubscriptionResult = Apollo.SubscriptionResult<LatestLiveDeploymentSubSubscription>;
|
||||||
|
export const InsertDeploymentDocument = gql`
|
||||||
|
mutation InsertDeployment($object: deployments_insert_input!) {
|
||||||
|
insertDeployment(object: $object) {
|
||||||
|
...DeploymentRow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${DeploymentRowFragmentDoc}`;
|
||||||
|
export type InsertDeploymentMutationFn = Apollo.MutationFunction<InsertDeploymentMutation, InsertDeploymentMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useInsertDeploymentMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useInsertDeploymentMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useInsertDeploymentMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [insertDeploymentMutation, { data, loading, error }] = useInsertDeploymentMutation({
|
||||||
|
* variables: {
|
||||||
|
* object: // value for 'object'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useInsertDeploymentMutation(baseOptions?: Apollo.MutationHookOptions<InsertDeploymentMutation, InsertDeploymentMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<InsertDeploymentMutation, InsertDeploymentMutationVariables>(InsertDeploymentDocument, options);
|
||||||
|
}
|
||||||
|
export type InsertDeploymentMutationHookResult = ReturnType<typeof useInsertDeploymentMutation>;
|
||||||
|
export type InsertDeploymentMutationResult = Apollo.MutationResult<InsertDeploymentMutation>;
|
||||||
|
export type InsertDeploymentMutationOptions = Apollo.BaseMutationOptions<InsertDeploymentMutation, InsertDeploymentMutationVariables>;
|
||||||
export const GetDeploymentsSubDocument = gql`
|
export const GetDeploymentsSubDocument = gql`
|
||||||
subscription getDeploymentsSub($id: uuid!, $limit: Int!, $offset: Int!) {
|
subscription getDeploymentsSub($id: uuid!, $limit: Int!, $offset: Int!) {
|
||||||
deployments(
|
deployments(
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ module.exports = {
|
|||||||
...defaultTheme.screens,
|
...defaultTheme.screens,
|
||||||
},
|
},
|
||||||
extend: {
|
extend: {
|
||||||
|
animation: {
|
||||||
|
'spin-reverse': 'spin 1.5s linear infinite reverse',
|
||||||
|
},
|
||||||
colors: {
|
colors: {
|
||||||
primary: '#0052cd',
|
primary: '#0052cd',
|
||||||
'primary-light': '#ebf3ff',
|
'primary-light': '#ebf3ff',
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ You can install the Nhost Next.js SDK with:
|
|||||||
<TabItem value="npm" label="npm" default>
|
<TabItem value="npm" label="npm" default>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install@nhost/nextjs graphql
|
npm install @nhost/nextjs graphql
|
||||||
```
|
```
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
# GraphQL Code Generator Example with React and Apollo Client
|
# GraphQL Code Generator Example with React and Apollo Client
|
||||||
|
|
||||||
This is an example repo for how to use GraphQL Code Generator together with:
|
Todo app to show how to use:
|
||||||
|
|
||||||
- [TypeScript](https://www.typescriptlang.org/)
|
- [Nhost](https://nhost.io/)
|
||||||
- [React](https://reactjs.org/)
|
- [React](https://reactjs.org/)
|
||||||
|
- [TypeScript](https://www.typescriptlang.org/)
|
||||||
|
- [GraphQL Code Generator](https://the-guild.dev/graphql/codegen)
|
||||||
- [Apollo Client](https://www.apollographql.com/docs/react/)
|
- [Apollo Client](https://www.apollographql.com/docs/react/)
|
||||||
- [Nhost](http://nhost.io/)
|
|
||||||
|
|
||||||
This repo is a reference repo for the blog post: [How to use GraphQL Code Generator with React and Apollo](https://nhost.io/blog/how-to-use-graphql-code-generator-with-react-and-apollo).
|
This repo is a reference repo for the blog post: [How to use GraphQL Code Generator with React and Apollo](https://nhost.io/blog/how-to-use-graphql-code-generator-with-react-and-apollo).
|
||||||
|
|
||||||
@@ -13,42 +14,42 @@ This repo is a reference repo for the blog post: [How to use GraphQL Code Genera
|
|||||||
|
|
||||||
1. Clone the repository
|
1. Clone the repository
|
||||||
|
|
||||||
```
|
```sh
|
||||||
git clone https://github.com/nhost/nhost
|
git clone https://github.com/nhost/nhost
|
||||||
cd nhost
|
cd nhost
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install and build dependencies
|
2. Install and build dependencies
|
||||||
|
|
||||||
```
|
```sh
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm build
|
pnpm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Go to the Codegen React Apollo example folder
|
3. Go to the example folder
|
||||||
|
|
||||||
```
|
```sh
|
||||||
cd examples/codegen-react-apollo
|
cd examples/codegen-react-urql
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Terminal 1: Start Nhost
|
4. Terminal 1: Start Nhost
|
||||||
|
|
||||||
> Make sure you have the [Nhost CLI installed](https://docs.nhost.io/platform/cli).
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nhost up
|
nhost up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Terminal 2: Run GraphQL Codegen
|
5. Terminal 2: Start the React application
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## GraphQL Code Generators
|
||||||
|
|
||||||
|
To re-run the GraphQL Code Generators, run the following:
|
||||||
|
|
||||||
```
|
```
|
||||||
pnpm codegen -w
|
pnpm codegen -w
|
||||||
```
|
```
|
||||||
|
|
||||||
> `-w` runs [codegen in watch mode](https://www.the-guild.dev/graphql/codegen/docs/getting-started/development-workflow#watch-mode).
|
> `-w` runs [codegen in watch mode](https://www.the-guild.dev/graphql/codegen/docs/getting-started/development-workflow#watch-mode).
|
||||||
|
|
||||||
6. Terminal 3: Start the React application
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pnpm dev
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -2,13 +2,3 @@ schema:
|
|||||||
- http://localhost:1337/v1/graphql:
|
- http://localhost:1337/v1/graphql:
|
||||||
headers:
|
headers:
|
||||||
x-hasura-admin-secret: nhost-admin-secret
|
x-hasura-admin-secret: nhost-admin-secret
|
||||||
documents:
|
|
||||||
- 'src/**/*.graphql'
|
|
||||||
generates:
|
|
||||||
src/utils/__generated__/graphql.ts:
|
|
||||||
plugins:
|
|
||||||
- 'typescript'
|
|
||||||
- 'typescript-operations'
|
|
||||||
- 'typescript-react-apollo'
|
|
||||||
config:
|
|
||||||
withRefetchFn: true
|
|
||||||
|
|||||||
@@ -9,6 +9,6 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/index.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
metadata_directory: metadata
|
metadata_directory: metadata
|
||||||
services:
|
services:
|
||||||
hasura:
|
hasura:
|
||||||
image: hasura/graphql-engine:v2.15.2
|
|
||||||
environment:
|
environment:
|
||||||
hasura_graphql_enable_remote_schema_permissions: false
|
hasura_graphql_enable_remote_schema_permissions: false
|
||||||
|
image: hasura/graphql-engine:v2.15.2
|
||||||
|
minio:
|
||||||
|
environment:
|
||||||
|
minio_root_password: minioaccesskey123123
|
||||||
|
minio_root_user: minioaccesskey123123
|
||||||
|
postgres:
|
||||||
|
environment:
|
||||||
|
postgres_password: postgres
|
||||||
|
postgres_user: postgres
|
||||||
auth:
|
auth:
|
||||||
image: nhost/hasura-auth:0.16.2
|
image: nhost/hasura-auth:0.16.1
|
||||||
storage:
|
storage:
|
||||||
image: nhost/hasura-storage:0.3.0
|
image: nhost/hasura-storage:0.3.0
|
||||||
auth:
|
auth:
|
||||||
|
webauthn:
|
||||||
|
enabled: true
|
||||||
|
rp_name: URQL
|
||||||
access_control:
|
access_control:
|
||||||
email:
|
email:
|
||||||
allowed_email_domains: ''
|
allowed_email_domains: ''
|
||||||
@@ -18,13 +29,13 @@ auth:
|
|||||||
url:
|
url:
|
||||||
allowed_redirect_urls: ''
|
allowed_redirect_urls: ''
|
||||||
anonymous_users_enabled: false
|
anonymous_users_enabled: false
|
||||||
client_url: http://localhost:3000
|
client_url: http://localhost:5173
|
||||||
disable_new_users: false
|
disable_new_users: false
|
||||||
email:
|
email:
|
||||||
enabled: false
|
enabled: false
|
||||||
passwordless:
|
passwordless:
|
||||||
enabled: false
|
enabled: true
|
||||||
signin_email_verified_required: true
|
signin_email_verified_required: false
|
||||||
template_fetch_url: ''
|
template_fetch_url: ''
|
||||||
gravatar:
|
gravatar:
|
||||||
default: ''
|
default: ''
|
||||||
@@ -117,11 +128,7 @@ auth:
|
|||||||
secure: false
|
secure: false
|
||||||
sender: hasura-auth@example.com
|
sender: hasura-auth@example.com
|
||||||
user: user
|
user: user
|
||||||
token:
|
access_token_expires_in: 315
|
||||||
access:
|
|
||||||
expires_in: 900
|
|
||||||
refresh:
|
|
||||||
expires_in: 43200
|
|
||||||
user:
|
user:
|
||||||
allowed_roles: user,me
|
allowed_roles: user,me
|
||||||
default_allowed_roles: user,me
|
default_allowed_roles: user,me
|
||||||
|
|||||||
@@ -9,6 +9,6 @@
|
|||||||
connection_lifetime: 600
|
connection_lifetime: 600
|
||||||
idle_timeout: 180
|
idle_timeout: 180
|
||||||
max_connections: 50
|
max_connections: 50
|
||||||
retries: 1
|
retries: 20
|
||||||
use_prepared_statements: true
|
use_prepared_statements: true
|
||||||
tables: "!include default/tables/tables.yaml"
|
tables: "!include default/tables/tables.yaml"
|
||||||
|
|||||||
@@ -2,8 +2,14 @@ table:
|
|||||||
name: provider_requests
|
name: provider_requests
|
||||||
schema: auth
|
schema: auth
|
||||||
configuration:
|
configuration:
|
||||||
column_config: {}
|
column_config:
|
||||||
custom_column_names: {}
|
id:
|
||||||
|
custom_name: id
|
||||||
|
options:
|
||||||
|
custom_name: options
|
||||||
|
custom_column_names:
|
||||||
|
id: id
|
||||||
|
options: options
|
||||||
custom_name: authProviderRequests
|
custom_name: authProviderRequests
|
||||||
custom_root_fields:
|
custom_root_fields:
|
||||||
delete: deleteAuthProviderRequests
|
delete: deleteAuthProviderRequests
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ table:
|
|||||||
name: providers
|
name: providers
|
||||||
schema: auth
|
schema: auth
|
||||||
configuration:
|
configuration:
|
||||||
column_config: {}
|
column_config:
|
||||||
custom_column_names: {}
|
id:
|
||||||
|
custom_name: id
|
||||||
|
custom_column_names:
|
||||||
|
id: id
|
||||||
custom_name: authProviders
|
custom_name: authProviders
|
||||||
custom_root_fields:
|
custom_root_fields:
|
||||||
delete: deleteAuthProviders
|
delete: deleteAuthProviders
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ table:
|
|||||||
name: roles
|
name: roles
|
||||||
schema: auth
|
schema: auth
|
||||||
configuration:
|
configuration:
|
||||||
column_config: {}
|
column_config:
|
||||||
custom_column_names: {}
|
role:
|
||||||
|
custom_name: role
|
||||||
|
custom_column_names:
|
||||||
|
role: role
|
||||||
custom_name: authRoles
|
custom_name: authRoles
|
||||||
custom_root_fields:
|
custom_root_fields:
|
||||||
delete: deleteAuthRoles
|
delete: deleteAuthRoles
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
table:
|
|
||||||
name: user_authenticators
|
|
||||||
schema: auth
|
|
||||||
configuration:
|
|
||||||
column_config:
|
|
||||||
credential_id:
|
|
||||||
custom_name: credentialId
|
|
||||||
credential_public_key:
|
|
||||||
custom_name: credentialPublicKey
|
|
||||||
user_id:
|
|
||||||
custom_name: userId
|
|
||||||
custom_column_names:
|
|
||||||
credential_id: credentialId
|
|
||||||
credential_public_key: credentialPublicKey
|
|
||||||
user_id: userId
|
|
||||||
custom_name: authUserAuthenticators
|
|
||||||
custom_root_fields:
|
|
||||||
delete: deleteAuthUserAuthenticators
|
|
||||||
delete_by_pk: deleteAuthUserAuthenticator
|
|
||||||
insert: insertAuthUserAuthenticators
|
|
||||||
insert_one: insertAuthUserAuthenticator
|
|
||||||
select: authUserAuthenticators
|
|
||||||
select_aggregate: authUserAuthenticatorsAggregate
|
|
||||||
select_by_pk: authUserAuthenticator
|
|
||||||
update: updateAuthUserAuthenticators
|
|
||||||
update_by_pk: updateAuthUserAuthenticator
|
|
||||||
object_relationships:
|
|
||||||
- name: user
|
|
||||||
using:
|
|
||||||
foreign_key_constraint_on: user_id
|
|
||||||
@@ -7,6 +7,8 @@ configuration:
|
|||||||
custom_name: accessToken
|
custom_name: accessToken
|
||||||
created_at:
|
created_at:
|
||||||
custom_name: createdAt
|
custom_name: createdAt
|
||||||
|
id:
|
||||||
|
custom_name: id
|
||||||
provider_id:
|
provider_id:
|
||||||
custom_name: providerId
|
custom_name: providerId
|
||||||
provider_user_id:
|
provider_user_id:
|
||||||
@@ -20,6 +22,7 @@ configuration:
|
|||||||
custom_column_names:
|
custom_column_names:
|
||||||
access_token: accessToken
|
access_token: accessToken
|
||||||
created_at: createdAt
|
created_at: createdAt
|
||||||
|
id: id
|
||||||
provider_id: providerId
|
provider_id: providerId
|
||||||
provider_user_id: providerUserId
|
provider_user_id: providerUserId
|
||||||
refresh_token: refreshToken
|
refresh_token: refreshToken
|
||||||
|
|||||||
@@ -5,10 +5,16 @@ configuration:
|
|||||||
column_config:
|
column_config:
|
||||||
created_at:
|
created_at:
|
||||||
custom_name: createdAt
|
custom_name: createdAt
|
||||||
|
id:
|
||||||
|
custom_name: id
|
||||||
|
role:
|
||||||
|
custom_name: role
|
||||||
user_id:
|
user_id:
|
||||||
custom_name: userId
|
custom_name: userId
|
||||||
custom_column_names:
|
custom_column_names:
|
||||||
created_at: createdAt
|
created_at: createdAt
|
||||||
|
id: id
|
||||||
|
role: role
|
||||||
user_id: userId
|
user_id: userId
|
||||||
custom_name: authUserRoles
|
custom_name: authUserRoles
|
||||||
custom_root_fields:
|
custom_root_fields:
|
||||||
|
|||||||
@@ -11,14 +11,22 @@ configuration:
|
|||||||
custom_name: createdAt
|
custom_name: createdAt
|
||||||
default_role:
|
default_role:
|
||||||
custom_name: defaultRole
|
custom_name: defaultRole
|
||||||
|
disabled:
|
||||||
|
custom_name: disabled
|
||||||
display_name:
|
display_name:
|
||||||
custom_name: displayName
|
custom_name: displayName
|
||||||
|
email:
|
||||||
|
custom_name: email
|
||||||
email_verified:
|
email_verified:
|
||||||
custom_name: emailVerified
|
custom_name: emailVerified
|
||||||
|
id:
|
||||||
|
custom_name: id
|
||||||
is_anonymous:
|
is_anonymous:
|
||||||
custom_name: isAnonymous
|
custom_name: isAnonymous
|
||||||
last_seen:
|
last_seen:
|
||||||
custom_name: lastSeen
|
custom_name: lastSeen
|
||||||
|
locale:
|
||||||
|
custom_name: locale
|
||||||
new_email:
|
new_email:
|
||||||
custom_name: newEmail
|
custom_name: newEmail
|
||||||
otp_hash:
|
otp_hash:
|
||||||
@@ -33,6 +41,8 @@ configuration:
|
|||||||
custom_name: phoneNumber
|
custom_name: phoneNumber
|
||||||
phone_number_verified:
|
phone_number_verified:
|
||||||
custom_name: phoneNumberVerified
|
custom_name: phoneNumberVerified
|
||||||
|
ticket:
|
||||||
|
custom_name: ticket
|
||||||
ticket_expires_at:
|
ticket_expires_at:
|
||||||
custom_name: ticketExpiresAt
|
custom_name: ticketExpiresAt
|
||||||
totp_secret:
|
totp_secret:
|
||||||
@@ -46,10 +56,14 @@ configuration:
|
|||||||
avatar_url: avatarUrl
|
avatar_url: avatarUrl
|
||||||
created_at: createdAt
|
created_at: createdAt
|
||||||
default_role: defaultRole
|
default_role: defaultRole
|
||||||
|
disabled: disabled
|
||||||
display_name: displayName
|
display_name: displayName
|
||||||
|
email: email
|
||||||
email_verified: emailVerified
|
email_verified: emailVerified
|
||||||
|
id: id
|
||||||
is_anonymous: isAnonymous
|
is_anonymous: isAnonymous
|
||||||
last_seen: lastSeen
|
last_seen: lastSeen
|
||||||
|
locale: locale
|
||||||
new_email: newEmail
|
new_email: newEmail
|
||||||
otp_hash: otpHash
|
otp_hash: otpHash
|
||||||
otp_hash_expires_at: otpHashExpiresAt
|
otp_hash_expires_at: otpHashExpiresAt
|
||||||
@@ -57,6 +71,7 @@ configuration:
|
|||||||
password_hash: passwordHash
|
password_hash: passwordHash
|
||||||
phone_number: phoneNumber
|
phone_number: phoneNumber
|
||||||
phone_number_verified: phoneNumberVerified
|
phone_number_verified: phoneNumberVerified
|
||||||
|
ticket: ticket
|
||||||
ticket_expires_at: ticketExpiresAt
|
ticket_expires_at: ticketExpiresAt
|
||||||
totp_secret: totpSecret
|
totp_secret: totpSecret
|
||||||
updated_at: updatedAt
|
updated_at: updatedAt
|
||||||
@@ -77,13 +92,6 @@ object_relationships:
|
|||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: default_role
|
foreign_key_constraint_on: default_role
|
||||||
array_relationships:
|
array_relationships:
|
||||||
- name: authenticators
|
|
||||||
using:
|
|
||||||
foreign_key_constraint_on:
|
|
||||||
column: user_id
|
|
||||||
table:
|
|
||||||
name: user_authenticators
|
|
||||||
schema: auth
|
|
||||||
- name: refreshTokens
|
- name: refreshTokens
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
@@ -98,6 +106,13 @@ array_relationships:
|
|||||||
table:
|
table:
|
||||||
name: user_roles
|
name: user_roles
|
||||||
schema: auth
|
schema: auth
|
||||||
|
- name: securityKeys
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on:
|
||||||
|
column: user_id
|
||||||
|
table:
|
||||||
|
name: user_security_keys
|
||||||
|
schema: auth
|
||||||
- name: userProviders
|
- name: userProviders
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
@@ -106,12 +121,17 @@ array_relationships:
|
|||||||
name: user_providers
|
name: user_providers
|
||||||
schema: auth
|
schema: auth
|
||||||
select_permissions:
|
select_permissions:
|
||||||
|
- role: public
|
||||||
|
permission:
|
||||||
|
columns:
|
||||||
|
- display_name
|
||||||
|
- id
|
||||||
|
filter: {}
|
||||||
|
limit: 0
|
||||||
- role: user
|
- role: user
|
||||||
permission:
|
permission:
|
||||||
columns:
|
columns:
|
||||||
- avatar_url
|
|
||||||
- display_name
|
- display_name
|
||||||
- id
|
- id
|
||||||
filter:
|
filter: {}
|
||||||
id:
|
limit: 0
|
||||||
_eq: X-Hasura-User-Id
|
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
table:
|
|
||||||
name: customers
|
|
||||||
schema: public
|
|
||||||
insert_permissions:
|
|
||||||
- role: public
|
|
||||||
permission:
|
|
||||||
check: {}
|
|
||||||
columns:
|
|
||||||
- name
|
|
||||||
select_permissions:
|
|
||||||
- role: public
|
|
||||||
permission:
|
|
||||||
columns:
|
|
||||||
- id
|
|
||||||
- name
|
|
||||||
- created_at
|
|
||||||
filter: {}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
table:
|
|
||||||
name: doc_links
|
|
||||||
schema: public
|
|
||||||
configuration:
|
|
||||||
column_config:
|
|
||||||
created_at:
|
|
||||||
custom_name: createdAt
|
|
||||||
doc_id:
|
|
||||||
custom_name: docId
|
|
||||||
download_allowed:
|
|
||||||
custom_name: downloadAllowed
|
|
||||||
is_active:
|
|
||||||
custom_name: isActive
|
|
||||||
require_email_to_view:
|
|
||||||
custom_name: requireEmailToView
|
|
||||||
updated_at:
|
|
||||||
custom_name: updatedAt
|
|
||||||
custom_column_names:
|
|
||||||
created_at: createdAt
|
|
||||||
doc_id: docId
|
|
||||||
download_allowed: downloadAllowed
|
|
||||||
is_active: isActive
|
|
||||||
require_email_to_view: requireEmailToView
|
|
||||||
updated_at: updatedAt
|
|
||||||
custom_root_fields:
|
|
||||||
insert: insertDocLinks
|
|
||||||
insert_one: insertDocLink
|
|
||||||
select: docLinks
|
|
||||||
select_by_pk: docLink
|
|
||||||
object_relationships:
|
|
||||||
- name: doc
|
|
||||||
using:
|
|
||||||
foreign_key_constraint_on: doc_id
|
|
||||||
array_relationships:
|
|
||||||
- name: docVisits
|
|
||||||
using:
|
|
||||||
foreign_key_constraint_on:
|
|
||||||
column: doc_link_id
|
|
||||||
table:
|
|
||||||
name: doc_visits
|
|
||||||
schema: public
|
|
||||||
insert_permissions:
|
|
||||||
- permission:
|
|
||||||
check:
|
|
||||||
doc:
|
|
||||||
user_id:
|
|
||||||
_eq: X-Hasura-User-Id
|
|
||||||
columns:
|
|
||||||
- doc_id
|
|
||||||
- download_allowed
|
|
||||||
- is_active
|
|
||||||
- passcode
|
|
||||||
- require_email_to_view
|
|
||||||
role: user
|
|
||||||
select_permissions:
|
|
||||||
- permission:
|
|
||||||
columns:
|
|
||||||
- download_allowed
|
|
||||||
- id
|
|
||||||
- is_active
|
|
||||||
- passcode
|
|
||||||
- require_email_to_view
|
|
||||||
filter: {}
|
|
||||||
limit: 0
|
|
||||||
role: public
|
|
||||||
- permission:
|
|
||||||
columns:
|
|
||||||
- download_allowed
|
|
||||||
- is_active
|
|
||||||
- require_email_to_view
|
|
||||||
- passcode
|
|
||||||
- created_at
|
|
||||||
- updated_at
|
|
||||||
- doc_id
|
|
||||||
- id
|
|
||||||
filter:
|
|
||||||
doc:
|
|
||||||
user_id:
|
|
||||||
_eq: X-Hasura-User-Id
|
|
||||||
role: user
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
table:
|
|
||||||
name: doc_visits
|
|
||||||
schema: public
|
|
||||||
configuration:
|
|
||||||
column_config:
|
|
||||||
created_at:
|
|
||||||
custom_name: createdAt
|
|
||||||
doc_link_id:
|
|
||||||
custom_name: docLinkId
|
|
||||||
updated_at:
|
|
||||||
custom_name: updatedAt
|
|
||||||
custom_column_names:
|
|
||||||
created_at: createdAt
|
|
||||||
doc_link_id: docLinkId
|
|
||||||
updated_at: updatedAt
|
|
||||||
custom_root_fields: {}
|
|
||||||
object_relationships:
|
|
||||||
- name: docLink
|
|
||||||
using:
|
|
||||||
foreign_key_constraint_on: doc_link_id
|
|
||||||
select_permissions:
|
|
||||||
- permission:
|
|
||||||
allow_aggregations: true
|
|
||||||
columns:
|
|
||||||
- email
|
|
||||||
- created_at
|
|
||||||
- updated_at
|
|
||||||
- doc_link_id
|
|
||||||
- id
|
|
||||||
filter:
|
|
||||||
docLink:
|
|
||||||
doc:
|
|
||||||
user_id:
|
|
||||||
_eq: X-Hasura-User-Id
|
|
||||||
role: user
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
table:
|
|
||||||
name: docs
|
|
||||||
schema: public
|
|
||||||
configuration:
|
|
||||||
column_config:
|
|
||||||
created_at:
|
|
||||||
custom_name: createdAt
|
|
||||||
file_id:
|
|
||||||
custom_name: fileId
|
|
||||||
updated_at:
|
|
||||||
custom_name: updatedAt
|
|
||||||
user_id:
|
|
||||||
custom_name: userId
|
|
||||||
custom_column_names:
|
|
||||||
created_at: createdAt
|
|
||||||
file_id: fileId
|
|
||||||
updated_at: updatedAt
|
|
||||||
user_id: userId
|
|
||||||
custom_root_fields:
|
|
||||||
delete: deleteDocs
|
|
||||||
delete_by_pk: DeleteDoc
|
|
||||||
insert: insertDocs
|
|
||||||
insert_one: insertDoc
|
|
||||||
select: docs
|
|
||||||
select_aggregate: docsAggregate
|
|
||||||
select_by_pk: doc
|
|
||||||
update: updateDocs
|
|
||||||
update_by_pk: updateDoc
|
|
||||||
object_relationships:
|
|
||||||
- name: file
|
|
||||||
using:
|
|
||||||
foreign_key_constraint_on: file_id
|
|
||||||
- name: user
|
|
||||||
using:
|
|
||||||
foreign_key_constraint_on: user_id
|
|
||||||
array_relationships:
|
|
||||||
- name: docLinks
|
|
||||||
using:
|
|
||||||
foreign_key_constraint_on:
|
|
||||||
column: doc_id
|
|
||||||
table:
|
|
||||||
name: doc_links
|
|
||||||
schema: public
|
|
||||||
insert_permissions:
|
|
||||||
- permission:
|
|
||||||
check: {}
|
|
||||||
columns:
|
|
||||||
- file_id
|
|
||||||
- name
|
|
||||||
set:
|
|
||||||
user_id: x-hasura-user-id
|
|
||||||
role: user
|
|
||||||
select_permissions:
|
|
||||||
- permission:
|
|
||||||
columns:
|
|
||||||
- file_id
|
|
||||||
- id
|
|
||||||
filter: {}
|
|
||||||
limit: 0
|
|
||||||
role: public
|
|
||||||
- permission:
|
|
||||||
columns:
|
|
||||||
- name
|
|
||||||
- created_at
|
|
||||||
- updated_at
|
|
||||||
- file_id
|
|
||||||
- id
|
|
||||||
- user_id
|
|
||||||
filter:
|
|
||||||
user_id:
|
|
||||||
_eq: X-Hasura-User-Id
|
|
||||||
role: user
|
|
||||||
@@ -9,6 +9,8 @@ configuration:
|
|||||||
custom_name: createdAt
|
custom_name: createdAt
|
||||||
download_expiration:
|
download_expiration:
|
||||||
custom_name: downloadExpiration
|
custom_name: downloadExpiration
|
||||||
|
id:
|
||||||
|
custom_name: id
|
||||||
max_upload_file_size:
|
max_upload_file_size:
|
||||||
custom_name: maxUploadFileSize
|
custom_name: maxUploadFileSize
|
||||||
min_upload_file_size:
|
min_upload_file_size:
|
||||||
@@ -21,6 +23,7 @@ configuration:
|
|||||||
cache_control: cacheControl
|
cache_control: cacheControl
|
||||||
created_at: createdAt
|
created_at: createdAt
|
||||||
download_expiration: downloadExpiration
|
download_expiration: downloadExpiration
|
||||||
|
id: id
|
||||||
max_upload_file_size: maxUploadFileSize
|
max_upload_file_size: maxUploadFileSize
|
||||||
min_upload_file_size: minUploadFileSize
|
min_upload_file_size: minUploadFileSize
|
||||||
presigned_urls_enabled: presignedUrlsEnabled
|
presigned_urls_enabled: presignedUrlsEnabled
|
||||||
|
|||||||
@@ -9,10 +9,14 @@ configuration:
|
|||||||
custom_name: createdAt
|
custom_name: createdAt
|
||||||
etag:
|
etag:
|
||||||
custom_name: etag
|
custom_name: etag
|
||||||
|
id:
|
||||||
|
custom_name: id
|
||||||
is_uploaded:
|
is_uploaded:
|
||||||
custom_name: isUploaded
|
custom_name: isUploaded
|
||||||
mime_type:
|
mime_type:
|
||||||
custom_name: mimeType
|
custom_name: mimeType
|
||||||
|
name:
|
||||||
|
custom_name: name
|
||||||
size:
|
size:
|
||||||
custom_name: size
|
custom_name: size
|
||||||
updated_at:
|
updated_at:
|
||||||
@@ -23,8 +27,10 @@ configuration:
|
|||||||
bucket_id: bucketId
|
bucket_id: bucketId
|
||||||
created_at: createdAt
|
created_at: createdAt
|
||||||
etag: etag
|
etag: etag
|
||||||
|
id: id
|
||||||
is_uploaded: isUploaded
|
is_uploaded: isUploaded
|
||||||
mime_type: mimeType
|
mime_type: mimeType
|
||||||
|
name: name
|
||||||
size: size
|
size: size
|
||||||
updated_at: updatedAt
|
updated_at: updatedAt
|
||||||
uploaded_by_user_id: uploadedByUserId
|
uploaded_by_user_id: uploadedByUserId
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
- "!include auth_providers.yaml"
|
- "!include auth_providers.yaml"
|
||||||
- "!include auth_refresh_tokens.yaml"
|
- "!include auth_refresh_tokens.yaml"
|
||||||
- "!include auth_roles.yaml"
|
- "!include auth_roles.yaml"
|
||||||
- "!include auth_user_authenticators.yaml"
|
|
||||||
- "!include auth_user_providers.yaml"
|
- "!include auth_user_providers.yaml"
|
||||||
- "!include auth_user_roles.yaml"
|
- "!include auth_user_roles.yaml"
|
||||||
|
- "!include auth_user_security_keys.yaml"
|
||||||
- "!include auth_users.yaml"
|
- "!include auth_users.yaml"
|
||||||
- "!include public_customers.yaml"
|
- "!include public_tasks.yaml"
|
||||||
- "!include storage_buckets.yaml"
|
- "!include storage_buckets.yaml"
|
||||||
- "!include storage_files.yaml"
|
- "!include storage_files.yaml"
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
DROP TABLE "public"."customers";
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
CREATE TABLE "public"."customers" ("id" serial NOT NULL, "created_at" timestamptz NOT NULL DEFAULT now(), "name" text NOT NULL, PRIMARY KEY ("id") );
|
|
||||||
@@ -2,16 +2,8 @@
|
|||||||
"name": "@nhost-examples/codegen-react-apollo",
|
"name": "@nhost-examples/codegen-react-apollo",
|
||||||
"version": "0.1.5",
|
"version": "0.1.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
|
||||||
"@apollo/client": "^3.6.9",
|
|
||||||
"@nhost/react": "*",
|
|
||||||
"@nhost/react-apollo": "*",
|
|
||||||
"graphql": "15.7.2",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"codegen": "graphql-codegen --config graphql.config.yaml --errors-only",
|
"codegen": "graphql-codegen",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview --host localhost --port 3000"
|
"preview": "vite preview --host localhost --port 3000"
|
||||||
@@ -22,28 +14,28 @@
|
|||||||
"react-app/jest"
|
"react-app/jest"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"dependencies": {
|
||||||
"production": [
|
"@apollo/client": "^3.7.3",
|
||||||
">0.2%",
|
"@nhost/react": "*",
|
||||||
"not dead",
|
"@nhost/react-apollo": "*",
|
||||||
"not op_mini all"
|
"clsx": "^1.2.1",
|
||||||
],
|
"graphql": "15.7.2",
|
||||||
"development": [
|
"react": "^18.2.0",
|
||||||
"last 1 chrome version",
|
"react-dom": "^18.2.0"
|
||||||
"last 1 firefox version",
|
|
||||||
"last 1 safari version"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "^2.12.0",
|
"@graphql-codegen/cli": "^2.14.1",
|
||||||
"@graphql-codegen/typescript-operations": "^2.5.3",
|
"@graphql-codegen/client-preset": "^1.1.5",
|
||||||
"@graphql-codegen/typescript-react-apollo": "^3.3.3",
|
"@graphql-typed-document-node/core": "^3.1.1",
|
||||||
"@types/node": "^16.11.7",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
|
"@types/node": "^18.11.9",
|
||||||
"@types/react": "^18.0.25",
|
"@types/react": "^18.0.25",
|
||||||
"@types/react-dom": "^18.0.9",
|
"@types/react-dom": "^18.0.9",
|
||||||
"eslint": "^8.23.0",
|
"@vitejs/plugin-react": "^3.0.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"autoprefixer": "^10.4.13",
|
||||||
"typescript": "^4.8.2",
|
"postcss": "^8.4.19",
|
||||||
|
"tailwindcss": "^3.2.1",
|
||||||
|
"typescript": "^4.6.4",
|
||||||
"vite": "^4.0.2"
|
"vite": "^4.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,40 @@
|
|||||||
import { NhostProvider } from '@nhost/react'
|
import React from 'react'
|
||||||
|
|
||||||
|
import { NhostProvider, SignedIn, SignedOut } from '@nhost/react'
|
||||||
import { NhostApolloProvider } from '@nhost/react-apollo'
|
import { NhostApolloProvider } from '@nhost/react-apollo'
|
||||||
import { Customers } from './components/customers'
|
|
||||||
import { NewCustomer } from './components/new-customer'
|
import { Tasks } from './components/Tasks'
|
||||||
|
import { SignIn } from './components/SignIn'
|
||||||
import { nhost } from './utils/nhost'
|
import { nhost } from './utils/nhost'
|
||||||
|
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<NhostProvider nhost={nhost}>
|
<NhostProvider nhost={nhost}>
|
||||||
<NhostApolloProvider nhost={nhost}>
|
<NhostApolloProvider nhost={nhost}>
|
||||||
<div>
|
<SignedIn>
|
||||||
<h1>GraphQL Code Generator example with React and Apollo</h1>
|
<div className="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8 mt-6 text-gray-200">
|
||||||
<div>
|
<div className="flex justify-between pb-4 mb-4 border-b border-gray-700">
|
||||||
<NewCustomer />
|
<div className="uppercase font-semibold">Todo App</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={() => nhost.auth.signOut()}
|
||||||
|
type="submit"
|
||||||
|
className="rounded-sm border border-transparent px-3 py-2 text-sm font-medium leading-4 bg-slate-100 hover:bg-slate-200 text-gray-800 shadow-sm hover:focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 w-full "
|
||||||
|
>
|
||||||
|
Sign Out
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Tasks />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</SignedIn>
|
||||||
<Customers />
|
<SignedOut>
|
||||||
|
<div className="h-full">
|
||||||
|
<SignIn />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</SignedOut>
|
||||||
</NhostApolloProvider>
|
</NhostApolloProvider>
|
||||||
</NhostProvider>
|
</NhostProvider>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function SignIn() {
|
|||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="rounded-sm border border-transparent bg-blue-600 px-3 py-2 text-sm font-medium leading-4 text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 w-full "
|
className="rounded-sm border border-transparent px-3 py-2 text-sm font-medium leading-4 bg-slate-100 hover:bg-slate-200 text-gray-800 shadow-sm hover:focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 w-full "
|
||||||
>
|
>
|
||||||
Send Magic Link
|
Send Magic Link
|
||||||
</button>
|
</button>
|
||||||
165
examples/codegen-react-apollo/src/components/Tasks.tsx
Normal file
165
examples/codegen-react-apollo/src/components/Tasks.tsx
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import clsx from 'clsx'
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { useMutation, useQuery } from '@apollo/client'
|
||||||
|
|
||||||
|
import { graphql } from '../gql/gql'
|
||||||
|
|
||||||
|
const GET_TASKS = graphql(`
|
||||||
|
query GetTasks {
|
||||||
|
tasks(order_by: { createdAt: desc }) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
const INSERT_TASK = graphql(`
|
||||||
|
mutation InsertTask($task: tasks_insert_input!) {
|
||||||
|
insertTasks(objects: [$task]) {
|
||||||
|
affected_rows
|
||||||
|
returning {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
const UPDATE_TASK = graphql(`
|
||||||
|
mutation UpdateTask($id: uuid!, $task: tasks_set_input!) {
|
||||||
|
updateTask(pk_columns: { id: $id }, _set: $task) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
const DELETE_TASK = graphql(`
|
||||||
|
mutation DeleteTask($id: uuid!) {
|
||||||
|
deleteTask(id: $id) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
export function Tasks() {
|
||||||
|
const [name, setName] = useState('')
|
||||||
|
|
||||||
|
const { data, loading } = useQuery(GET_TASKS)
|
||||||
|
|
||||||
|
const [insertTask, { loading: insertPostIsLoading }] = useMutation(INSERT_TASK)
|
||||||
|
|
||||||
|
const [deleteTask] = useMutation(DELETE_TASK)
|
||||||
|
const [updateTask] = useMutation(UPDATE_TASK)
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await insertTask({
|
||||||
|
variables: {
|
||||||
|
task: {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
refetchQueries: ['GetTasks']
|
||||||
|
})
|
||||||
|
|
||||||
|
setName('')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <div>Loading...</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return <div>No data</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tasks } = data
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-5">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
|
||||||
|
Todo
|
||||||
|
</label>
|
||||||
|
<div className="mt-1">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Todo"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
className="bg-gray-800 border-gray-600 text-gray-100 block w-full rounded-sm shadow-sm focus:shadow-md sm:text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="rounded-sm border border-transparent px-3 py-2 text-sm font-medium leading-4 bg-slate-100 hover:bg-slate-200 text-gray-800 shadow-sm hover:focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 w-full "
|
||||||
|
disabled={insertPostIsLoading}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{tasks.map((task) => {
|
||||||
|
const style = clsx('p-2', {
|
||||||
|
'line-through text-green-600': task.done
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={task.id}
|
||||||
|
className="flex justify-between transition-all duration-300 ease-in-out"
|
||||||
|
>
|
||||||
|
<div className={style}>{task.name}</div>
|
||||||
|
<div className="flex space-x-4">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
updateTask({
|
||||||
|
variables: {
|
||||||
|
id: task.id,
|
||||||
|
task: {
|
||||||
|
done: !task.done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
className="cursor-pointer p-2 hover:bg-gray-900 rounded-sm transition-all duration-100 ease-in-out"
|
||||||
|
>
|
||||||
|
{task.done ? 'Not Done' : 'Done'}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
deleteTask({
|
||||||
|
variables: {
|
||||||
|
id: task.id
|
||||||
|
},
|
||||||
|
refetchQueries: ['GetTasks']
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
className="cursor-pointer p-2 hover:bg-gray-900 rounded-sm transition-all duration-100 ease-in-out"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { useGetCustomersQuery } from '../utils/__generated__/graphql'
|
|
||||||
|
|
||||||
export function Customers() {
|
|
||||||
const { data, loading, error } = useGetCustomersQuery()
|
|
||||||
|
|
||||||
if (loading || !data) {
|
|
||||||
return <div>Loading</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return <div>Error</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
const { customers } = data
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h2>Customers</h2>
|
|
||||||
<ul>
|
|
||||||
{customers.map((customer) => (
|
|
||||||
<li key={customer.id}>{customer.name}</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import { useState } from 'react'
|
|
||||||
import { refetchGetCustomersQuery, useInsertCustomerMutation } from '../utils/__generated__/graphql'
|
|
||||||
|
|
||||||
export function NewCustomer() {
|
|
||||||
const [name, setName] = useState('')
|
|
||||||
|
|
||||||
const [insertCustomer, { loading, error }] = useInsertCustomerMutation({
|
|
||||||
refetchQueries: [refetchGetCustomersQuery()]
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
try {
|
|
||||||
await insertCustomer({
|
|
||||||
variables: {
|
|
||||||
customer: {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
return console.error(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
setName('')
|
|
||||||
|
|
||||||
alert('Customer added!')
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h2>New Customer</h2>
|
|
||||||
<div>
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="name"
|
|
||||||
placeholder="Name"
|
|
||||||
value={name}
|
|
||||||
onChange={(e) => setName(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{error && <div>Error: {error.message}</div>}
|
|
||||||
<div>
|
|
||||||
<button type="submit" disabled={loading}>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
query GetCustomers {
|
|
||||||
customers {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mutation InsertCustomer($customer: customers_insert_input!) {
|
|
||||||
insert_customers_one(object: $customer) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,7 @@
|
|||||||
body {
|
html {
|
||||||
margin: 0;
|
background: #000;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
@tailwind base;
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
@tailwind components;
|
||||||
monospace;
|
@tailwind utilities;
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import ReactDOM from 'react-dom/client'
|
|
||||||
import App from './App'
|
|
||||||
import './index.css?inline'
|
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
|
|
||||||
root.render(
|
|
||||||
<React.StrictMode>
|
|
||||||
<App />
|
|
||||||
</React.StrictMode>
|
|
||||||
)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.6 KiB |
@@ -2,6 +2,4 @@ import ReactDOM from 'react-dom/client'
|
|||||||
|
|
||||||
import App from './App'
|
import App from './App'
|
||||||
|
|
||||||
import './index.css?inline'
|
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />)
|
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />)
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { NhostClient } from '@nhost/react'
|
import { NhostClient } from '@nhost/react'
|
||||||
|
|
||||||
const nhost = new NhostClient({
|
const nhost = new NhostClient({
|
||||||
subdomain: 'localhost:1337'
|
subdomain: import.meta.env.VITE_NHOST_SUBDOMAIN || 'localhost',
|
||||||
|
region: import.meta.env.VITE_NHOST_REGION
|
||||||
})
|
})
|
||||||
|
|
||||||
export { nhost }
|
export { nhost }
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ import react from '@vitejs/plugin-react'
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: ['react/jsx-runtime'],
|
include: ['react/jsx-runtime'],
|
||||||
// * Shim: do not optimize @nhost/react when running this example in the Nhost monorepo
|
exclude: ['@nhost/react']
|
||||||
exclude: process.env.PNPM_PACKAGE_NAME === 'nhost-root' ? ['@nhost/react'] : []
|
|
||||||
},
|
},
|
||||||
plugins: [react()]
|
plugins: [react()]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
# GraphQL Code Generator Example with React and React Query
|
# GraphQL Code Generator Example with React and React Query
|
||||||
|
|
||||||
This is an example repo for how to use GraphQL Code Generator together with:
|
Todo app to show how to use:
|
||||||
|
|
||||||
- [TypeScript](https://www.typescriptlang.org/)
|
- [Nhost](https://nhost.io/)
|
||||||
- [React](https://reactjs.org/)
|
- [React](https://reactjs.org/)
|
||||||
|
- [TypeScript](https://www.typescriptlang.org/)
|
||||||
|
- [GraphQL Code Generator](https://the-guild.dev/graphql/codegen)
|
||||||
- [React Query](https://tanstack.com/query/v4/)
|
- [React Query](https://tanstack.com/query/v4/)
|
||||||
- [Nhost](http://nhost.io/)
|
|
||||||
|
|
||||||
This repo is a reference repo for the blog post: [How to use GraphQL Code Generator with React and React Query](https://nhost.io/blog/how-to-use-graphql-code-generator-with-react-query).
|
This repo is a reference repo for the blog post: [How to use GraphQL Code Generator with React and React Query](https://nhost.io/blog/how-to-use-graphql-code-generator-with-react-query).
|
||||||
|
|
||||||
@@ -13,42 +14,42 @@ This repo is a reference repo for the blog post: [How to use GraphQL Code Genera
|
|||||||
|
|
||||||
1. Clone the repository
|
1. Clone the repository
|
||||||
|
|
||||||
```
|
```sh
|
||||||
git clone https://github.com/nhost/nhost
|
git clone https://github.com/nhost/nhost
|
||||||
cd nhost
|
cd nhost
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install and build dependencies
|
2. Install and build dependencies
|
||||||
|
|
||||||
```
|
```sh
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm build
|
pnpm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Go to the Codegen React Query example folder
|
3. Go to the example folder
|
||||||
|
|
||||||
```
|
```sh
|
||||||
cd examples/codegen-react-query
|
cd examples/codegen-react-query
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Terminal 1: Start Nhost
|
4. Terminal 1: Start Nhost
|
||||||
|
|
||||||
> Make sure you have the [Nhost CLI installed](https://docs.nhost.io/platform/cli).
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nhost up
|
nhost up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Terminal 2: Run GraphQL Codegen
|
5. Terminal 2: Start the React application
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## GraphQL Code Generators
|
||||||
|
|
||||||
|
To re-run the GraphQL Code Generators, run the following:
|
||||||
|
|
||||||
```
|
```
|
||||||
pnpm codegen -w
|
pnpm codegen -w
|
||||||
```
|
```
|
||||||
|
|
||||||
> `-w` runs [codegen in watch mode](https://www.the-guild.dev/graphql/codegen/docs/getting-started/development-workflow#watch-mode).
|
> `-w` runs [codegen in watch mode](https://www.the-guild.dev/graphql/codegen/docs/getting-started/development-workflow#watch-mode).
|
||||||
|
|
||||||
6. Terminal 3: Start the React application
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pnpm dev
|
|
||||||
```
|
|
||||||
|
|||||||
23
examples/codegen-react-query/codegen.ts
Normal file
23
examples/codegen-react-query/codegen.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { CodegenConfig } from '@graphql-codegen/cli'
|
||||||
|
|
||||||
|
const config: CodegenConfig = {
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
'http://localhost:1337/v1/graphql': {
|
||||||
|
headers: {
|
||||||
|
'x-hasura-admin-secret': 'nhost-admin-secret'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
ignoreNoDocuments: true, // for better experience with the watcher
|
||||||
|
generates: {
|
||||||
|
'./src/gql/': {
|
||||||
|
documents: ['src/**/*.tsx'],
|
||||||
|
preset: 'client',
|
||||||
|
plugins: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config
|
||||||
@@ -2,15 +2,3 @@ schema:
|
|||||||
- http://localhost:1337/v1/graphql:
|
- http://localhost:1337/v1/graphql:
|
||||||
headers:
|
headers:
|
||||||
x-hasura-admin-secret: nhost-admin-secret
|
x-hasura-admin-secret: nhost-admin-secret
|
||||||
generates:
|
|
||||||
src/utils/__generated__/graphql.ts:
|
|
||||||
documents:
|
|
||||||
- 'src/**/*.graphql'
|
|
||||||
plugins:
|
|
||||||
- 'typescript'
|
|
||||||
- 'typescript-operations'
|
|
||||||
- 'typescript-react-query'
|
|
||||||
config:
|
|
||||||
fetcher:
|
|
||||||
func: '../graphql-fetcher#fetchData'
|
|
||||||
isReactHook: false
|
|
||||||
|
|||||||
@@ -9,6 +9,6 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/index.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
metadata_directory: metadata
|
metadata_directory: metadata
|
||||||
services:
|
services:
|
||||||
hasura:
|
hasura:
|
||||||
image: hasura/graphql-engine:v2.15.2
|
|
||||||
environment:
|
environment:
|
||||||
hasura_graphql_enable_remote_schema_permissions: false
|
hasura_graphql_enable_remote_schema_permissions: false
|
||||||
|
image: hasura/graphql-engine:v2.15.2
|
||||||
|
minio:
|
||||||
|
environment:
|
||||||
|
minio_root_password: minioaccesskey123123
|
||||||
|
minio_root_user: minioaccesskey123123
|
||||||
|
postgres:
|
||||||
|
environment:
|
||||||
|
postgres_password: postgres
|
||||||
|
postgres_user: postgres
|
||||||
auth:
|
auth:
|
||||||
image: nhost/hasura-auth:0.16.2
|
image: nhost/hasura-auth:0.16.1
|
||||||
storage:
|
storage:
|
||||||
image: nhost/hasura-storage:0.3.0
|
image: nhost/hasura-storage:0.3.0
|
||||||
auth:
|
auth:
|
||||||
|
webauthn:
|
||||||
|
enabled: true
|
||||||
|
rp_name: URQL
|
||||||
access_control:
|
access_control:
|
||||||
email:
|
email:
|
||||||
allowed_email_domains: ''
|
allowed_email_domains: ''
|
||||||
@@ -18,13 +29,13 @@ auth:
|
|||||||
url:
|
url:
|
||||||
allowed_redirect_urls: ''
|
allowed_redirect_urls: ''
|
||||||
anonymous_users_enabled: false
|
anonymous_users_enabled: false
|
||||||
client_url: http://localhost:3000
|
client_url: http://localhost:5173
|
||||||
disable_new_users: false
|
disable_new_users: false
|
||||||
email:
|
email:
|
||||||
enabled: false
|
enabled: false
|
||||||
passwordless:
|
passwordless:
|
||||||
enabled: false
|
enabled: true
|
||||||
signin_email_verified_required: true
|
signin_email_verified_required: false
|
||||||
template_fetch_url: ''
|
template_fetch_url: ''
|
||||||
gravatar:
|
gravatar:
|
||||||
default: ''
|
default: ''
|
||||||
@@ -117,11 +128,7 @@ auth:
|
|||||||
secure: false
|
secure: false
|
||||||
sender: hasura-auth@example.com
|
sender: hasura-auth@example.com
|
||||||
user: user
|
user: user
|
||||||
token:
|
access_token_expires_in: 315
|
||||||
access:
|
|
||||||
expires_in: 900
|
|
||||||
refresh:
|
|
||||||
expires_in: 43200
|
|
||||||
user:
|
user:
|
||||||
allowed_roles: user,me
|
allowed_roles: user,me
|
||||||
default_allowed_roles: user,me
|
default_allowed_roles: user,me
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Your code is ${code}.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Votre code est ${code}.
|
||||||
@@ -9,6 +9,6 @@
|
|||||||
connection_lifetime: 600
|
connection_lifetime: 600
|
||||||
idle_timeout: 180
|
idle_timeout: 180
|
||||||
max_connections: 50
|
max_connections: 50
|
||||||
retries: 1
|
retries: 20
|
||||||
use_prepared_statements: true
|
use_prepared_statements: true
|
||||||
tables: "!include default/tables/tables.yaml"
|
tables: "!include default/tables/tables.yaml"
|
||||||
|
|||||||
@@ -2,8 +2,14 @@ table:
|
|||||||
name: provider_requests
|
name: provider_requests
|
||||||
schema: auth
|
schema: auth
|
||||||
configuration:
|
configuration:
|
||||||
column_config: {}
|
column_config:
|
||||||
custom_column_names: {}
|
id:
|
||||||
|
custom_name: id
|
||||||
|
options:
|
||||||
|
custom_name: options
|
||||||
|
custom_column_names:
|
||||||
|
id: id
|
||||||
|
options: options
|
||||||
custom_name: authProviderRequests
|
custom_name: authProviderRequests
|
||||||
custom_root_fields:
|
custom_root_fields:
|
||||||
delete: deleteAuthProviderRequests
|
delete: deleteAuthProviderRequests
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ table:
|
|||||||
name: providers
|
name: providers
|
||||||
schema: auth
|
schema: auth
|
||||||
configuration:
|
configuration:
|
||||||
column_config: {}
|
column_config:
|
||||||
custom_column_names: {}
|
id:
|
||||||
|
custom_name: id
|
||||||
|
custom_column_names:
|
||||||
|
id: id
|
||||||
custom_name: authProviders
|
custom_name: authProviders
|
||||||
custom_root_fields:
|
custom_root_fields:
|
||||||
delete: deleteAuthProviders
|
delete: deleteAuthProviders
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ table:
|
|||||||
name: roles
|
name: roles
|
||||||
schema: auth
|
schema: auth
|
||||||
configuration:
|
configuration:
|
||||||
column_config: {}
|
column_config:
|
||||||
custom_column_names: {}
|
role:
|
||||||
|
custom_name: role
|
||||||
|
custom_column_names:
|
||||||
|
role: role
|
||||||
custom_name: authRoles
|
custom_name: authRoles
|
||||||
custom_root_fields:
|
custom_root_fields:
|
||||||
delete: deleteAuthRoles
|
delete: deleteAuthRoles
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
table:
|
|
||||||
name: user_authenticators
|
|
||||||
schema: auth
|
|
||||||
configuration:
|
|
||||||
column_config:
|
|
||||||
credential_id:
|
|
||||||
custom_name: credentialId
|
|
||||||
credential_public_key:
|
|
||||||
custom_name: credentialPublicKey
|
|
||||||
user_id:
|
|
||||||
custom_name: userId
|
|
||||||
custom_column_names:
|
|
||||||
credential_id: credentialId
|
|
||||||
credential_public_key: credentialPublicKey
|
|
||||||
user_id: userId
|
|
||||||
custom_name: authUserAuthenticators
|
|
||||||
custom_root_fields:
|
|
||||||
delete: deleteAuthUserAuthenticators
|
|
||||||
delete_by_pk: deleteAuthUserAuthenticator
|
|
||||||
insert: insertAuthUserAuthenticators
|
|
||||||
insert_one: insertAuthUserAuthenticator
|
|
||||||
select: authUserAuthenticators
|
|
||||||
select_aggregate: authUserAuthenticatorsAggregate
|
|
||||||
select_by_pk: authUserAuthenticator
|
|
||||||
update: updateAuthUserAuthenticators
|
|
||||||
update_by_pk: updateAuthUserAuthenticator
|
|
||||||
object_relationships:
|
|
||||||
- name: user
|
|
||||||
using:
|
|
||||||
foreign_key_constraint_on: user_id
|
|
||||||
@@ -7,6 +7,8 @@ configuration:
|
|||||||
custom_name: accessToken
|
custom_name: accessToken
|
||||||
created_at:
|
created_at:
|
||||||
custom_name: createdAt
|
custom_name: createdAt
|
||||||
|
id:
|
||||||
|
custom_name: id
|
||||||
provider_id:
|
provider_id:
|
||||||
custom_name: providerId
|
custom_name: providerId
|
||||||
provider_user_id:
|
provider_user_id:
|
||||||
@@ -20,6 +22,7 @@ configuration:
|
|||||||
custom_column_names:
|
custom_column_names:
|
||||||
access_token: accessToken
|
access_token: accessToken
|
||||||
created_at: createdAt
|
created_at: createdAt
|
||||||
|
id: id
|
||||||
provider_id: providerId
|
provider_id: providerId
|
||||||
provider_user_id: providerUserId
|
provider_user_id: providerUserId
|
||||||
refresh_token: refreshToken
|
refresh_token: refreshToken
|
||||||
|
|||||||
@@ -5,10 +5,16 @@ configuration:
|
|||||||
column_config:
|
column_config:
|
||||||
created_at:
|
created_at:
|
||||||
custom_name: createdAt
|
custom_name: createdAt
|
||||||
|
id:
|
||||||
|
custom_name: id
|
||||||
|
role:
|
||||||
|
custom_name: role
|
||||||
user_id:
|
user_id:
|
||||||
custom_name: userId
|
custom_name: userId
|
||||||
custom_column_names:
|
custom_column_names:
|
||||||
created_at: createdAt
|
created_at: createdAt
|
||||||
|
id: id
|
||||||
|
role: role
|
||||||
user_id: userId
|
user_id: userId
|
||||||
custom_name: authUserRoles
|
custom_name: authUserRoles
|
||||||
custom_root_fields:
|
custom_root_fields:
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
table:
|
||||||
|
name: user_security_keys
|
||||||
|
schema: auth
|
||||||
|
configuration:
|
||||||
|
column_config:
|
||||||
|
credential_id:
|
||||||
|
custom_name: credentialId
|
||||||
|
credential_public_key:
|
||||||
|
custom_name: credentialPublicKey
|
||||||
|
id:
|
||||||
|
custom_name: id
|
||||||
|
user_id:
|
||||||
|
custom_name: userId
|
||||||
|
custom_column_names:
|
||||||
|
credential_id: credentialId
|
||||||
|
credential_public_key: credentialPublicKey
|
||||||
|
id: id
|
||||||
|
user_id: userId
|
||||||
|
custom_name: authUserSecurityKeys
|
||||||
|
custom_root_fields:
|
||||||
|
delete: deleteAuthUserSecurityKeys
|
||||||
|
delete_by_pk: deleteAuthUserSecurityKey
|
||||||
|
insert: insertAuthUserSecurityKeys
|
||||||
|
insert_one: insertAuthUserSecurityKey
|
||||||
|
select: authUserSecurityKeys
|
||||||
|
select_aggregate: authUserSecurityKeysAggregate
|
||||||
|
select_by_pk: authUserSecurityKey
|
||||||
|
update: updateAuthUserSecurityKeys
|
||||||
|
update_by_pk: updateAuthUserSecurityKey
|
||||||
|
object_relationships:
|
||||||
|
- name: user
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on: user_id
|
||||||
@@ -11,14 +11,22 @@ configuration:
|
|||||||
custom_name: createdAt
|
custom_name: createdAt
|
||||||
default_role:
|
default_role:
|
||||||
custom_name: defaultRole
|
custom_name: defaultRole
|
||||||
|
disabled:
|
||||||
|
custom_name: disabled
|
||||||
display_name:
|
display_name:
|
||||||
custom_name: displayName
|
custom_name: displayName
|
||||||
|
email:
|
||||||
|
custom_name: email
|
||||||
email_verified:
|
email_verified:
|
||||||
custom_name: emailVerified
|
custom_name: emailVerified
|
||||||
|
id:
|
||||||
|
custom_name: id
|
||||||
is_anonymous:
|
is_anonymous:
|
||||||
custom_name: isAnonymous
|
custom_name: isAnonymous
|
||||||
last_seen:
|
last_seen:
|
||||||
custom_name: lastSeen
|
custom_name: lastSeen
|
||||||
|
locale:
|
||||||
|
custom_name: locale
|
||||||
new_email:
|
new_email:
|
||||||
custom_name: newEmail
|
custom_name: newEmail
|
||||||
otp_hash:
|
otp_hash:
|
||||||
@@ -33,6 +41,8 @@ configuration:
|
|||||||
custom_name: phoneNumber
|
custom_name: phoneNumber
|
||||||
phone_number_verified:
|
phone_number_verified:
|
||||||
custom_name: phoneNumberVerified
|
custom_name: phoneNumberVerified
|
||||||
|
ticket:
|
||||||
|
custom_name: ticket
|
||||||
ticket_expires_at:
|
ticket_expires_at:
|
||||||
custom_name: ticketExpiresAt
|
custom_name: ticketExpiresAt
|
||||||
totp_secret:
|
totp_secret:
|
||||||
@@ -46,10 +56,14 @@ configuration:
|
|||||||
avatar_url: avatarUrl
|
avatar_url: avatarUrl
|
||||||
created_at: createdAt
|
created_at: createdAt
|
||||||
default_role: defaultRole
|
default_role: defaultRole
|
||||||
|
disabled: disabled
|
||||||
display_name: displayName
|
display_name: displayName
|
||||||
|
email: email
|
||||||
email_verified: emailVerified
|
email_verified: emailVerified
|
||||||
|
id: id
|
||||||
is_anonymous: isAnonymous
|
is_anonymous: isAnonymous
|
||||||
last_seen: lastSeen
|
last_seen: lastSeen
|
||||||
|
locale: locale
|
||||||
new_email: newEmail
|
new_email: newEmail
|
||||||
otp_hash: otpHash
|
otp_hash: otpHash
|
||||||
otp_hash_expires_at: otpHashExpiresAt
|
otp_hash_expires_at: otpHashExpiresAt
|
||||||
@@ -57,6 +71,7 @@ configuration:
|
|||||||
password_hash: passwordHash
|
password_hash: passwordHash
|
||||||
phone_number: phoneNumber
|
phone_number: phoneNumber
|
||||||
phone_number_verified: phoneNumberVerified
|
phone_number_verified: phoneNumberVerified
|
||||||
|
ticket: ticket
|
||||||
ticket_expires_at: ticketExpiresAt
|
ticket_expires_at: ticketExpiresAt
|
||||||
totp_secret: totpSecret
|
totp_secret: totpSecret
|
||||||
updated_at: updatedAt
|
updated_at: updatedAt
|
||||||
@@ -77,13 +92,6 @@ object_relationships:
|
|||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: default_role
|
foreign_key_constraint_on: default_role
|
||||||
array_relationships:
|
array_relationships:
|
||||||
- name: authenticators
|
|
||||||
using:
|
|
||||||
foreign_key_constraint_on:
|
|
||||||
column: user_id
|
|
||||||
table:
|
|
||||||
name: user_authenticators
|
|
||||||
schema: auth
|
|
||||||
- name: refreshTokens
|
- name: refreshTokens
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
@@ -98,6 +106,13 @@ array_relationships:
|
|||||||
table:
|
table:
|
||||||
name: user_roles
|
name: user_roles
|
||||||
schema: auth
|
schema: auth
|
||||||
|
- name: securityKeys
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on:
|
||||||
|
column: user_id
|
||||||
|
table:
|
||||||
|
name: user_security_keys
|
||||||
|
schema: auth
|
||||||
- name: userProviders
|
- name: userProviders
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
@@ -106,12 +121,17 @@ array_relationships:
|
|||||||
name: user_providers
|
name: user_providers
|
||||||
schema: auth
|
schema: auth
|
||||||
select_permissions:
|
select_permissions:
|
||||||
|
- role: public
|
||||||
|
permission:
|
||||||
|
columns:
|
||||||
|
- display_name
|
||||||
|
- id
|
||||||
|
filter: {}
|
||||||
|
limit: 0
|
||||||
- role: user
|
- role: user
|
||||||
permission:
|
permission:
|
||||||
columns:
|
columns:
|
||||||
- avatar_url
|
|
||||||
- display_name
|
- display_name
|
||||||
- id
|
- id
|
||||||
filter:
|
filter: {}
|
||||||
id:
|
limit: 0
|
||||||
_eq: X-Hasura-User-Id
|
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
table:
|
|
||||||
name: customers
|
|
||||||
schema: public
|
|
||||||
insert_permissions:
|
|
||||||
- role: public
|
|
||||||
permission:
|
|
||||||
check: {}
|
|
||||||
columns:
|
|
||||||
- name
|
|
||||||
select_permissions:
|
|
||||||
- role: public
|
|
||||||
permission:
|
|
||||||
columns:
|
|
||||||
- id
|
|
||||||
- name
|
|
||||||
- created_at
|
|
||||||
filter: {}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
table:
|
|
||||||
name: doc_links
|
|
||||||
schema: public
|
|
||||||
configuration:
|
|
||||||
column_config:
|
|
||||||
created_at:
|
|
||||||
custom_name: createdAt
|
|
||||||
doc_id:
|
|
||||||
custom_name: docId
|
|
||||||
download_allowed:
|
|
||||||
custom_name: downloadAllowed
|
|
||||||
is_active:
|
|
||||||
custom_name: isActive
|
|
||||||
require_email_to_view:
|
|
||||||
custom_name: requireEmailToView
|
|
||||||
updated_at:
|
|
||||||
custom_name: updatedAt
|
|
||||||
custom_column_names:
|
|
||||||
created_at: createdAt
|
|
||||||
doc_id: docId
|
|
||||||
download_allowed: downloadAllowed
|
|
||||||
is_active: isActive
|
|
||||||
require_email_to_view: requireEmailToView
|
|
||||||
updated_at: updatedAt
|
|
||||||
custom_root_fields:
|
|
||||||
insert: insertDocLinks
|
|
||||||
insert_one: insertDocLink
|
|
||||||
select: docLinks
|
|
||||||
select_by_pk: docLink
|
|
||||||
object_relationships:
|
|
||||||
- name: doc
|
|
||||||
using:
|
|
||||||
foreign_key_constraint_on: doc_id
|
|
||||||
array_relationships:
|
|
||||||
- name: docVisits
|
|
||||||
using:
|
|
||||||
foreign_key_constraint_on:
|
|
||||||
column: doc_link_id
|
|
||||||
table:
|
|
||||||
name: doc_visits
|
|
||||||
schema: public
|
|
||||||
insert_permissions:
|
|
||||||
- permission:
|
|
||||||
check:
|
|
||||||
doc:
|
|
||||||
user_id:
|
|
||||||
_eq: X-Hasura-User-Id
|
|
||||||
columns:
|
|
||||||
- doc_id
|
|
||||||
- download_allowed
|
|
||||||
- is_active
|
|
||||||
- passcode
|
|
||||||
- require_email_to_view
|
|
||||||
role: user
|
|
||||||
select_permissions:
|
|
||||||
- permission:
|
|
||||||
columns:
|
|
||||||
- download_allowed
|
|
||||||
- id
|
|
||||||
- is_active
|
|
||||||
- passcode
|
|
||||||
- require_email_to_view
|
|
||||||
filter: {}
|
|
||||||
limit: 0
|
|
||||||
role: public
|
|
||||||
- permission:
|
|
||||||
columns:
|
|
||||||
- download_allowed
|
|
||||||
- is_active
|
|
||||||
- require_email_to_view
|
|
||||||
- passcode
|
|
||||||
- created_at
|
|
||||||
- updated_at
|
|
||||||
- doc_id
|
|
||||||
- id
|
|
||||||
filter:
|
|
||||||
doc:
|
|
||||||
user_id:
|
|
||||||
_eq: X-Hasura-User-Id
|
|
||||||
role: user
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
table:
|
|
||||||
name: doc_visits
|
|
||||||
schema: public
|
|
||||||
configuration:
|
|
||||||
column_config:
|
|
||||||
created_at:
|
|
||||||
custom_name: createdAt
|
|
||||||
doc_link_id:
|
|
||||||
custom_name: docLinkId
|
|
||||||
updated_at:
|
|
||||||
custom_name: updatedAt
|
|
||||||
custom_column_names:
|
|
||||||
created_at: createdAt
|
|
||||||
doc_link_id: docLinkId
|
|
||||||
updated_at: updatedAt
|
|
||||||
custom_root_fields: {}
|
|
||||||
object_relationships:
|
|
||||||
- name: docLink
|
|
||||||
using:
|
|
||||||
foreign_key_constraint_on: doc_link_id
|
|
||||||
select_permissions:
|
|
||||||
- permission:
|
|
||||||
allow_aggregations: true
|
|
||||||
columns:
|
|
||||||
- email
|
|
||||||
- created_at
|
|
||||||
- updated_at
|
|
||||||
- doc_link_id
|
|
||||||
- id
|
|
||||||
filter:
|
|
||||||
docLink:
|
|
||||||
doc:
|
|
||||||
user_id:
|
|
||||||
_eq: X-Hasura-User-Id
|
|
||||||
role: user
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
table:
|
|
||||||
name: docs
|
|
||||||
schema: public
|
|
||||||
configuration:
|
|
||||||
column_config:
|
|
||||||
created_at:
|
|
||||||
custom_name: createdAt
|
|
||||||
file_id:
|
|
||||||
custom_name: fileId
|
|
||||||
updated_at:
|
|
||||||
custom_name: updatedAt
|
|
||||||
user_id:
|
|
||||||
custom_name: userId
|
|
||||||
custom_column_names:
|
|
||||||
created_at: createdAt
|
|
||||||
file_id: fileId
|
|
||||||
updated_at: updatedAt
|
|
||||||
user_id: userId
|
|
||||||
custom_root_fields:
|
|
||||||
delete: deleteDocs
|
|
||||||
delete_by_pk: DeleteDoc
|
|
||||||
insert: insertDocs
|
|
||||||
insert_one: insertDoc
|
|
||||||
select: docs
|
|
||||||
select_aggregate: docsAggregate
|
|
||||||
select_by_pk: doc
|
|
||||||
update: updateDocs
|
|
||||||
update_by_pk: updateDoc
|
|
||||||
object_relationships:
|
|
||||||
- name: file
|
|
||||||
using:
|
|
||||||
foreign_key_constraint_on: file_id
|
|
||||||
- name: user
|
|
||||||
using:
|
|
||||||
foreign_key_constraint_on: user_id
|
|
||||||
array_relationships:
|
|
||||||
- name: docLinks
|
|
||||||
using:
|
|
||||||
foreign_key_constraint_on:
|
|
||||||
column: doc_id
|
|
||||||
table:
|
|
||||||
name: doc_links
|
|
||||||
schema: public
|
|
||||||
insert_permissions:
|
|
||||||
- permission:
|
|
||||||
check: {}
|
|
||||||
columns:
|
|
||||||
- file_id
|
|
||||||
- name
|
|
||||||
set:
|
|
||||||
user_id: x-hasura-user-id
|
|
||||||
role: user
|
|
||||||
select_permissions:
|
|
||||||
- permission:
|
|
||||||
columns:
|
|
||||||
- file_id
|
|
||||||
- id
|
|
||||||
filter: {}
|
|
||||||
limit: 0
|
|
||||||
role: public
|
|
||||||
- permission:
|
|
||||||
columns:
|
|
||||||
- name
|
|
||||||
- created_at
|
|
||||||
- updated_at
|
|
||||||
- file_id
|
|
||||||
- id
|
|
||||||
- user_id
|
|
||||||
filter:
|
|
||||||
user_id:
|
|
||||||
_eq: X-Hasura-User-Id
|
|
||||||
role: user
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
table:
|
||||||
|
name: posts
|
||||||
|
schema: public
|
||||||
|
configuration:
|
||||||
|
column_config: {}
|
||||||
|
custom_column_names: {}
|
||||||
|
custom_root_fields:
|
||||||
|
delete: deletePosts
|
||||||
|
delete_by_pk: deletePost
|
||||||
|
insert: insertPosts
|
||||||
|
insert_one: insertPost
|
||||||
|
update: updatePosts
|
||||||
|
update_by_pk: updatePost
|
||||||
|
insert_permissions:
|
||||||
|
- role: user
|
||||||
|
permission:
|
||||||
|
check: {}
|
||||||
|
set:
|
||||||
|
user_id: x-hasura-user-id
|
||||||
|
columns:
|
||||||
|
- is_public
|
||||||
|
- title
|
||||||
|
select_permissions:
|
||||||
|
- role: public
|
||||||
|
permission:
|
||||||
|
columns:
|
||||||
|
- is_public
|
||||||
|
- title
|
||||||
|
- created_at
|
||||||
|
- updated_at
|
||||||
|
- id
|
||||||
|
- user_id
|
||||||
|
filter:
|
||||||
|
is_public:
|
||||||
|
_eq: true
|
||||||
|
- role: user
|
||||||
|
permission:
|
||||||
|
columns:
|
||||||
|
- is_public
|
||||||
|
- title
|
||||||
|
- created_at
|
||||||
|
- updated_at
|
||||||
|
- id
|
||||||
|
- user_id
|
||||||
|
filter:
|
||||||
|
_or:
|
||||||
|
- is_public:
|
||||||
|
_eq: true
|
||||||
|
- user_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
update_permissions:
|
||||||
|
- role: user
|
||||||
|
permission:
|
||||||
|
columns:
|
||||||
|
- is_public
|
||||||
|
- title
|
||||||
|
filter:
|
||||||
|
user_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
check: null
|
||||||
|
delete_permissions:
|
||||||
|
- role: user
|
||||||
|
permission:
|
||||||
|
filter:
|
||||||
|
user_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
table:
|
||||||
|
name: tasks
|
||||||
|
schema: public
|
||||||
|
configuration:
|
||||||
|
column_config:
|
||||||
|
created_at:
|
||||||
|
custom_name: createdAt
|
||||||
|
updated_at:
|
||||||
|
custom_name: updatedAt
|
||||||
|
custom_column_names:
|
||||||
|
created_at: createdAt
|
||||||
|
updated_at: updatedAt
|
||||||
|
custom_root_fields:
|
||||||
|
delete: deleteTasks
|
||||||
|
delete_by_pk: deleteTask
|
||||||
|
insert: insertTasks
|
||||||
|
insert_one: insertTask
|
||||||
|
select: tasks
|
||||||
|
select_aggregate: tasksAggregate
|
||||||
|
select_by_pk: task
|
||||||
|
select_stream: tasksStream
|
||||||
|
update: updateTasks
|
||||||
|
update_by_pk: updateTask
|
||||||
|
update_many: updateManyTasks
|
||||||
|
object_relationships:
|
||||||
|
- name: user
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on: user_id
|
||||||
|
insert_permissions:
|
||||||
|
- role: user
|
||||||
|
permission:
|
||||||
|
check: {}
|
||||||
|
set:
|
||||||
|
user_id: x-hasura-user-id
|
||||||
|
columns:
|
||||||
|
- name
|
||||||
|
select_permissions:
|
||||||
|
- role: user
|
||||||
|
permission:
|
||||||
|
columns:
|
||||||
|
- done
|
||||||
|
- name
|
||||||
|
- created_at
|
||||||
|
- updated_at
|
||||||
|
- id
|
||||||
|
- user_id
|
||||||
|
filter:
|
||||||
|
user_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
update_permissions:
|
||||||
|
- role: user
|
||||||
|
permission:
|
||||||
|
columns:
|
||||||
|
- done
|
||||||
|
- name
|
||||||
|
filter:
|
||||||
|
user_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
check: null
|
||||||
|
delete_permissions:
|
||||||
|
- role: user
|
||||||
|
permission:
|
||||||
|
filter:
|
||||||
|
user_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
@@ -9,6 +9,8 @@ configuration:
|
|||||||
custom_name: createdAt
|
custom_name: createdAt
|
||||||
download_expiration:
|
download_expiration:
|
||||||
custom_name: downloadExpiration
|
custom_name: downloadExpiration
|
||||||
|
id:
|
||||||
|
custom_name: id
|
||||||
max_upload_file_size:
|
max_upload_file_size:
|
||||||
custom_name: maxUploadFileSize
|
custom_name: maxUploadFileSize
|
||||||
min_upload_file_size:
|
min_upload_file_size:
|
||||||
@@ -21,6 +23,7 @@ configuration:
|
|||||||
cache_control: cacheControl
|
cache_control: cacheControl
|
||||||
created_at: createdAt
|
created_at: createdAt
|
||||||
download_expiration: downloadExpiration
|
download_expiration: downloadExpiration
|
||||||
|
id: id
|
||||||
max_upload_file_size: maxUploadFileSize
|
max_upload_file_size: maxUploadFileSize
|
||||||
min_upload_file_size: minUploadFileSize
|
min_upload_file_size: minUploadFileSize
|
||||||
presigned_urls_enabled: presignedUrlsEnabled
|
presigned_urls_enabled: presignedUrlsEnabled
|
||||||
|
|||||||
@@ -9,10 +9,14 @@ configuration:
|
|||||||
custom_name: createdAt
|
custom_name: createdAt
|
||||||
etag:
|
etag:
|
||||||
custom_name: etag
|
custom_name: etag
|
||||||
|
id:
|
||||||
|
custom_name: id
|
||||||
is_uploaded:
|
is_uploaded:
|
||||||
custom_name: isUploaded
|
custom_name: isUploaded
|
||||||
mime_type:
|
mime_type:
|
||||||
custom_name: mimeType
|
custom_name: mimeType
|
||||||
|
name:
|
||||||
|
custom_name: name
|
||||||
size:
|
size:
|
||||||
custom_name: size
|
custom_name: size
|
||||||
updated_at:
|
updated_at:
|
||||||
@@ -23,8 +27,10 @@ configuration:
|
|||||||
bucket_id: bucketId
|
bucket_id: bucketId
|
||||||
created_at: createdAt
|
created_at: createdAt
|
||||||
etag: etag
|
etag: etag
|
||||||
|
id: id
|
||||||
is_uploaded: isUploaded
|
is_uploaded: isUploaded
|
||||||
mime_type: mimeType
|
mime_type: mimeType
|
||||||
|
name: name
|
||||||
size: size
|
size: size
|
||||||
updated_at: updatedAt
|
updated_at: updatedAt
|
||||||
uploaded_by_user_id: uploadedByUserId
|
uploaded_by_user_id: uploadedByUserId
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user