Compare commits
86 Commits
@nhost/nex
...
@nhost/nho
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c3d9b11a6 | ||
|
|
b1678eaad3 | ||
|
|
f1c16dba6e | ||
|
|
aa1fdf6c2c | ||
|
|
bebf9e1f2b | ||
|
|
2413c10283 | ||
|
|
0f7fbdab97 | ||
|
|
14e5fd63a6 | ||
|
|
2446913836 | ||
|
|
1f88a9f47a | ||
|
|
261e37cda4 | ||
|
|
5ee395ea8e | ||
|
|
828633ffc9 | ||
|
|
7b7527a5e6 | ||
|
|
620566fa4d | ||
|
|
4ce8b88d27 | ||
|
|
28d25e46de | ||
|
|
e0cfcafead | ||
|
|
12bc30daa3 | ||
|
|
7b5f00d10e | ||
|
|
58e1485c13 | ||
|
|
a64f1c4396 | ||
|
|
75a1428114 | ||
|
|
d82d830849 | ||
|
|
2def59fc6c | ||
|
|
64ceb2c6bf | ||
|
|
3ee007620c | ||
|
|
b9cf8172a0 | ||
|
|
32edfb4a9f | ||
|
|
848db9b672 | ||
|
|
3766921bcc | ||
|
|
5546052b2c | ||
|
|
c569b56d3d | ||
|
|
52ffa84adb | ||
|
|
b5ae438a8e | ||
|
|
fae05f7af2 | ||
|
|
380d7fc8ce | ||
|
|
94132bbc7f | ||
|
|
d87a9d7c79 | ||
|
|
be7756d4a2 | ||
|
|
ca5e335bff | ||
|
|
b9ed794f2b | ||
|
|
042dc7d27c | ||
|
|
db2df3d5b2 | ||
|
|
0b1cb628f2 | ||
|
|
912d95d153 | ||
|
|
76059f4738 | ||
|
|
011572f3ef | ||
|
|
b727b354dd | ||
|
|
a0682ed22e | ||
|
|
4d16306e56 | ||
|
|
b7861bbd36 | ||
|
|
e279805896 | ||
|
|
e3ebd9cb1b | ||
|
|
5bb928da2c | ||
|
|
ab06e96eac | ||
|
|
6e2aabbda0 | ||
|
|
e4ce235f38 | ||
|
|
e783b7478b | ||
|
|
06d2d2b0c7 | ||
|
|
656379e78b | ||
|
|
4156a9a61e | ||
|
|
0b72829274 | ||
|
|
6b0baab151 | ||
|
|
93f9d2d01d | ||
|
|
3fb3d4c282 | ||
|
|
ccba0b5015 | ||
|
|
62e331500d | ||
|
|
4104ddbcb6 | ||
|
|
43fc040a29 | ||
|
|
e472b2cb19 | ||
|
|
6570a940ee | ||
|
|
d3e97c87d6 | ||
|
|
36508c7930 | ||
|
|
709d364749 | ||
|
|
73eb2db159 | ||
|
|
86eb8903dc | ||
|
|
d665473074 | ||
|
|
58534c24f0 | ||
|
|
90a1c3b9e1 | ||
|
|
bdfa2b3053 | ||
|
|
2c5b31f27a | ||
|
|
d75fd747e0 | ||
|
|
a71b3aff59 | ||
|
|
13935ebdc4 | ||
|
|
f6093a619f |
42
README.md
42
README.md
@@ -20,7 +20,7 @@
|
|||||||
<hr />
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
**Nhost is a open-source GraphQL backend,** built with the following things in mind:
|
**Nhost is an open-source GraphQL backend,** built with the following things in mind:
|
||||||
|
|
||||||
- Open-Source
|
- Open-Source
|
||||||
- Developer Productivity
|
- Developer Productivity
|
||||||
@@ -213,6 +213,13 @@ Here are some ways of contributing to making Nhost better:
|
|||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/hajek-raven">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/7288737?v=4" width="100;" alt="hajek-raven"/>
|
||||||
|
<br />
|
||||||
|
<sub><b>Filip Hájek</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/jerryjappinen">
|
<a href="https://github.com/jerryjappinen">
|
||||||
<img src="https://avatars.githubusercontent.com/u/1101002?v=4" width="100;" alt="jerryjappinen"/>
|
<img src="https://avatars.githubusercontent.com/u/1101002?v=4" width="100;" alt="jerryjappinen"/>
|
||||||
@@ -227,6 +234,13 @@ Here are some ways of contributing to making Nhost better:
|
|||||||
<sub><b>Mustafa Hanif</b></sub>
|
<sub><b>Mustafa Hanif</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/timpratim">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/32492961?v=4" width="100;" alt="timpratim"/>
|
||||||
|
<br />
|
||||||
|
<sub><b>Pratim</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/Savinvadim1312">
|
<a href="https://github.com/Savinvadim1312">
|
||||||
<img src="https://avatars.githubusercontent.com/u/16936043?v=4" width="100;" alt="Savinvadim1312"/>
|
<img src="https://avatars.githubusercontent.com/u/16936043?v=4" width="100;" alt="Savinvadim1312"/>
|
||||||
@@ -240,7 +254,8 @@ Here are some ways of contributing to making Nhost better:
|
|||||||
<br />
|
<br />
|
||||||
<sub><b>Amir Ahmic</b></sub>
|
<sub><b>Amir Ahmic</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/akd-io">
|
<a href="https://github.com/akd-io">
|
||||||
<img src="https://avatars.githubusercontent.com/u/30059155?v=4" width="100;" alt="akd-io"/>
|
<img src="https://avatars.githubusercontent.com/u/30059155?v=4" width="100;" alt="akd-io"/>
|
||||||
@@ -254,8 +269,7 @@ Here are some ways of contributing to making Nhost better:
|
|||||||
<br />
|
<br />
|
||||||
<sub><b>Colin Broderick</b></sub>
|
<sub><b>Colin Broderick</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr>
|
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/dohomi">
|
<a href="https://github.com/dohomi">
|
||||||
<img src="https://avatars.githubusercontent.com/u/489221?v=4" width="100;" alt="dohomi"/>
|
<img src="https://avatars.githubusercontent.com/u/489221?v=4" width="100;" alt="dohomi"/>
|
||||||
@@ -263,6 +277,13 @@ Here are some ways of contributing to making Nhost better:
|
|||||||
<sub><b>Dominic Garms</b></sub>
|
<sub><b>Dominic Garms</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/GavanWilhite">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/2085119?v=4" width="100;" alt="GavanWilhite"/>
|
||||||
|
<br />
|
||||||
|
<sub><b>Gavan Wilhite</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/alveshelio">
|
<a href="https://github.com/alveshelio">
|
||||||
<img src="https://avatars.githubusercontent.com/u/8176422?v=4" width="100;" alt="alveshelio"/>
|
<img src="https://avatars.githubusercontent.com/u/8176422?v=4" width="100;" alt="alveshelio"/>
|
||||||
@@ -276,7 +297,8 @@ Here are some ways of contributing to making Nhost better:
|
|||||||
<br />
|
<br />
|
||||||
<sub><b>Hoang Do</b></sub>
|
<sub><b>Hoang Do</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/ghoshnirmalya">
|
<a href="https://github.com/ghoshnirmalya">
|
||||||
<img src="https://avatars.githubusercontent.com/u/6391763?v=4" width="100;" alt="ghoshnirmalya"/>
|
<img src="https://avatars.githubusercontent.com/u/6391763?v=4" width="100;" alt="ghoshnirmalya"/>
|
||||||
@@ -284,21 +306,13 @@ Here are some ways of contributing to making Nhost better:
|
|||||||
<sub><b>Nirmalya Ghosh</b></sub>
|
<sub><b>Nirmalya Ghosh</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="center">
|
|
||||||
<a href="https://github.com/timpratim">
|
|
||||||
<img src="https://avatars.githubusercontent.com/u/32492961?v=4" width="100;" alt="timpratim"/>
|
|
||||||
<br />
|
|
||||||
<sub><b>Pratim</b></sub>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/quentin-decre">
|
<a href="https://github.com/quentin-decre">
|
||||||
<img src="https://avatars.githubusercontent.com/u/1137511?v=4" width="100;" alt="quentin-decre"/>
|
<img src="https://avatars.githubusercontent.com/u/1137511?v=4" width="100;" alt="quentin-decre"/>
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Quentin Decré</b></sub>
|
<sub><b>Quentin Decré</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr>
|
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/komninoschat">
|
<a href="https://github.com/komninoschat">
|
||||||
<img src="https://avatars.githubusercontent.com/u/29049104?v=4" width="100;" alt="komninoschat"/>
|
<img src="https://avatars.githubusercontent.com/u/29049104?v=4" width="100;" alt="komninoschat"/>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ $ pnpm build
|
|||||||
|
|
||||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||||
|
|
||||||
### Serve
|
### Serve
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ pnpm serve
|
$ pnpm serve
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
title: 'Metadata and Serverless Functions'
|
title: 'Metadata and Serverless Functions'
|
||||||
---
|
---
|
||||||
|
|
||||||
|
import Tabs from '@theme/Tabs';
|
||||||
|
import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
In the previous section, we only created a new table; `customers`. Using the CLI you can also do changes to other parts of your backend.
|
In the previous section, we only created a new table; `customers`. Using the CLI you can also do changes to other parts of your backend.
|
||||||
|
|
||||||
There are three things the CLI and the GitHub integration track and applies to production:
|
There are three things the CLI and the GitHub integration track and applies to production:
|
||||||
@@ -73,20 +76,25 @@ Serverless functions are placed in the `functions/` folder of your repository. E
|
|||||||
|
|
||||||
Before we create our serverless function we'll install `express`, which is a requirement for serverless functions to work.
|
Before we create our serverless function we'll install `express`, which is a requirement for serverless functions to work.
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabItem value="npm" label="npm" default>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install express
|
npm install express
|
||||||
# or with yarn
|
npm install -d @types/node @types/express
|
||||||
yarn add express
|
|
||||||
```
|
```
|
||||||
|
|
||||||
We'll use TypeScript so we'll install two type definitions too:
|
</TabItem>
|
||||||
|
<TabItem value="yarn" label="Yarn">
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install -d @types/node @types/express
|
yarn add express
|
||||||
# or with yarn
|
|
||||||
yarn add -D @types/node @types/express
|
yarn add -D @types/node @types/express
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
Then we'll create a file `functions/time.ts`
|
Then we'll create a file `functions/time.ts`
|
||||||
|
|
||||||
In the file `time.ts` we'll add the following code to create our serverless function:
|
In the file `time.ts` we'll add the following code to create our serverless function:
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: 'Create your app'
|
title: 'Create your app'
|
||||||
slug: /get-started/quick-start
|
|
||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
title: 'JavaScript client'
|
title: 'JavaScript client'
|
||||||
---
|
---
|
||||||
|
|
||||||
|
import Tabs from '@theme/Tabs';
|
||||||
|
import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
In the previous section, you used the Hasura Console to fetch a list of todos. Now, you will write a small JavaScript client to interact and retrieve todos from your Nhost app.
|
In the previous section, you used the Hasura Console to fetch a list of todos. Now, you will write a small JavaScript client to interact and retrieve todos from your Nhost app.
|
||||||
|
|
||||||
### Frontend frameworks
|
### Frontend frameworks
|
||||||
@@ -14,40 +17,36 @@ In this guide, we'll keep the example simple. We're not using a frontend framewo
|
|||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
> Make sure you have [Node.js](https://nodejs.org) and [npm](https://docs.npmjs.com/getting-started) installed.
|
:::info
|
||||||
|
|
||||||
Create a new folder called `nhost-todos`, and initialize a new JavaScript app there:
|
Make sure you have [Node.js](https://nodejs.org) and [npm](https://docs.npmjs.com/getting-started) or [Yarn](https://classic.yarnpkg.com/lang/en/docs/install) installed.
|
||||||
|
|
||||||
Using npm package manager
|
:::
|
||||||
|
|
||||||
|
Create a new folder called `nhost-todos`, initialize a new JavaScript app there, and install the Nhost JavaScript SDK:
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabItem value="npm" label="npm" default>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm init -y
|
npm init -y
|
||||||
|
yarn add @nhost/nhost-js graphql
|
||||||
```
|
```
|
||||||
|
|
||||||
or
|
</TabItem>
|
||||||
|
<TabItem value="yarn" label="Yarn">
|
||||||
Using Yarn package manager
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn init -y
|
yarn init -y
|
||||||
|
npm install @nhost/nhost-js graphql
|
||||||
```
|
```
|
||||||
|
|
||||||
> You might have to edit the `package.json` file and add/change the `type` object to `module` (`"type": "module"`).
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
Install Nhost JavaScript SDK:
|
:::caution attention
|
||||||
|
You might have to edit the `package.json` file and add/change the `type` object to `module` (`"type": "module"`).
|
||||||
Using npm package manager
|
:::
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install @nhost/nhost-js
|
|
||||||
```
|
|
||||||
|
|
||||||
or
|
|
||||||
Using Yarn package manager
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yarn add @nhost/nhost-js
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
title: 'Set permissions'
|
title: 'Set permissions'
|
||||||
---
|
---
|
||||||
|
|
||||||
In the previous section, you could fetch the todos because the **admin** role is enabled by default when using Hasura Console. When building your applications, you want to define permissions using **roles** that your users can assume when making requests.
|
While using the Hasura Console, you could fetch the todos because the **admin** role is enabled by default but when building your applications with a client, you want to define permissions using **roles** that your users can assume when making requests.
|
||||||
|
|
||||||
Hasura supports role-based access control. You create rules for each role, table, and operation (select, insert, update and delete) that can check dynamic session variables, like user ID.
|
Hasura supports role-based access control. You create rules for each role, table, and operation (select, insert, update and delete) that can check dynamic session variables, like user ID.
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ cd my-nhost-v2-app
|
|||||||
|
|
||||||
### Update config
|
### Update config
|
||||||
|
|
||||||
Update `config: 3` to `config: 2` in `nhost/config.yaml`. This will update Hasura's configuration version, and we need to downgrade the version when we export migrations and metadata.
|
Update `version: 3` to `version: 2` in `nhost/config.yaml`. This will update Hasura's configuration version, and we need to downgrade the version when we export migrations and metadata.
|
||||||
|
|
||||||
### Export current migrations and metadata from Nhost v1
|
### Export current migrations and metadata from Nhost v1
|
||||||
|
|
||||||
@@ -88,15 +88,17 @@ Update `config: 2` to `config: 3` in `nhost/config.yaml`.
|
|||||||
|
|
||||||
### Pull migrations and metadata from our local instance
|
### Pull migrations and metadata from our local instance
|
||||||
|
|
||||||
:::tip
|
In the `nhost/` folder, run the following command:
|
||||||
You can not use port `1337` in these requests. You have to use the specific port Huasra uses. Go to the Hasura Console under API and look what port Hasura is using under GraphQL Endpoint.
|
|
||||||
:::
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hasura migrate create init --from-server --endpoint=http://localhost:[hasura-port] --admin-secret=nhost-admin-secret
|
hasura migrate create init --from-server --endpoint=http://localhost:[hasura-port] --admin-secret=nhost-admin-secret
|
||||||
hasura metadata export --from-server --endpoint=http://localhost:[hasura-port] --admin-secret=nhost-admin-secret
|
hasura metadata export --endpoint=http://localhost:[hasura-port] --admin-secret=nhost-admin-secret
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
You cannot use port `1337` in the commands above. You have to use the specific port Hasura uses. Go to the Hasura Console under API and look for the port Hasura is using under GraphQL Endpoint.
|
||||||
|
:::
|
||||||
|
|
||||||
### Done
|
### Done
|
||||||
|
|
||||||
You now have a Nhost v2 project locally with correct migrations and metadata. Happy hacking!
|
You now have a Nhost v2 project locally with correct migrations and metadata. Happy hacking!
|
||||||
|
|||||||
5
docs/docs/platform/_category_.json
Normal file
5
docs/docs/platform/_category_.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"label": "The Nhost Platform",
|
||||||
|
"position": 1,
|
||||||
|
"link": { "type": "generated-index", "slug": "/platform" }
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"label": "Authentication",
|
"label": "Authentication",
|
||||||
"position": 3,
|
"position": 4,
|
||||||
"link": { "id": "platform/authentication/index", "type": "doc" }
|
"link": { "id": "platform/authentication/index", "type": "doc" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ await nhost.auth.signIn({
|
|||||||
|
|
||||||
A user can be created anonymously. This is useful for offering a limited version of your application to your users without having them sign in first.
|
A user can be created anonymously. This is useful for offering a limited version of your application to your users without having them sign in first.
|
||||||
|
|
||||||
An anonymous user gets a user ID with the `anonymous` role. This role can be used to [set permissions in Hasura](/platform/database/permissions).
|
An anonymous user gets a user ID with the `anonymous` role. This role can be used to [set permissions in Hasura](/platform/graphql/permissions).
|
||||||
|
|
||||||
### Deanonymize users
|
### Deanonymize users
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
{
|
{
|
||||||
"label": "Database",
|
"label": "Database",
|
||||||
"position": 2,
|
"position": 2
|
||||||
"collapsed": false,
|
|
||||||
"link": { "type": "generated-index", "slug": "/platform/database" }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: 'Schema'
|
title: 'Database'
|
||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
---
|
---
|
||||||
|
|
||||||
4
docs/docs/platform/graphql/_category_.json
Normal file
4
docs/docs/platform/graphql/_category_.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"label": "GraphQL",
|
||||||
|
"position": 3
|
||||||
|
}
|
||||||
@@ -1,31 +1,49 @@
|
|||||||
---
|
---
|
||||||
title: 'GraphQL'
|
title: 'GraphQL'
|
||||||
sidebar_position: 3
|
sidebar_position: 1
|
||||||
---
|
---
|
||||||
|
|
||||||
GraphQL is a query language for APIs. It provides a complete and understandable description of the data in your API.
|
Every Nhost app has its own autogenerated GraphQL API. The GraphQL API is based on the tables and columns in the [Postgres database](/platform/database) and is instantly available. It's [Hasura GraphQL engine](https://github.com/hasura/graphql-engine) that powers the GraphQL API.
|
||||||
|
|
||||||
Every Nhost app comes with a GraphQL API, and you can connect to this API with any GraphQL client you like. Most often you'd use the client included in [Nhost SDK](/reference/sdk/graphql).
|
The GraphQL API is available at: `https://[subdomain].nhost.run/v1/graphql`.
|
||||||
|
|
||||||
|
## What is GraphQL
|
||||||
|
|
||||||
|
GraphQL is a query language for APIs that prioritize developer experience. The GraphQL API can be used to both fetch (query) and modify (mutation) data. GraphQL is especially powerful for frontend developers who wants to build products fast.
|
||||||
|
|
||||||
|
### GraphQL clients for JavaScript
|
||||||
|
|
||||||
|
To interact with the GraphQL API it's recommended to use a GraphQL client:
|
||||||
|
|
||||||
|
- [Apollo Client](https://www.apollographql.com/docs/react/)
|
||||||
|
- [URQL](https://formidable.com/open-source/urql/)
|
||||||
|
- [React Query](https://react-query.tanstack.com/graphql)
|
||||||
|
- [SWR](https://swr.vercel.app/docs/data-fetching#graphql)
|
||||||
|
|
||||||
|
It's also possible to use the built-in [GraphQL client](/reference/sdk/graphql) in the Nhost JavaScript client.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## GraphQL queries
|
## GraphQL Query
|
||||||
|
|
||||||
You can read your app's data with a GraphQL query.
|
A GraphQL query is used to fetch data from the database.
|
||||||
|
|
||||||
Here is a GraphQL query that selects title, body, and done for every row in your todos table.
|
Here is a GraphQL query that selects `title`, `body`, and `isCompleted` for every row in a `todos` table.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
```graphql
|
```graphql
|
||||||
query GetTodos {
|
query GetTodos {
|
||||||
todos {
|
todos {
|
||||||
title
|
title
|
||||||
body
|
body
|
||||||
|
done
|
||||||
isCompleted
|
isCompleted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Response:
|
**Response:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -33,7 +51,7 @@ Response:
|
|||||||
"todos": [
|
"todos": [
|
||||||
{
|
{
|
||||||
"title": "Delete Firebase account",
|
"title": "Delete Firebase account",
|
||||||
"body": "Migrate to nhost.io",
|
"body": "Migrate to Nhost",
|
||||||
"isCompleted": true
|
"isCompleted": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -43,7 +61,7 @@ Response:
|
|||||||
|
|
||||||
#### Filtering and sorting
|
#### Filtering and sorting
|
||||||
|
|
||||||
More complex queries utilize filters, limits, sorting and aggregation.
|
GraphQL queries More complex queries utilize filters, limits, sorting and aggregation.
|
||||||
|
|
||||||
This GraphQL query selects all items in the todo table that aren't done, with the total number of comments and the last five comments:
|
This GraphQL query selects all items in the todo table that aren't done, with the total number of comments and the last five comments:
|
||||||
|
|
||||||
@@ -98,6 +116,12 @@ Response:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Check out Hasura's documentation for full documentation for GraphQL queries.
|
||||||
|
|
||||||
|
[Hasura GraphQL queries](https://hasura.io/docs/latest/graphql/core/databases/postgres/queries/index/)
|
||||||
|
:::
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## GraphQL mutations
|
## GraphQL mutations
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: 'Permissions'
|
title: 'Permissions'
|
||||||
sidebar_position: 2
|
sidebar_position: 1
|
||||||
---
|
---
|
||||||
|
|
||||||
The GraphQL API is protected by a role-based permission system based on access tokens. Permissions are handled on a per-table basis in Hasura Console.
|
The GraphQL API is protected by a role-based permission system based on access tokens. Permissions are handled on a per-table basis in Hasura Console.
|
||||||
@@ -1,35 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: 'The Nhost platform'
|
title: 'The Nhost Platform'
|
||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
---
|
---
|
||||||
|
|
||||||
This section:
|
- [Database](/platform/database)
|
||||||
|
- [GraphQL](/platform/graphql)
|
||||||
### Database
|
- [Authentication](/platform/authentication)
|
||||||
|
- [Storage](/platform/storage)
|
||||||
- [Schema](/platform/database)
|
- [Serverless Functions](/platform/serverless-functions)
|
||||||
- [Permissions](/platform/database/permissions)
|
- [Nhost](/platform/nhost)
|
||||||
- [GraphQL](/platform/database/graphql)
|
|
||||||
|
|
||||||
### Authentication
|
|
||||||
|
|
||||||
- [Authentication overview](/platform/authentication)
|
|
||||||
- [User management](/platform/authentication/user-management)
|
|
||||||
- [Sign-in methods](/platform/authentication/sign-in-methods)
|
|
||||||
- [OAuth providers](/platform/authentication/social-sign-in)
|
|
||||||
- [Email templates](/platform/authentication/email-templates)
|
|
||||||
|
|
||||||
### Storage
|
|
||||||
|
|
||||||
- [File storage](/platform/storage)
|
|
||||||
|
|
||||||
### Serverless functions
|
|
||||||
|
|
||||||
- [Creating functions](/platform/serverless-functions)
|
|
||||||
- [Event triggers](/platform/serverless-functions/event-triggers)
|
|
||||||
|
|
||||||
### Nhost
|
|
||||||
|
|
||||||
- [Environment variables](/platform/nhost/environment-variables)
|
|
||||||
- [GitHub integration](/platform/nhost/github-integration)
|
|
||||||
- [Local development](/platform/nhost/local-development)
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"label": "Nhost",
|
"label": "Nhost",
|
||||||
"position": 6
|
"position": 7
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ title: 'Nhost CLI'
|
|||||||
sidebar_position: 3
|
sidebar_position: 3
|
||||||
---
|
---
|
||||||
|
|
||||||
|
import Tabs from '@theme/Tabs';
|
||||||
|
import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
Nhost CLI lets you run Nhost's development environment locally on macOS, Linux and Windows.
|
Nhost CLI lets you run Nhost's development environment locally on macOS, Linux and Windows.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -28,10 +31,23 @@ To run serverless functions locally, you must have the appropriate runtimes inst
|
|||||||
|
|
||||||
For Node.js, you will also need to have [express](https://www.npmjs.com/package/express) installed in your repository:
|
For Node.js, you will also need to have [express](https://www.npmjs.com/package/express) installed in your repository:
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabItem value="npm" label="npm" default>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install --save-dev express @types/express
|
npm install --save-dev express @types/express
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="yarn" label="Yarn">
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn add -D express @types/express
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
[Read more about runtimes](/platform/serverless-functions)
|
[Read more about runtimes](/platform/serverless-functions)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
{
|
{
|
||||||
"label": "Serverless Functions",
|
"label": "Serverless Functions",
|
||||||
"position": 5,
|
"position": 6
|
||||||
"link": {
|
|
||||||
"type": "generated-index",
|
|
||||||
"slug": "/platform/serverless-functions"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: 'Creating functions'
|
title: 'Serverless Functions'
|
||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -39,19 +39,19 @@ HTTP endpoints are automatically generated based on the file structure under `fu
|
|||||||
|
|
||||||
As such, given this file structure:
|
As such, given this file structure:
|
||||||
|
|
||||||
```js
|
```text
|
||||||
functions / index.js;
|
functions/index.js
|
||||||
functions / users / index.ts;
|
functions/users/index.ts
|
||||||
functions / active.ts;
|
functions/active.ts
|
||||||
functions / my - company.js;
|
functions/my-company.js
|
||||||
```
|
```
|
||||||
|
|
||||||
The following endpoints will be available:
|
The following endpoints will be available:
|
||||||
|
|
||||||
- https://yourappid.nhost.run/v1/functions/ - (functions/index.js)
|
- https://yourappid.nhost.run/v1/functions/ from `functions/index.js`.
|
||||||
- https://yourappid.nhost.run/v1/functions/users - (functions/users/index.ts)
|
- https://yourappid.nhost.run/v1/functions/users from `functions/users/index.ts`.
|
||||||
- https://yourappid.nhost.run/v1/functions/users/active - (functions/users/active.ts)
|
- https://yourappid.nhost.run/v1/functions/users/active from `functions/users/active.ts`.
|
||||||
- https://yourappid.nhost.run/v1/functions/my-company - (functions/my-company.js)
|
- https://yourappid.nhost.run/v1/functions/my-company from `functions/my-company.js`.
|
||||||
|
|
||||||
If you've used Netlify or Vercel, this routing logic will be familiar to you.
|
If you've used Netlify or Vercel, this routing logic will be familiar to you.
|
||||||
|
|
||||||
@@ -59,6 +59,4 @@ If you've used Netlify or Vercel, this routing logic will be familiar to you.
|
|||||||
|
|
||||||
## Infrastructure
|
## Infrastructure
|
||||||
|
|
||||||
In production, serverless functions are deployed on AWS Lambda.
|
Serverless Functions are deployed to AWS Lambda in the same region as your app.
|
||||||
|
|
||||||
When developing locally, you must have the correct runtime installed on your machine.
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"label": "Storage",
|
"label": "Storage",
|
||||||
"position": 4,
|
"position": 5
|
||||||
"link": { "type": "generated-index", "slug": "/platform/storage" }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: 'File storage'
|
title: 'Storage'
|
||||||
sidebar_position: 1
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Nhost stores and serves files of any type in your backend.
|
Nhost stores and serves files of any type in your backend.
|
||||||
@@ -11,7 +11,7 @@ sidebar_position: 3
|
|||||||
| HASURA_GRAPHQL_DATABASE_URL**\*** | [PostgreSQL connection URI](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING). Required to inject the `auth` schema into the database. | |
|
| HASURA_GRAPHQL_DATABASE_URL**\*** | [PostgreSQL connection URI](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING). Required to inject the `auth` schema into the database. | |
|
||||||
| HASURA_GRAPHQL_GRAPHQL_URL**\*** | Hasura GraphQL endpoint. Required to manipulate account data. For instance: `https://graphql-engine:8080/v1/graphql` | |
|
| HASURA_GRAPHQL_GRAPHQL_URL**\*** | Hasura GraphQL endpoint. Required to manipulate account data. For instance: `https://graphql-engine:8080/v1/graphql` | |
|
||||||
| HASURA_GRAPHQL_ADMIN_SECRET**\*** | Hasura GraphQL Admin Secret. Required to manipulate account data. | |
|
| HASURA_GRAPHQL_ADMIN_SECRET**\*** | Hasura GraphQL Admin Secret. Required to manipulate account data. | |
|
||||||
| AUTH_HOST | Server host. [Docs](http://expressjs.com/en/5x/api.html#app.listen) | `0.0.0.0` |
|
| AUTH_HOST | Server host. This option is available until Hasura-auth `v0.6.0`. [Docs](http://expressjs.com/en/5x/api.html#app.listen) | `0.0.0.0` |
|
||||||
| AUTH_PORT | Server port. [Docs](http://expressjs.com/en/5x/api.html#app.listen) | `4000` |
|
| AUTH_PORT | Server port. [Docs](http://expressjs.com/en/5x/api.html#app.listen) | `4000` |
|
||||||
| AUTH_SERVER_URL | Server URL of where Hasura Backend Plus is running. This value is to used as a callback in email templates and for the OAuth authentication process. | |
|
| AUTH_SERVER_URL | Server URL of where Hasura Backend Plus is running. This value is to used as a callback in email templates and for the OAuth authentication process. | |
|
||||||
| AUTH_CLIENT_URL | URL of your frontend application. Used to redirect users to the right page once actions based on emails or OAuth succeed. | |
|
| AUTH_CLIENT_URL | URL of your frontend application. Used to redirect users to the right page once actions based on emails or OAuth succeed. | |
|
||||||
|
|||||||
@@ -3,20 +3,28 @@ title: 'Configuration'
|
|||||||
sidebar_position: 2
|
sidebar_position: 2
|
||||||
---
|
---
|
||||||
|
|
||||||
|
import Tabs from '@theme/Tabs';
|
||||||
|
import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
With yarn:
|
<Tabs>
|
||||||
|
<TabItem value="npm" label="npm" default>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn add @nhost/react @nhost/nextjs
|
npm install @nhost/react @nhost/nextjs graphql
|
||||||
```
|
```
|
||||||
|
|
||||||
With Npm:
|
</TabItem>
|
||||||
|
<TabItem value="yarn" label="Yarn">
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install @nhost/react @nhost/nextjs
|
yarn add @nhost/react @nhost/nextjs graphql
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|||||||
@@ -3,20 +3,28 @@ title: 'Apollo GraphQL'
|
|||||||
sidebar_position: 4
|
sidebar_position: 4
|
||||||
---
|
---
|
||||||
|
|
||||||
|
import Tabs from '@theme/Tabs';
|
||||||
|
import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
With Yarn:
|
<Tabs>
|
||||||
|
<TabItem value="npm" label="npm" default>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn add @nhost/react @nhost/react-apollo @apollo/client
|
npm install @nhost/react @nhost/react-apollo @apollo/client graphql
|
||||||
```
|
```
|
||||||
|
|
||||||
With Npm:
|
</TabItem>
|
||||||
|
<TabItem value="yarn" label="Yarn">
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install @nhost/react @nhost/react-apollo @apollo/client
|
yarn add @nhost/react @nhost/react-apollo @apollo/client graphql
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Let's add a `NhostApolloProvider`. Make sure the Apollo Provider is nested into `NhostReactProvider`, as it will need the Nhost context to determine the authentication headers to be sent to the GraphQL endpoint.
|
Let's add a `NhostApolloProvider`. Make sure the Apollo Provider is nested into `NhostReactProvider`, as it will need the Nhost context to determine the authentication headers to be sent to the GraphQL endpoint.
|
||||||
|
|||||||
@@ -3,20 +3,28 @@ title: 'Getting started'
|
|||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
---
|
---
|
||||||
|
|
||||||
|
import Tabs from '@theme/Tabs';
|
||||||
|
import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
With Yarn:
|
<Tabs>
|
||||||
|
<TabItem value="npm" label="npm" default>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn add @nhost/react
|
npm install @nhost/react graphql
|
||||||
```
|
```
|
||||||
|
|
||||||
With npm:
|
</TabItem>
|
||||||
|
<TabItem value="yarn" label="Yarn">
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install @nhost/react
|
yarn add @nhost/react graphql
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ title: 'Overview'
|
|||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
---
|
---
|
||||||
|
|
||||||
|
import Tabs from '@theme/Tabs';
|
||||||
|
import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
Nhost SDK is the primary way of interacting with your Nhost app. It exposes a standard interface for each of the following services:
|
Nhost SDK is the primary way of interacting with your Nhost app. It exposes a standard interface for each of the following services:
|
||||||
|
|
||||||
- GraphQL
|
- GraphQL
|
||||||
@@ -14,10 +17,23 @@ Nhost SDK is the primary way of interacting with your Nhost app. It exposes a st
|
|||||||
|
|
||||||
Install the dependency:
|
Install the dependency:
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabItem value="npm" label="npm" default>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install @nhost/nhost-js
|
npm install @nhost/nhost-js graphql
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="yarn" label="Yarn">
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn add @nhost/nhost-js graphql
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
Then import and initialize a single `nhost` instance in your code:
|
Then import and initialize a single `nhost` instance in your code:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const config = {
|
|||||||
sidebarPath: require.resolve('./sidebars.js'),
|
sidebarPath: require.resolve('./sidebars.js'),
|
||||||
remarkPlugins: [require('mdx-mermaid')],
|
remarkPlugins: [require('mdx-mermaid')],
|
||||||
// Please change this to your repo.
|
// Please change this to your repo.
|
||||||
editUrl: 'https://github.com/nhost/nhost/blob/main/docs/',
|
editUrl: 'https://github.com/nhost/nhost/edit/main/docs/',
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
customCss: require.resolve('./src/css/custom.css'),
|
customCss: require.resolve('./src/css/custom.css'),
|
||||||
@@ -51,6 +51,12 @@ const config = {
|
|||||||
disableSwitch: false,
|
disableSwitch: false,
|
||||||
respectPrefersColorScheme: true,
|
respectPrefersColorScheme: true,
|
||||||
},
|
},
|
||||||
|
metadata: [
|
||||||
|
{
|
||||||
|
name: 'og:image',
|
||||||
|
content: 'https://docs.nhost.io/img/splash.png',
|
||||||
|
},
|
||||||
|
],
|
||||||
navbar: {
|
navbar: {
|
||||||
hideOnScroll: true,
|
hideOnScroll: true,
|
||||||
logo: {
|
logo: {
|
||||||
|
|||||||
@@ -19,9 +19,8 @@ const sidebars = {
|
|||||||
label: 'Quick Start',
|
label: 'Quick Start',
|
||||||
type: 'category',
|
type: 'category',
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
link: { id: 'get-started/quick-start/create-your-app', type: 'doc' },
|
|
||||||
items: [
|
items: [
|
||||||
'get-started/quick-start/create-your-app',
|
'get-started/quick-start/index',
|
||||||
'get-started/quick-start/schema',
|
'get-started/quick-start/schema',
|
||||||
'get-started/quick-start/javascript-client',
|
'get-started/quick-start/javascript-client',
|
||||||
'get-started/quick-start/permissions',
|
'get-started/quick-start/permissions',
|
||||||
@@ -30,13 +29,11 @@ const sidebars = {
|
|||||||
{
|
{
|
||||||
label: 'Authentication',
|
label: 'Authentication',
|
||||||
type: 'category',
|
type: 'category',
|
||||||
link: { id: 'get-started/authentication/index', type: 'doc' },
|
|
||||||
items: ['get-started/authentication/index'],
|
items: ['get-started/authentication/index'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'CLI Workflow',
|
label: 'CLI Workflow',
|
||||||
type: 'category',
|
type: 'category',
|
||||||
link: { id: 'get-started/cli-workflow/index', type: 'doc' },
|
|
||||||
items: [
|
items: [
|
||||||
'get-started/cli-workflow/index',
|
'get-started/cli-workflow/index',
|
||||||
'get-started/cli-workflow/workflow-setup',
|
'get-started/cli-workflow/workflow-setup',
|
||||||
@@ -48,7 +45,6 @@ const sidebars = {
|
|||||||
{
|
{
|
||||||
label: 'Upgrade',
|
label: 'Upgrade',
|
||||||
type: 'category',
|
type: 'category',
|
||||||
link: { id: 'get-started/upgrade/index', type: 'doc' },
|
|
||||||
items: ['get-started/upgrade/index'],
|
items: ['get-started/upgrade/index'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
BIN
docs/static/img/splash.png
vendored
Normal file
BIN
docs/static/img/splash.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@@ -14,12 +14,11 @@ services:
|
|||||||
auth:
|
auth:
|
||||||
access_control:
|
access_control:
|
||||||
email:
|
email:
|
||||||
allowed_email_domains: ""
|
allowed_email_domains: ''
|
||||||
allowed_emails: ""
|
allowed_emails: ''
|
||||||
blocked_email_domains: ""
|
blocked_email_domains: ''
|
||||||
blocked_emails: ""
|
blocked_emails: ''
|
||||||
url:
|
allowed_redirect_urls: ''
|
||||||
allowed_redirect_urls: ""
|
|
||||||
anonymous_users_enabled: false
|
anonymous_users_enabled: false
|
||||||
client_url: http://localhost:3000
|
client_url: http://localhost:3000
|
||||||
disable_new_users: false
|
disable_new_users: false
|
||||||
@@ -28,11 +27,11 @@ auth:
|
|||||||
passwordless:
|
passwordless:
|
||||||
enabled: false
|
enabled: false
|
||||||
signin_email_verified_required: true
|
signin_email_verified_required: true
|
||||||
template_fetch_url: ""
|
template_fetch_url: ''
|
||||||
gravatar:
|
gravatar:
|
||||||
default: ""
|
default: ''
|
||||||
enabled: true
|
enabled: true
|
||||||
rating: ""
|
rating: ''
|
||||||
locale:
|
locale:
|
||||||
allowed: en
|
allowed: en
|
||||||
default: en
|
default: en
|
||||||
@@ -41,65 +40,65 @@ auth:
|
|||||||
min_length: 3
|
min_length: 3
|
||||||
provider:
|
provider:
|
||||||
apple:
|
apple:
|
||||||
client_id: ""
|
client_id: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
key_id: ""
|
key_id: ''
|
||||||
private_key: ""
|
private_key: ''
|
||||||
scope: name,email
|
scope: name,email
|
||||||
team_id: ""
|
team_id: ''
|
||||||
bitbucket:
|
bitbucket:
|
||||||
client_id: ""
|
client_id: ''
|
||||||
client_secret: ""
|
client_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
facebook:
|
facebook:
|
||||||
client_id: ""
|
client_id: ''
|
||||||
client_secret: ""
|
client_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
scope: email,photos,displayName
|
scope: email,photos,displayName
|
||||||
github:
|
github:
|
||||||
client_id: ""
|
client_id: ''
|
||||||
client_secret: ""
|
client_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
scope: user:email
|
scope: user:email
|
||||||
token_url: ""
|
token_url: ''
|
||||||
user_profile_url: ""
|
user_profile_url: ''
|
||||||
gitlab:
|
gitlab:
|
||||||
base_url: ""
|
base_url: ''
|
||||||
client_id: ""
|
client_id: ''
|
||||||
client_secret: ""
|
client_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
scope: read_user
|
scope: read_user
|
||||||
google:
|
google:
|
||||||
client_id: ""
|
client_id: ''
|
||||||
client_secret: ""
|
client_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
scope: email,profile
|
scope: email,profile
|
||||||
linkedin:
|
linkedin:
|
||||||
client_id: ""
|
client_id: ''
|
||||||
client_secret: ""
|
client_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
scope: r_emailaddress,r_liteprofile
|
scope: r_emailaddress,r_liteprofile
|
||||||
spotify:
|
spotify:
|
||||||
client_id: ""
|
client_id: ''
|
||||||
client_secret: ""
|
client_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
scope: user-read-email,user-read-private
|
scope: user-read-email,user-read-private
|
||||||
strava:
|
strava:
|
||||||
client_id: ""
|
client_id: ''
|
||||||
client_secret: ""
|
client_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
twilio:
|
twilio:
|
||||||
account_sid: ""
|
account_sid: ''
|
||||||
auth_token: ""
|
auth_token: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
messaging_service_id: ""
|
messaging_service_id: ''
|
||||||
twitter:
|
twitter:
|
||||||
consumer_key: ""
|
consumer_key: ''
|
||||||
consumer_secret: ""
|
consumer_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
windows_live:
|
windows_live:
|
||||||
client_id: ""
|
client_id: ''
|
||||||
client_secret: ""
|
client_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
scope: wl.basic,wl.emails,wl.contacts_emails
|
scope: wl.basic,wl.emails,wl.contacts_emails
|
||||||
sms:
|
sms:
|
||||||
@@ -108,13 +107,13 @@ auth:
|
|||||||
enabled: false
|
enabled: false
|
||||||
provider:
|
provider:
|
||||||
twilio:
|
twilio:
|
||||||
account_sid: ""
|
account_sid: ''
|
||||||
auth_token: ""
|
auth_token: ''
|
||||||
from: ""
|
from: ''
|
||||||
messaging_service_id: ""
|
messaging_service_id: ''
|
||||||
smtp:
|
smtp:
|
||||||
host: nhost_mailhog
|
host: nhost_mailhog
|
||||||
method: ""
|
method: ''
|
||||||
pass: password
|
pass: password
|
||||||
port: 1765
|
port: 1765
|
||||||
secure: false
|
secure: false
|
||||||
|
|||||||
@@ -2,14 +2,32 @@
|
|||||||
|
|
||||||
This demo is a work in progress, further improvements are to come
|
This demo is a work in progress, further improvements are to come
|
||||||
|
|
||||||
### Installation
|
## Get started
|
||||||
|
|
||||||
First, clone this repo. Then run the commands:
|
1. Clone the repository
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/nhost/nhost
|
||||||
|
cd nhost
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install dependencies
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd examples/nextjs
|
cd examples/nextjs
|
||||||
yarn
|
pnpm install
|
||||||
yarn dev
|
```
|
||||||
|
|
||||||
|
3. Terminal 1: Start Nhost
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nhost dev
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Terminal 2: Start React App
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to use this demo with your own cloud instance:
|
If you want to use this demo with your own cloud instance:
|
||||||
@@ -18,8 +36,3 @@ If you want to use this demo with your own cloud instance:
|
|||||||
- don't forget to change the client URL in the Nhost console so email verification will work: `Users -> Login Settings -> Client login URLs`: `http://localhost:4000`
|
- don't forget to change the client URL in the Nhost console so email verification will work: `Users -> Login Settings -> Client login URLs`: `http://localhost:4000`
|
||||||
|
|
||||||
If you want to use a local Nhost instance, start the CLI in parallel to Nextjs:
|
If you want to use a local Nhost instance, start the CLI in parallel to Nextjs:
|
||||||
|
|
||||||
```sh
|
|
||||||
# Inside examples/nextjs
|
|
||||||
nhost -d
|
|
||||||
```
|
|
||||||
|
|||||||
13
examples/nextjs/next.config.js
Normal file
13
examples/nextjs/next.config.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {}
|
||||||
|
|
||||||
|
const pkg = require('./package.json')
|
||||||
|
// * Only required to make it work with the monorepo. Is not required otherwise
|
||||||
|
const withTM = require('next-transpile-modules')(
|
||||||
|
// * All references to workspace packages are transpiled
|
||||||
|
Object.entries(pkg.dependencies)
|
||||||
|
.filter(([name, version]) => version.startsWith('workspace'))
|
||||||
|
.map(([name]) => name)
|
||||||
|
)
|
||||||
|
|
||||||
|
module.exports = withTM(nextConfig)
|
||||||
@@ -15,8 +15,7 @@ auth:
|
|||||||
allowed_emails: ''
|
allowed_emails: ''
|
||||||
blocked_email_domains: ''
|
blocked_email_domains: ''
|
||||||
blocked_emails: ''
|
blocked_emails: ''
|
||||||
url:
|
allowed_redirect_urls: ''
|
||||||
allowed_redirect_urls: ''
|
|
||||||
anonymous_users_enabled: false
|
anonymous_users_enabled: false
|
||||||
client_url: http://localhost:3000
|
client_url: http://localhost:3000
|
||||||
disable_new_users: false
|
disable_new_users: false
|
||||||
|
|||||||
@@ -11,4 +11,4 @@
|
|||||||
max_connections: 50
|
max_connections: 50
|
||||||
retries: 20
|
retries: 20
|
||||||
use_prepared_statements: true
|
use_prepared_statements: true
|
||||||
tables: "!include default/tables/tables.yaml"
|
tables: '!include default/tables/tables.yaml'
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ configuration:
|
|||||||
update: updateAuthProviders
|
update: updateAuthProviders
|
||||||
update_by_pk: updateAuthProvider
|
update_by_pk: updateAuthProvider
|
||||||
array_relationships:
|
array_relationships:
|
||||||
- name: userProviders
|
- name: userProviders
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: provider_id
|
column: provider_id
|
||||||
table:
|
table:
|
||||||
name: user_providers
|
name: user_providers
|
||||||
schema: auth
|
schema: auth
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ configuration:
|
|||||||
update: updateAuthRefreshTokens
|
update: updateAuthRefreshTokens
|
||||||
update_by_pk: updateAuthRefreshToken
|
update_by_pk: updateAuthRefreshToken
|
||||||
object_relationships:
|
object_relationships:
|
||||||
- name: user
|
- name: user
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: user_id
|
foreign_key_constraint_on: user_id
|
||||||
|
|||||||
@@ -16,17 +16,17 @@ configuration:
|
|||||||
update: updateAuthRoles
|
update: updateAuthRoles
|
||||||
update_by_pk: updateAuthRole
|
update_by_pk: updateAuthRole
|
||||||
array_relationships:
|
array_relationships:
|
||||||
- name: userRoles
|
- name: userRoles
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: role
|
column: role
|
||||||
table:
|
table:
|
||||||
name: user_roles
|
name: user_roles
|
||||||
schema: auth
|
schema: auth
|
||||||
- name: usersByDefaultRole
|
- name: usersByDefaultRole
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: default_role
|
column: default_role
|
||||||
table:
|
table:
|
||||||
name: users
|
name: users
|
||||||
schema: auth
|
schema: auth
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ configuration:
|
|||||||
update: updateAuthUserProviders
|
update: updateAuthUserProviders
|
||||||
update_by_pk: updateAuthUserProvider
|
update_by_pk: updateAuthUserProvider
|
||||||
object_relationships:
|
object_relationships:
|
||||||
- name: provider
|
- name: provider
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: provider_id
|
foreign_key_constraint_on: provider_id
|
||||||
- name: user
|
- name: user
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: user_id
|
foreign_key_constraint_on: user_id
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ configuration:
|
|||||||
update: updateAuthUserRoles
|
update: updateAuthUserRoles
|
||||||
update_by_pk: updateAuthUserRole
|
update_by_pk: updateAuthUserRole
|
||||||
object_relationships:
|
object_relationships:
|
||||||
- name: roleByRole
|
- name: roleByRole
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: role
|
foreign_key_constraint_on: role
|
||||||
- name: user
|
- name: user
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: user_id
|
foreign_key_constraint_on: user_id
|
||||||
|
|||||||
@@ -38,28 +38,28 @@ configuration:
|
|||||||
update: updateUsers
|
update: updateUsers
|
||||||
update_by_pk: updateUser
|
update_by_pk: updateUser
|
||||||
object_relationships:
|
object_relationships:
|
||||||
- name: defaultRoleByRole
|
- name: defaultRoleByRole
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: default_role
|
foreign_key_constraint_on: default_role
|
||||||
array_relationships:
|
array_relationships:
|
||||||
- name: refreshTokens
|
- name: refreshTokens
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: user_id
|
column: user_id
|
||||||
table:
|
table:
|
||||||
name: refresh_tokens
|
name: refresh_tokens
|
||||||
schema: auth
|
schema: auth
|
||||||
- name: roles
|
- name: roles
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: user_id
|
column: user_id
|
||||||
table:
|
table:
|
||||||
name: user_roles
|
name: user_roles
|
||||||
schema: auth
|
schema: auth
|
||||||
- name: userProviders
|
- name: userProviders
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: user_id
|
column: user_id
|
||||||
table:
|
table:
|
||||||
name: user_providers
|
name: user_providers
|
||||||
schema: auth
|
schema: auth
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ table:
|
|||||||
name: books
|
name: books
|
||||||
schema: public
|
schema: public
|
||||||
select_permissions:
|
select_permissions:
|
||||||
- permission:
|
- permission:
|
||||||
columns:
|
columns:
|
||||||
- id
|
- id
|
||||||
- title
|
- title
|
||||||
filter: {}
|
filter: {}
|
||||||
role: user
|
role: user
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ table:
|
|||||||
name: test
|
name: test
|
||||||
schema: public
|
schema: public
|
||||||
select_permissions:
|
select_permissions:
|
||||||
- permission:
|
- permission:
|
||||||
columns:
|
columns:
|
||||||
- bidon
|
- bidon
|
||||||
- id
|
- id
|
||||||
filter: {}
|
filter: {}
|
||||||
role: user
|
role: user
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ configuration:
|
|||||||
update: updateBuckets
|
update: updateBuckets
|
||||||
update_by_pk: updateBucket
|
update_by_pk: updateBucket
|
||||||
array_relationships:
|
array_relationships:
|
||||||
- name: files
|
- name: files
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: bucket_id
|
column: bucket_id
|
||||||
table:
|
table:
|
||||||
name: files
|
name: files
|
||||||
schema: storage
|
schema: storage
|
||||||
|
|||||||
@@ -25,6 +25,6 @@ configuration:
|
|||||||
update: updateFiles
|
update: updateFiles
|
||||||
update_by_pk: updateFile
|
update_by_pk: updateFile
|
||||||
object_relationships:
|
object_relationships:
|
||||||
- name: bucket
|
- name: bucket
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: bucket_id
|
foreign_key_constraint_on: bucket_id
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
- "!include auth_provider_requests.yaml"
|
- '!include auth_provider_requests.yaml'
|
||||||
- "!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_providers.yaml"
|
- '!include auth_user_providers.yaml'
|
||||||
- "!include auth_user_roles.yaml"
|
- '!include auth_user_roles.yaml'
|
||||||
- "!include auth_users.yaml"
|
- '!include auth_users.yaml'
|
||||||
- "!include public_books.yaml"
|
- '!include public_books.yaml'
|
||||||
- "!include storage_buckets.yaml"
|
- '!include storage_buckets.yaml'
|
||||||
- "!include storage_files.yaml"
|
- '!include storage_files.yaml'
|
||||||
|
|||||||
@@ -6,13 +6,23 @@
|
|||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"prettier": "prettier --check .",
|
||||||
|
"prettier:fix": "prettier --write .",
|
||||||
|
"lint": "eslint . --ext .ts,.tsx",
|
||||||
|
"lint:fix": "eslint . --ext .ts,.tsx --fix",
|
||||||
|
"verify": "run-p prettier lint",
|
||||||
|
"verify:fix": "run-p prettier:fix lint:fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.5.10",
|
"@apollo/client": "^3.5.10",
|
||||||
"@nhost/nextjs": "^1.0.10",
|
"@nhost/apollo": "workspace:*",
|
||||||
"@nhost/react": "^0.5.0",
|
"@nhost/core": "workspace:*",
|
||||||
"@nhost/react-apollo": "^4.0.10",
|
"@nhost/nextjs": "workspace:*",
|
||||||
|
"@nhost/react": "workspace:*",
|
||||||
|
"@nhost/react-apollo": "workspace:*",
|
||||||
|
"@nhost/nhost-js": "workspace:*",
|
||||||
|
"@nhost/hasura-auth-js": "workspace:*",
|
||||||
|
"@nhost/hasura-storage-js": "workspace:*",
|
||||||
"graphql": "^16.3.0",
|
"graphql": "^16.3.0",
|
||||||
"next": "12.1.0",
|
"next": "12.1.0",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
@@ -22,9 +32,10 @@
|
|||||||
"@types/node": "17.0.23",
|
"@types/node": "17.0.23",
|
||||||
"@types/react": "17.0.43",
|
"@types/react": "17.0.43",
|
||||||
"@xstate/inspect": "^0.6.2",
|
"@xstate/inspect": "^0.6.2",
|
||||||
"eslint": "8.8.0",
|
|
||||||
"eslint-config-next": "12.0.10",
|
"eslint-config-next": "12.0.10",
|
||||||
|
"next-transpile-modules": "^9.0.0",
|
||||||
"typescript": "4.5.5",
|
"typescript": "4.5.5",
|
||||||
"ws": "^8.5.0"
|
"ws": "^8.5.0",
|
||||||
|
"xstate": "^4.30.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,14 @@ if (typeof window !== 'undefined' && process.env.NEXT_PUBLIC_DEBUG) {
|
|||||||
const nhost = new NhostClient({ backendUrl: BACKEND_URL })
|
const nhost = new NhostClient({ backendUrl: BACKEND_URL })
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }: AppProps) {
|
function MyApp({ Component, pageProps }: AppProps) {
|
||||||
|
// * Monorepo-related. See: https://stackoverflow.com/questions/71843247/react-nextjs-type-error-component-cannot-be-used-as-a-jsx-component
|
||||||
|
const AnyComponent = Component as any
|
||||||
return (
|
return (
|
||||||
<NhostNextProvider nhost={nhost} initial={pageProps.nhostSession}>
|
<NhostNextProvider nhost={nhost} initial={pageProps.nhostSession}>
|
||||||
<NhostApolloProvider nhost={nhost}>
|
<NhostApolloProvider nhost={nhost}>
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<Header />
|
<Header />
|
||||||
<Component {...pageProps} />
|
<AnyComponent {...pageProps} />
|
||||||
</div>
|
</div>
|
||||||
</NhostApolloProvider>
|
</NhostApolloProvider>
|
||||||
</NhostNextProvider>
|
</NhostNextProvider>
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"dom",
|
|
||||||
"dom.iterable",
|
|
||||||
"esnext"
|
|
||||||
],
|
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": false,
|
"strict": false,
|
||||||
@@ -19,11 +15,6 @@
|
|||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"incremental": true
|
"incremental": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||||
"next-env.d.ts",
|
"exclude": ["node_modules"]
|
||||||
"**/*.ts",
|
}
|
||||||
"**/*.tsx"],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
|||||||
REACT_APP_BACKEND_URL=http://localhost:1337
|
VITE_NHOST_URL=http://localhost:1337
|
||||||
@@ -72,25 +72,33 @@ This example app has some work in progress:
|
|||||||
|
|
||||||
## Get started
|
## Get started
|
||||||
|
|
||||||
1. Install dependencies
|
1. Clone the repository
|
||||||
|
|
||||||
```
|
```sh
|
||||||
npm install
|
git clone https://github.com/nhost/nhost
|
||||||
|
cd nhost
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Terminal 1: Start Nhost
|
2. Install dependencies
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd examples/react-apollo-crm
|
||||||
|
pnpm install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
3. Terminal 1: Start Nhost
|
||||||
|
|
||||||
|
```sh
|
||||||
nhost dev
|
nhost dev
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Terminal 2: Start React App
|
4. Terminal 2: Start React App
|
||||||
|
|
||||||
```
|
```sh
|
||||||
npm run start
|
pnpm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Terminal 3: Start GraphQL Codegens
|
5. Terminal 3: Start GraphQL Codegens
|
||||||
|
|
||||||
> Make sure that the Nhost backend in step 2 has started and is available before you run this command
|
> Make sure that the Nhost backend in step 2 has started and is available before you run this command
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
client: {
|
client: {
|
||||||
service: {
|
service: {
|
||||||
name: "backend",
|
name: 'backend',
|
||||||
url: "http://localhost:1337/v1/graphql",
|
url: 'http://localhost:1337/v1/graphql',
|
||||||
headers: {
|
headers: {
|
||||||
"x-hasura-admin-secret": "nhost-admin-secret",
|
'x-hasura-admin-secret': 'nhost-admin-secret'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
includes: ["src/**/*.graphql", "src/**/*.gql"],
|
includes: ['src/**/*.graphql', 'src/**/*.gql']
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ schema: http://localhost:1337/v1/graphql
|
|||||||
headers:
|
headers:
|
||||||
x-hasura-admin-secret: nhost-admin-secret
|
x-hasura-admin-secret: nhost-admin-secret
|
||||||
documents:
|
documents:
|
||||||
- "src/**/*.graphql"
|
- 'src/**/*.graphql'
|
||||||
- "src/**/*.gql"
|
- 'src/**/*.gql'
|
||||||
generates:
|
generates:
|
||||||
src/utils/__generated__/graphql.ts:
|
src/utils/__generated__/graphql.ts:
|
||||||
plugins:
|
plugins:
|
||||||
- "typescript"
|
- 'typescript'
|
||||||
- "typescript-operations"
|
- 'typescript-operations'
|
||||||
- "typescript-react-apollo"
|
- 'typescript-react-apollo'
|
||||||
config:
|
config:
|
||||||
withRefetchFn: true
|
withRefetchFn: true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NhostClient } from '@nhost/nhost-js'
|
import { NhostClient } from '@nhost/nhost-js'
|
||||||
|
|
||||||
const nhost = new NhostClient({
|
const nhost = new NhostClient({
|
||||||
backendUrl: process.env.NHOST_BACKEND_URL!
|
backendUrl: process.env.NHOST_BACKEND_URL!
|
||||||
})
|
})
|
||||||
|
|
||||||
export { nhost }
|
export { nhost }
|
||||||
|
|||||||
@@ -2,104 +2,104 @@ import { Request, Response } from 'express'
|
|||||||
import { nhost } from '../../_utils/nhost'
|
import { nhost } from '../../_utils/nhost'
|
||||||
|
|
||||||
const handler = async (req: Request, res: Response) => {
|
const handler = async (req: Request, res: Response) => {
|
||||||
if (req.headers['nhsot-webhook-secret'] !== process.env.NHSOT_WEBHOOK_SECRET) {
|
if (req.headers['nhsot-webhook-secret'] !== process.env.NHSOT_WEBHOOK_SECRET) {
|
||||||
return res.status(401).send('Unauthorized')
|
return res.status(401).send('Unauthorized')
|
||||||
}
|
}
|
||||||
|
|
||||||
// User who just signed up
|
// User who just signed up
|
||||||
const user = req.body.event.data.new
|
const user = req.body.event.data.new
|
||||||
|
|
||||||
// Get the user's email domain
|
// Get the user's email domain
|
||||||
const emailDomain = user.email.split('@')[1]
|
const emailDomain = user.email.split('@')[1]
|
||||||
|
|
||||||
// Check if a company with the user's email domain already exists.
|
// Check if a company with the user's email domain already exists.
|
||||||
const GET_COMPANY_WITH_EMAIL_DOMAIN = `
|
const GET_COMPANY_WITH_EMAIL_DOMAIN = `
|
||||||
query getCompanyWithEmailDomain($emailDomain: String!) {
|
query getCompanyWithEmailDomain($emailDomain: String!) {
|
||||||
companies(where: { emailDomain: { _eq: $emailDomain } }) {
|
companies(where: { emailDomain: { _eq: $emailDomain } }) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
const { data, error } = await nhost.graphql.request(
|
const { data, error } = await nhost.graphql.request(
|
||||||
GET_COMPANY_WITH_EMAIL_DOMAIN,
|
GET_COMPANY_WITH_EMAIL_DOMAIN,
|
||||||
{
|
{
|
||||||
emailDomain
|
emailDomain
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
'x-hasura-admin-secret': process.env.NHOST_ADMIN_SECRET
|
'x-hasura-admin-secret': process.env.NHOST_ADMIN_SECRET
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return res.status(500).send(error)
|
return res.status(500).send(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { companies } = data as any
|
const { companies } = data as any
|
||||||
|
|
||||||
let companyId
|
let companyId
|
||||||
if (companies.length === 1) {
|
if (companies.length === 1) {
|
||||||
// if a company already exists, use that company's id
|
// if a company already exists, use that company's id
|
||||||
companyId = companies[0].id
|
companyId = companies[0].id
|
||||||
} else {
|
} else {
|
||||||
// else, create a new company for the newly created user with the same email domain as the user
|
// else, create a new company for the newly created user with the same email domain as the user
|
||||||
const CREATE_NEW_COMPANY = `
|
const CREATE_NEW_COMPANY = `
|
||||||
mutation insertCompany($emailDomain: String!) {
|
mutation insertCompany($emailDomain: String!) {
|
||||||
insertCompany(object: { name: $emailDomain, emailDomain: $emailDomain }) {
|
insertCompany(object: { name: $emailDomain, emailDomain: $emailDomain }) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
const { data, error } = await nhost.graphql.request(
|
const { data, error } = await nhost.graphql.request(
|
||||||
CREATE_NEW_COMPANY,
|
CREATE_NEW_COMPANY,
|
||||||
{
|
{
|
||||||
emailDomain
|
emailDomain
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
'x-hasura-admin-secret': process.env.NHOST_ADMIN_SECRET
|
'x-hasura-admin-secret': process.env.NHOST_ADMIN_SECRET
|
||||||
}
|
}
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return res.status(500).send(error)
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const { insertCompany } = data as any
|
if (error) {
|
||||||
|
return res.status(500).send(error)
|
||||||
|
}
|
||||||
|
|
||||||
companyId = insertCompany.id
|
const { insertCompany } = data as any
|
||||||
}
|
|
||||||
|
|
||||||
// We now have the company id of an existing, or a newly created company.
|
companyId = insertCompany.id
|
||||||
// Now let's add the user to the company.
|
}
|
||||||
|
|
||||||
const ADD_USER_TO_COMPANY = `
|
// We now have the company id of an existing, or a newly created company.
|
||||||
|
// Now let's add the user to the company.
|
||||||
|
|
||||||
|
const ADD_USER_TO_COMPANY = `
|
||||||
mutation addUserToCompany($userId: uuid!, $companyId: uuid!) {
|
mutation addUserToCompany($userId: uuid!, $companyId: uuid!) {
|
||||||
insertCompanyUser(object: {userId: $userId, companyId: $companyId}) {
|
insertCompanyUser(object: {userId: $userId, companyId: $companyId}) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
const { error: addUserToCompanyError } = await nhost.graphql.request(
|
const { error: addUserToCompanyError } = await nhost.graphql.request(
|
||||||
ADD_USER_TO_COMPANY,
|
ADD_USER_TO_COMPANY,
|
||||||
{
|
{
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
companyId
|
companyId
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
'x-hasura-admin-secret': process.env.NHOST_ADMIN_SECRET
|
'x-hasura-admin-secret': process.env.NHOST_ADMIN_SECRET
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (addUserToCompanyError) {
|
if (addUserToCompanyError) {
|
||||||
return res.status(500).send(error)
|
return res.status(500).send(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(200).send(`OK`)
|
res.status(200).send(`OK`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default handler
|
export default handler
|
||||||
|
|||||||
18
examples/react-apollo-crm/index.html
Normal file
18
examples/react-apollo-crm/index.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta name="description" content="Web site created using create-react-app" />
|
||||||
|
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||||
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
<title>React App</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -6,12 +6,11 @@ services:
|
|||||||
auth:
|
auth:
|
||||||
access_control:
|
access_control:
|
||||||
email:
|
email:
|
||||||
allowed_email_domains: ""
|
allowed_email_domains: ''
|
||||||
allowed_emails: ""
|
allowed_emails: ''
|
||||||
blocked_email_domains: ""
|
blocked_email_domains: ''
|
||||||
blocked_emails: ""
|
blocked_emails: ''
|
||||||
url:
|
allowed_redirect_urls: ''
|
||||||
allowed_redirect_urls: ""
|
|
||||||
anonymous_users_enabled: false
|
anonymous_users_enabled: false
|
||||||
client_url: http://localhost:3000
|
client_url: http://localhost:3000
|
||||||
disable_new_users: false
|
disable_new_users: false
|
||||||
@@ -19,12 +18,12 @@ auth:
|
|||||||
enabled: false
|
enabled: false
|
||||||
passwordless:
|
passwordless:
|
||||||
enabled: false
|
enabled: false
|
||||||
template_fetch_url: ""
|
template_fetch_url: ''
|
||||||
signin_email_verified_required: false
|
signin_email_verified_required: false
|
||||||
gravatar:
|
gravatar:
|
||||||
default: ""
|
default: ''
|
||||||
enabled: true
|
enabled: true
|
||||||
rating: ""
|
rating: ''
|
||||||
locale:
|
locale:
|
||||||
allowed: en
|
allowed: en
|
||||||
default: en
|
default: en
|
||||||
@@ -33,65 +32,65 @@ auth:
|
|||||||
min_length: 3
|
min_length: 3
|
||||||
provider:
|
provider:
|
||||||
apple:
|
apple:
|
||||||
client_id: ""
|
client_id: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
key_id: ""
|
key_id: ''
|
||||||
private_key: ""
|
private_key: ''
|
||||||
scope: name,email
|
scope: name,email
|
||||||
team_id: ""
|
team_id: ''
|
||||||
bitbucket:
|
bitbucket:
|
||||||
client_id: ""
|
client_id: ''
|
||||||
client_secret: ""
|
client_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
facebook:
|
facebook:
|
||||||
client_id: ""
|
client_id: ''
|
||||||
client_secret: ""
|
client_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
scope: email,photos,displayName
|
scope: email,photos,displayName
|
||||||
github:
|
github:
|
||||||
client_id: ""
|
client_id: ''
|
||||||
client_secret: ""
|
client_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
scope: user:email
|
scope: user:email
|
||||||
token_url: ""
|
token_url: ''
|
||||||
user_profile_url: ""
|
user_profile_url: ''
|
||||||
gitlab:
|
gitlab:
|
||||||
base_url: ""
|
base_url: ''
|
||||||
client_id: ""
|
client_id: ''
|
||||||
client_secret: ""
|
client_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
scope: read_user
|
scope: read_user
|
||||||
google:
|
google:
|
||||||
client_id: ""
|
client_id: ''
|
||||||
client_secret: ""
|
client_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
scope: email,profile
|
scope: email,profile
|
||||||
linkedin:
|
linkedin:
|
||||||
client_id: ""
|
client_id: ''
|
||||||
client_secret: ""
|
client_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
scope: r_emailaddress,r_liteprofile
|
scope: r_emailaddress,r_liteprofile
|
||||||
spotify:
|
spotify:
|
||||||
client_id: ""
|
client_id: ''
|
||||||
client_secret: ""
|
client_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
scope: user-read-email,user-read-private
|
scope: user-read-email,user-read-private
|
||||||
strava:
|
strava:
|
||||||
client_id: ""
|
client_id: ''
|
||||||
client_secret: ""
|
client_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
twilio:
|
twilio:
|
||||||
account_sid: ""
|
account_sid: ''
|
||||||
auth_token: ""
|
auth_token: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
messaging_service_id: ""
|
messaging_service_id: ''
|
||||||
twitter:
|
twitter:
|
||||||
consumer_key: ""
|
consumer_key: ''
|
||||||
consumer_secret: ""
|
consumer_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
windows_live:
|
windows_live:
|
||||||
client_id: ""
|
client_id: ''
|
||||||
client_secret: ""
|
client_secret: ''
|
||||||
enabled: false
|
enabled: false
|
||||||
scope: wl.basic,wl.emails,wl.contacts_emails
|
scope: wl.basic,wl.emails,wl.contacts_emails
|
||||||
sms:
|
sms:
|
||||||
@@ -100,13 +99,13 @@ auth:
|
|||||||
enabled: false
|
enabled: false
|
||||||
provider:
|
provider:
|
||||||
twilio:
|
twilio:
|
||||||
account_sid: ""
|
account_sid: ''
|
||||||
auth_token: ""
|
auth_token: ''
|
||||||
from: ""
|
from: ''
|
||||||
messaging_service_id: ""
|
messaging_service_id: ''
|
||||||
smtp:
|
smtp:
|
||||||
host: nhost_mailhog
|
host: nhost_mailhog
|
||||||
method: ""
|
method: ''
|
||||||
pass: password
|
pass: password
|
||||||
port: 1586
|
port: 1586
|
||||||
secure: false
|
secure: false
|
||||||
|
|||||||
@@ -7,9 +7,7 @@
|
|||||||
<h2>Verify Email</h2>
|
<h2>Verify Email</h2>
|
||||||
<p>Use this link to verify your email:</p>
|
<p>Use this link to verify your email:</p>
|
||||||
<p>
|
<p>
|
||||||
<a
|
<a href="${serverUrl}/verify?&ticket=${ticket}&type=emailVerify&redirectTo=${redirectTo}">
|
||||||
href="${serverUrl}/verify?&ticket=${ticket}&type=emailVerify&redirectTo=${redirectTo}"
|
|
||||||
>
|
|
||||||
Verify Email
|
Verify Email
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -7,9 +7,7 @@
|
|||||||
<h2>Reset Password</h2>
|
<h2>Reset Password</h2>
|
||||||
<p>Use this link to reset your password:</p>
|
<p>Use this link to reset your password:</p>
|
||||||
<p>
|
<p>
|
||||||
<a
|
<a href="${serverUrl}/verify?&ticket=${ticket}&type=passwordReset&redirectTo=${redirectTo}">
|
||||||
href="${serverUrl}/verify?&ticket=${ticket}&type=passwordReset&redirectTo=${redirectTo}"
|
|
||||||
>
|
|
||||||
Reset password
|
Reset password
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
type Mutation {
|
type Mutation {
|
||||||
actionName(
|
actionName(arg1: SampleInput!): SampleOutput
|
||||||
arg1: SampleInput!
|
|
||||||
): SampleOutput
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input SampleInput {
|
input SampleInput {
|
||||||
@@ -12,4 +10,3 @@ input SampleInput {
|
|||||||
type SampleOutput {
|
type SampleOutput {
|
||||||
accessToken: String!
|
accessToken: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
actions:
|
actions:
|
||||||
- name: actionName
|
- name: actionName
|
||||||
definition:
|
definition:
|
||||||
kind: synchronous
|
kind: synchronous
|
||||||
handler: http://host.docker.internal:3000
|
handler: http://host.docker.internal:3000
|
||||||
permissions:
|
permissions:
|
||||||
- role: user
|
- role: user
|
||||||
custom_types:
|
custom_types:
|
||||||
enums: []
|
enums: []
|
||||||
input_objects:
|
input_objects:
|
||||||
- name: SampleInput
|
- name: SampleInput
|
||||||
objects:
|
objects:
|
||||||
- name: SampleOutput
|
- name: SampleOutput
|
||||||
scalars: []
|
scalars: []
|
||||||
|
|||||||
@@ -11,4 +11,4 @@
|
|||||||
max_connections: 50
|
max_connections: 50
|
||||||
retries: 20
|
retries: 20
|
||||||
use_prepared_statements: true
|
use_prepared_statements: true
|
||||||
tables: "!include default/tables/tables.yaml"
|
tables: '!include default/tables/tables.yaml'
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ configuration:
|
|||||||
update: updateAuthProviders
|
update: updateAuthProviders
|
||||||
update_by_pk: updateAuthProvider
|
update_by_pk: updateAuthProvider
|
||||||
array_relationships:
|
array_relationships:
|
||||||
- name: userProviders
|
- name: userProviders
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: provider_id
|
column: provider_id
|
||||||
table:
|
table:
|
||||||
name: user_providers
|
name: user_providers
|
||||||
schema: auth
|
schema: auth
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ configuration:
|
|||||||
update: updateAuthRefreshTokens
|
update: updateAuthRefreshTokens
|
||||||
update_by_pk: updateAuthRefreshToken
|
update_by_pk: updateAuthRefreshToken
|
||||||
object_relationships:
|
object_relationships:
|
||||||
- name: user
|
- name: user
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: user_id
|
foreign_key_constraint_on: user_id
|
||||||
|
|||||||
@@ -16,17 +16,17 @@ configuration:
|
|||||||
update: updateAuthRoles
|
update: updateAuthRoles
|
||||||
update_by_pk: updateAuthRole
|
update_by_pk: updateAuthRole
|
||||||
array_relationships:
|
array_relationships:
|
||||||
- name: userRoles
|
- name: userRoles
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: role
|
column: role
|
||||||
table:
|
table:
|
||||||
name: user_roles
|
name: user_roles
|
||||||
schema: auth
|
schema: auth
|
||||||
- name: usersByDefaultRole
|
- name: usersByDefaultRole
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: default_role
|
column: default_role
|
||||||
table:
|
table:
|
||||||
name: users
|
name: users
|
||||||
schema: auth
|
schema: auth
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ configuration:
|
|||||||
update: updateAuthUserProviders
|
update: updateAuthUserProviders
|
||||||
update_by_pk: updateAuthUserProvider
|
update_by_pk: updateAuthUserProvider
|
||||||
object_relationships:
|
object_relationships:
|
||||||
- name: provider
|
- name: provider
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: provider_id
|
foreign_key_constraint_on: provider_id
|
||||||
- name: user
|
- name: user
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: user_id
|
foreign_key_constraint_on: user_id
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ configuration:
|
|||||||
update: updateAuthUserRoles
|
update: updateAuthUserRoles
|
||||||
update_by_pk: updateAuthUserRole
|
update_by_pk: updateAuthUserRole
|
||||||
object_relationships:
|
object_relationships:
|
||||||
- name: roleByRole
|
- name: roleByRole
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: role
|
foreign_key_constraint_on: role
|
||||||
- name: user
|
- name: user
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: user_id
|
foreign_key_constraint_on: user_id
|
||||||
|
|||||||
@@ -38,79 +38,79 @@ configuration:
|
|||||||
update: updateUsers
|
update: updateUsers
|
||||||
update_by_pk: updateUser
|
update_by_pk: updateUser
|
||||||
object_relationships:
|
object_relationships:
|
||||||
- name: companyUser
|
- name: companyUser
|
||||||
using:
|
using:
|
||||||
manual_configuration:
|
manual_configuration:
|
||||||
column_mapping:
|
column_mapping:
|
||||||
id: user_id
|
id: user_id
|
||||||
insertion_order: null
|
insertion_order: null
|
||||||
remote_table:
|
remote_table:
|
||||||
name: company_users
|
name: company_users
|
||||||
schema: public
|
schema: public
|
||||||
- name: defaultRoleByRole
|
- name: defaultRoleByRole
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: default_role
|
foreign_key_constraint_on: default_role
|
||||||
array_relationships:
|
array_relationships:
|
||||||
- name: customer_comments
|
- name: customer_comments
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: user_id
|
column: user_id
|
||||||
table:
|
table:
|
||||||
name: customer_comments
|
name: customer_comments
|
||||||
schema: public
|
schema: public
|
||||||
- name: customers
|
- name: customers
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: user_id
|
column: user_id
|
||||||
table:
|
table:
|
||||||
name: customers
|
name: customers
|
||||||
schema: public
|
schema: public
|
||||||
- name: refreshTokens
|
- name: refreshTokens
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: user_id
|
column: user_id
|
||||||
table:
|
table:
|
||||||
name: refresh_tokens
|
name: refresh_tokens
|
||||||
schema: auth
|
schema: auth
|
||||||
- name: roles
|
- name: roles
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: user_id
|
column: user_id
|
||||||
table:
|
table:
|
||||||
name: user_roles
|
name: user_roles
|
||||||
schema: auth
|
schema: auth
|
||||||
- name: userProviders
|
- name: userProviders
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: user_id
|
column: user_id
|
||||||
table:
|
table:
|
||||||
name: user_providers
|
name: user_providers
|
||||||
schema: auth
|
schema: auth
|
||||||
select_permissions:
|
select_permissions:
|
||||||
- permission:
|
- permission:
|
||||||
columns:
|
columns:
|
||||||
- avatar_url
|
- avatar_url
|
||||||
- display_name
|
- display_name
|
||||||
- email
|
- email
|
||||||
- id
|
- id
|
||||||
filter:
|
filter:
|
||||||
companyUser:
|
companyUser:
|
||||||
company:
|
company:
|
||||||
companyUsers:
|
companyUsers:
|
||||||
user_id:
|
user_id:
|
||||||
_eq: X-Hasura-User-Id
|
_eq: X-Hasura-User-Id
|
||||||
role: user
|
role: user
|
||||||
event_triggers:
|
event_triggers:
|
||||||
- definition:
|
- definition:
|
||||||
enable_manual: false
|
enable_manual: false
|
||||||
insert:
|
insert:
|
||||||
columns: "*"
|
columns: '*'
|
||||||
headers:
|
headers:
|
||||||
- name: nhost-webhook-secret
|
- name: nhost-webhook-secret
|
||||||
value_from_env: NHOST_WEBHOOK_SECRET
|
value_from_env: NHOST_WEBHOOK_SECRET
|
||||||
name: users-insert-create-company-connection
|
name: users-insert-create-company-connection
|
||||||
retry_conf:
|
retry_conf:
|
||||||
interval_sec: 10
|
interval_sec: 10
|
||||||
num_retries: 0
|
num_retries: 0
|
||||||
timeout_sec: 60
|
timeout_sec: 60
|
||||||
webhook: "{{NHOST_BACKEND_URL}}/v1/functions/users/insert/create-company-connection"
|
webhook: '{{NHOST_BACKEND_URL}}/v1/functions/users/insert/create-company-connection'
|
||||||
|
|||||||
@@ -18,29 +18,29 @@ configuration:
|
|||||||
update: updateCompanies
|
update: updateCompanies
|
||||||
update_by_pk: updateCompany
|
update_by_pk: updateCompany
|
||||||
array_relationships:
|
array_relationships:
|
||||||
- name: companyUsers
|
- name: companyUsers
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: company_id
|
column: company_id
|
||||||
table:
|
table:
|
||||||
name: company_users
|
name: company_users
|
||||||
schema: public
|
schema: public
|
||||||
- name: customers
|
- name: customers
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: company_id
|
column: company_id
|
||||||
table:
|
table:
|
||||||
name: customers
|
name: customers
|
||||||
schema: public
|
schema: public
|
||||||
select_permissions:
|
select_permissions:
|
||||||
- permission:
|
- permission:
|
||||||
columns:
|
columns:
|
||||||
- id
|
- id
|
||||||
- created_at
|
- created_at
|
||||||
- updated_at
|
- updated_at
|
||||||
- name
|
- name
|
||||||
filter:
|
filter:
|
||||||
companyUsers:
|
companyUsers:
|
||||||
user_id:
|
user_id:
|
||||||
_eq: X-Hasura-User-Id
|
_eq: X-Hasura-User-Id
|
||||||
role: user
|
role: user
|
||||||
|
|||||||
@@ -20,23 +20,23 @@ configuration:
|
|||||||
update: updateCompanyUsers
|
update: updateCompanyUsers
|
||||||
update_by_pk: updateCompanyUser
|
update_by_pk: updateCompanyUser
|
||||||
object_relationships:
|
object_relationships:
|
||||||
- name: company
|
- name: company
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: company_id
|
foreign_key_constraint_on: company_id
|
||||||
- name: user
|
- name: user
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: user_id
|
foreign_key_constraint_on: user_id
|
||||||
select_permissions:
|
select_permissions:
|
||||||
- permission:
|
- permission:
|
||||||
columns:
|
columns:
|
||||||
- id
|
- id
|
||||||
- created_at
|
- created_at
|
||||||
- updated_at
|
- updated_at
|
||||||
- company_id
|
- company_id
|
||||||
- user_id
|
- user_id
|
||||||
filter:
|
filter:
|
||||||
company:
|
company:
|
||||||
companyUsers:
|
companyUsers:
|
||||||
user_id:
|
user_id:
|
||||||
_eq: X-Hasura-User-Id
|
_eq: X-Hasura-User-Id
|
||||||
role: user
|
role: user
|
||||||
|
|||||||
@@ -19,65 +19,65 @@ configuration:
|
|||||||
update: updateCustomerComments
|
update: updateCustomerComments
|
||||||
update_by_pk: updateCustomerComment
|
update_by_pk: updateCustomerComment
|
||||||
object_relationships:
|
object_relationships:
|
||||||
- name: customer
|
- name: customer
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: customer_id
|
foreign_key_constraint_on: customer_id
|
||||||
- name: file
|
- name: file
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: file_id
|
foreign_key_constraint_on: file_id
|
||||||
- name: user
|
- name: user
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: user_id
|
foreign_key_constraint_on: user_id
|
||||||
insert_permissions:
|
insert_permissions:
|
||||||
- permission:
|
- permission:
|
||||||
backend_only: false
|
backend_only: false
|
||||||
check:
|
check:
|
||||||
customer:
|
customer:
|
||||||
company:
|
company:
|
||||||
companyUsers:
|
companyUsers:
|
||||||
user_id:
|
user_id:
|
||||||
_eq: X-Hasura-User-Id
|
_eq: X-Hasura-User-Id
|
||||||
columns:
|
columns:
|
||||||
- customer_id
|
- customer_id
|
||||||
- file_id
|
- file_id
|
||||||
- text
|
- text
|
||||||
set:
|
set:
|
||||||
user_id: x-hasura-user-id
|
user_id: x-hasura-user-id
|
||||||
role: user
|
role: user
|
||||||
select_permissions:
|
select_permissions:
|
||||||
- permission:
|
- permission:
|
||||||
columns:
|
columns:
|
||||||
- created_at
|
- created_at
|
||||||
- customer_id
|
- customer_id
|
||||||
- file_id
|
- file_id
|
||||||
- id
|
- id
|
||||||
- text
|
- text
|
||||||
- updated_at
|
- updated_at
|
||||||
- user_id
|
- user_id
|
||||||
filter:
|
filter:
|
||||||
customer:
|
customer:
|
||||||
company:
|
company:
|
||||||
companyUsers:
|
companyUsers:
|
||||||
user_id:
|
user_id:
|
||||||
_eq: X-Hasura-User-Id
|
_eq: X-Hasura-User-Id
|
||||||
role: user
|
role: user
|
||||||
update_permissions:
|
update_permissions:
|
||||||
- permission:
|
- permission:
|
||||||
check: null
|
check: null
|
||||||
columns: []
|
columns: []
|
||||||
filter:
|
filter:
|
||||||
customer:
|
customer:
|
||||||
company:
|
company:
|
||||||
companyUsers:
|
companyUsers:
|
||||||
user_id:
|
user_id:
|
||||||
_eq: X-Hasura-User-Id
|
_eq: X-Hasura-User-Id
|
||||||
role: user
|
role: user
|
||||||
delete_permissions:
|
delete_permissions:
|
||||||
- permission:
|
- permission:
|
||||||
filter:
|
filter:
|
||||||
customer:
|
customer:
|
||||||
company:
|
company:
|
||||||
companyUsers:
|
companyUsers:
|
||||||
user_id:
|
user_id:
|
||||||
_eq: X-Hasura-User-Id
|
_eq: X-Hasura-User-Id
|
||||||
role: user
|
role: user
|
||||||
|
|||||||
@@ -20,48 +20,48 @@ configuration:
|
|||||||
update: updateCustomers
|
update: updateCustomers
|
||||||
update_by_pk: updateCustomer
|
update_by_pk: updateCustomer
|
||||||
object_relationships:
|
object_relationships:
|
||||||
- name: company
|
- name: company
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: company_id
|
foreign_key_constraint_on: company_id
|
||||||
- name: user
|
- name: user
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: user_id
|
foreign_key_constraint_on: user_id
|
||||||
array_relationships:
|
array_relationships:
|
||||||
- name: customerComments
|
- name: customerComments
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: customer_id
|
column: customer_id
|
||||||
table:
|
table:
|
||||||
name: customer_comments
|
name: customer_comments
|
||||||
schema: public
|
schema: public
|
||||||
insert_permissions:
|
insert_permissions:
|
||||||
- permission:
|
- permission:
|
||||||
backend_only: false
|
backend_only: false
|
||||||
check:
|
check:
|
||||||
company:
|
company:
|
||||||
companyUsers:
|
companyUsers:
|
||||||
user_id:
|
user_id:
|
||||||
_eq: X-Hasura-User-Id
|
_eq: X-Hasura-User-Id
|
||||||
columns:
|
columns:
|
||||||
- address_line_1
|
- address_line_1
|
||||||
- company_id
|
- company_id
|
||||||
- name
|
- name
|
||||||
set:
|
set:
|
||||||
user_id: x-hasura-user-id
|
user_id: x-hasura-user-id
|
||||||
role: user
|
role: user
|
||||||
select_permissions:
|
select_permissions:
|
||||||
- permission:
|
- permission:
|
||||||
columns:
|
columns:
|
||||||
- id
|
- id
|
||||||
- created_at
|
- created_at
|
||||||
- updated_at
|
- updated_at
|
||||||
- name
|
- name
|
||||||
- address_line_1
|
- address_line_1
|
||||||
- company_id
|
- company_id
|
||||||
- user_id
|
- user_id
|
||||||
filter:
|
filter:
|
||||||
company:
|
company:
|
||||||
companyUsers:
|
companyUsers:
|
||||||
user_id:
|
user_id:
|
||||||
_eq: X-Hasura-User-Id
|
_eq: X-Hasura-User-Id
|
||||||
role: user
|
role: user
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ configuration:
|
|||||||
update: updateBuckets
|
update: updateBuckets
|
||||||
update_by_pk: updateBucket
|
update_by_pk: updateBucket
|
||||||
array_relationships:
|
array_relationships:
|
||||||
- name: files
|
- name: files
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
column: bucket_id
|
column: bucket_id
|
||||||
table:
|
table:
|
||||||
name: files
|
name: files
|
||||||
schema: storage
|
schema: storage
|
||||||
|
|||||||
@@ -25,51 +25,51 @@ configuration:
|
|||||||
update: updateFiles
|
update: updateFiles
|
||||||
update_by_pk: updateFile
|
update_by_pk: updateFile
|
||||||
object_relationships:
|
object_relationships:
|
||||||
- name: bucket
|
- name: bucket
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on: bucket_id
|
foreign_key_constraint_on: bucket_id
|
||||||
array_relationships:
|
array_relationships:
|
||||||
- name: customerCommentFiles
|
- name: customerCommentFiles
|
||||||
using:
|
using:
|
||||||
manual_configuration:
|
manual_configuration:
|
||||||
column_mapping:
|
column_mapping:
|
||||||
id: file_id
|
id: file_id
|
||||||
insertion_order: null
|
insertion_order: null
|
||||||
remote_table:
|
remote_table:
|
||||||
name: customer_comments
|
name: customer_comments
|
||||||
schema: public
|
schema: public
|
||||||
insert_permissions:
|
insert_permissions:
|
||||||
- permission:
|
- permission:
|
||||||
backend_only: false
|
backend_only: false
|
||||||
check:
|
check:
|
||||||
_or:
|
_or:
|
||||||
- bucket_id:
|
- bucket_id:
|
||||||
_eq: customerComments
|
_eq: customerComments
|
||||||
columns:
|
columns:
|
||||||
- bucket_id
|
- bucket_id
|
||||||
- id
|
- id
|
||||||
- mime_type
|
- mime_type
|
||||||
- name
|
- name
|
||||||
role: user
|
role: user
|
||||||
select_permissions:
|
select_permissions:
|
||||||
- permission:
|
- permission:
|
||||||
columns:
|
columns:
|
||||||
- id
|
- id
|
||||||
- created_at
|
- created_at
|
||||||
- updated_at
|
- updated_at
|
||||||
- bucket_id
|
- bucket_id
|
||||||
- name
|
- name
|
||||||
- size
|
- size
|
||||||
- mime_type
|
- mime_type
|
||||||
- etag
|
- etag
|
||||||
- is_uploaded
|
- is_uploaded
|
||||||
- uploaded_by_user_id
|
- uploaded_by_user_id
|
||||||
filter:
|
filter:
|
||||||
_or:
|
_or:
|
||||||
- customerCommentFiles:
|
- customerCommentFiles:
|
||||||
customer:
|
customer:
|
||||||
company:
|
company:
|
||||||
companyUsers:
|
companyUsers:
|
||||||
user_id:
|
user_id:
|
||||||
_eq: X-Hasura-User-Id
|
_eq: X-Hasura-User-Id
|
||||||
role: user
|
role: user
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
- "!include auth_provider_requests.yaml"
|
- '!include auth_provider_requests.yaml'
|
||||||
- "!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_providers.yaml"
|
- '!include auth_user_providers.yaml'
|
||||||
- "!include auth_user_roles.yaml"
|
- '!include auth_user_roles.yaml'
|
||||||
- "!include auth_users.yaml"
|
- '!include auth_users.yaml'
|
||||||
- "!include public_companies.yaml"
|
- '!include public_companies.yaml'
|
||||||
- "!include public_company_users.yaml"
|
- '!include public_company_users.yaml'
|
||||||
- "!include public_customer_comments.yaml"
|
- '!include public_customer_comments.yaml'
|
||||||
- "!include public_customers.yaml"
|
- '!include public_customers.yaml'
|
||||||
- "!include storage_buckets.yaml"
|
- '!include storage_buckets.yaml'
|
||||||
- "!include storage_files.yaml"
|
- '!include storage_files.yaml'
|
||||||
|
|||||||
@@ -3,38 +3,34 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.4.16",
|
"@apollo/client": "^3.5.10",
|
||||||
"@headlessui/react": "^1.4.2",
|
"@headlessui/react": "^1.5.0",
|
||||||
"@heroicons/react": "^1.0.5",
|
"@heroicons/react": "^1.0.6",
|
||||||
"@nhost/nhost-js": "^1.0.0",
|
"@nhost/nhost-js": "workspace:*",
|
||||||
"@nhost/react": "^0.3.0",
|
"@nhost/react": "workspace:*",
|
||||||
"@nhost/react-apollo": "^4.0.0",
|
"@nhost/react-apollo": "workspace:*",
|
||||||
"@tailwindcss/forms": "^0.3.4",
|
"@tailwindcss/forms": "^0.5.0",
|
||||||
"@testing-library/jest-dom": "^5.11.4",
|
|
||||||
"@testing-library/react": "^11.1.0",
|
|
||||||
"@testing-library/user-event": "^12.1.10",
|
|
||||||
"@types/jest": "^26.0.15",
|
|
||||||
"@types/node": "^12.0.0",
|
|
||||||
"@types/react": "^17.0.0",
|
|
||||||
"@types/react-dom": "^17.0.0",
|
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
"graphql": "^15.8.0",
|
"graphql": "15.7.2",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
"pretty-bytes": "^5.6.0",
|
"pretty-bytes": "^5.6.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-router-dom": "^6.0.2",
|
"react-router-dom": "^6.3.0",
|
||||||
"react-scripts": "^5.0.0",
|
"tailwindcss": "^3.0.24"
|
||||||
"typescript": "^4.1.2",
|
|
||||||
"web-vitals": "^1.0.1"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"dev": "vite",
|
||||||
"build": "react-scripts build",
|
"build": "vite build",
|
||||||
"test": "react-scripts test",
|
"preview": "vite preview",
|
||||||
"eject": "react-scripts eject",
|
"codegen": "graphql-codegen --config codegen.yaml --errors-only",
|
||||||
"codegen": "graphql-codegen --config codegen.yaml --errors-only"
|
"prettier": "prettier --check .",
|
||||||
|
"prettier:fix": "prettier --write .",
|
||||||
|
"lint": "eslint . --ext .ts,.tsx",
|
||||||
|
"lint:fix": "eslint . --ext .ts,.tsx --fix",
|
||||||
|
"verify": "run-p prettier lint",
|
||||||
|
"verify:fix": "run-p prettier:fix lint:fix"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
@@ -55,15 +51,21 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "^2.2.1",
|
"@graphql-codegen/cli": "^2.6.2",
|
||||||
"@graphql-codegen/introspection": "^2.1.0",
|
"@graphql-codegen/introspection": "^2.1.1",
|
||||||
"@graphql-codegen/typescript": "^2.2.4",
|
"@graphql-codegen/typescript": "^2.4.8",
|
||||||
"@graphql-codegen/typescript-operations": "^2.1.8",
|
"@graphql-codegen/typescript-operations": "^2.3.5",
|
||||||
"@graphql-codegen/typescript-react-apollo": "^3.1.6",
|
"@graphql-codegen/typescript-react-apollo": "^3.2.11",
|
||||||
|
"@types/jest": "^26.0.24",
|
||||||
|
"@types/node": "^12.20.48",
|
||||||
|
"@types/react": "^17.0.44",
|
||||||
|
"@types/react-dom": "^17.0.15",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"autoprefixer": "^9.8.8",
|
"@vitejs/plugin-react": "^1.3.1",
|
||||||
"express": "^4.17.1",
|
"autoprefixer": "^10.4.4",
|
||||||
"postcss": "^7.0.39",
|
"express": "^4.17.3",
|
||||||
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17"
|
"postcss": "^8.4.12",
|
||||||
|
"typescript": "^4.6.3",
|
||||||
|
"vite": "^2.9.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
examples/react-apollo-crm/postcss.config.js
Normal file
6
examples/react-apollo-crm/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<meta name="theme-color" content="#000000" />
|
|
||||||
<meta
|
|
||||||
name="description"
|
|
||||||
content="Web site created using create-react-app"
|
|
||||||
/>
|
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
|
||||||
<!--
|
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
|
||||||
-->
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
|
||||||
<!--
|
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
|
||||||
-->
|
|
||||||
<title>React App</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
||||||
<div id="root"></div>
|
|
||||||
<!--
|
|
||||||
This HTML file is a template.
|
|
||||||
If you open it directly in the browser, you will see an empty page.
|
|
||||||
|
|
||||||
You can add webfonts, meta tags, or analytics to this file.
|
|
||||||
The build step will place the bundled scripts into the <body> tag.
|
|
||||||
|
|
||||||
To begin the development, run `npm start` or `yarn start`.
|
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
|
||||||
-->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import App from './App'
|
|
||||||
|
|
||||||
test('single noop test', () => {
|
|
||||||
expect(true).toBeTruthy()
|
|
||||||
})
|
|
||||||
@@ -2,7 +2,7 @@ import './App.css'
|
|||||||
import { NhostReactProvider } from '@nhost/react'
|
import { NhostReactProvider } from '@nhost/react'
|
||||||
import { NhostApolloProvider } from '@nhost/react-apollo'
|
import { NhostApolloProvider } from '@nhost/react-apollo'
|
||||||
import { nhost } from './utils/nhost'
|
import { nhost } from './utils/nhost'
|
||||||
import { Route, Routes } from 'react-router'
|
import { Route, Routes } from 'react-router-dom'
|
||||||
import { Layout } from './components/ui/Layout'
|
import { Layout } from './components/ui/Layout'
|
||||||
import { Customers } from './components/Customers'
|
import { Customers } from './components/Customers'
|
||||||
import { Dashboard } from './components/Dashboard'
|
import { Dashboard } from './components/Dashboard'
|
||||||
|
|||||||
@@ -3,91 +3,85 @@ import { Dialog, Transition } from '@headlessui/react'
|
|||||||
import { nhost } from '../utils/nhost'
|
import { nhost } from '../utils/nhost'
|
||||||
|
|
||||||
export function ChangePasswordModal() {
|
export function ChangePasswordModal() {
|
||||||
const [open, setOpen] = useState(true)
|
const [open, setOpen] = useState(true)
|
||||||
const [newPassword, setNewPassword] = useState('')
|
const [newPassword, setNewPassword] = useState('')
|
||||||
|
|
||||||
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
|
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const { error } = await nhost.auth.changePassword({ newPassword })
|
const { error } = await nhost.auth.changePassword({ newPassword })
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return alert(error.message)
|
return alert(error.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={open} as={Fragment}>
|
<Transition.Root show={open} as={Fragment}>
|
||||||
<Dialog as="div" className="fixed z-10 inset-0 overflow-y-auto" onClose={setOpen}>
|
<Dialog as="div" className="fixed inset-0 z-10 overflow-y-auto" onClose={setOpen}>
|
||||||
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
<div className="flex items-end justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0"
|
enterFrom="opacity-0"
|
||||||
enterTo="opacity-100"
|
enterTo="opacity-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
|
<Dialog.Overlay className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
||||||
{/* This element is to trick the browser into centering the modal contents. */}
|
{/* This element is to trick the browser into centering the modal contents. */}
|
||||||
<span
|
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
|
||||||
className="hidden sm:inline-block sm:align-middle sm:h-screen"
|
​
|
||||||
aria-hidden="true"
|
</span>
|
||||||
>
|
<Transition.Child
|
||||||
​
|
as={Fragment}
|
||||||
</span>
|
enter="ease-out duration-300"
|
||||||
<Transition.Child
|
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
as={Fragment}
|
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||||
enter="ease-out duration-300"
|
leave="ease-in duration-200"
|
||||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
leave="ease-in duration-200"
|
>
|
||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
<div className="inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transition-all transform bg-white rounded-lg shadow-xl sm:my-8 sm:align-middle sm:max-w-sm sm:w-full sm:p-6">
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
<form onSubmit={handleSubmit}>
|
||||||
>
|
<div>
|
||||||
<div className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-sm sm:w-full sm:p-6">
|
<div className="mt-3 text-center sm:mt-5">
|
||||||
<form onSubmit={handleSubmit}>
|
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-900">
|
||||||
<div>
|
Change Password
|
||||||
<div className="mt-3 text-center sm:mt-5">
|
</Dialog.Title>
|
||||||
<Dialog.Title
|
<div className="mt-2">
|
||||||
as="h3"
|
<input
|
||||||
className="text-lg leading-6 font-medium text-gray-900"
|
id="password"
|
||||||
>
|
name="password"
|
||||||
Change Password
|
type="password"
|
||||||
</Dialog.Title>
|
autoComplete="current-password"
|
||||||
<div className="mt-2">
|
required
|
||||||
<input
|
className="block w-full px-3 py-2 placeholder-gray-400 border border-gray-300 rounded-md shadow-sm appearance-none focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||||
id="password"
|
tabIndex={2}
|
||||||
name="password"
|
value={newPassword}
|
||||||
type="password"
|
onChange={(e) => setNewPassword(e.target.value)}
|
||||||
autoComplete="current-password"
|
/>
|
||||||
required
|
</div>
|
||||||
className="block w-full px-3 py-2 placeholder-gray-400 border border-gray-300 rounded-md shadow-sm appearance-none focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
|
||||||
tabIndex={2}
|
|
||||||
value={newPassword}
|
|
||||||
onChange={(e) => setNewPassword(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-5 sm:mt-6">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="inline-flex justify-center w-full rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:text-sm"
|
|
||||||
>
|
|
||||||
Set new password
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</Transition.Child>
|
</div>
|
||||||
|
<div className="mt-5 sm:mt-6">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="inline-flex justify-center w-full px-4 py-2 text-base font-medium text-white bg-blue-600 border border-transparent rounded-md shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:text-sm"
|
||||||
|
>
|
||||||
|
Set new password
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Transition.Child>
|
||||||
</Transition.Root>
|
</div>
|
||||||
)
|
</Dialog>
|
||||||
|
</Transition.Root>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,47 @@
|
|||||||
import { Main } from "./ui/Main";
|
import { Main } from './ui/Main'
|
||||||
import { Breadcrumbs } from "./ui/Breadcrumbs";
|
import { Breadcrumbs } from './ui/Breadcrumbs'
|
||||||
import { HeaderSection } from "./ui/HeaderSection";
|
import { HeaderSection } from './ui/HeaderSection'
|
||||||
import { PageHeader } from "./ui/PageHeader";
|
import { PageHeader } from './ui/PageHeader'
|
||||||
|
|
||||||
import { useParams } from "react-router";
|
import { useParams } from 'react-router-dom'
|
||||||
import { useCustomerQuery } from "../utils/__generated__/graphql";
|
import { useCustomerQuery } from '../utils/__generated__/graphql'
|
||||||
import { NavLink, Outlet } from "react-router-dom";
|
import { NavLink, Outlet } from 'react-router-dom'
|
||||||
import classNames from "classnames";
|
import classNames from 'classnames'
|
||||||
import { CustomerActivities } from "./CustomerActivities";
|
import { CustomerActivities } from './CustomerActivities'
|
||||||
import { CustomerAddComment } from "./CustomerAddComment";
|
import { CustomerAddComment } from './CustomerAddComment'
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ name: "Overview", href: "" },
|
{ name: 'Overview', href: '' },
|
||||||
{ name: "Orders", href: "orders" },
|
{ name: 'Orders', href: 'orders' },
|
||||||
{ name: "Files", href: "files" },
|
{ name: 'Files', href: 'files' }
|
||||||
];
|
]
|
||||||
|
|
||||||
export function Customer() {
|
export function Customer() {
|
||||||
const { customerId } = useParams();
|
const { customerId } = useParams()
|
||||||
|
|
||||||
const { data, loading } = useCustomerQuery({
|
const { data, loading } = useCustomerQuery({
|
||||||
variables: {
|
variables: {
|
||||||
customerId,
|
customerId
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div>Loading..</div>;
|
return <div>Loading..</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data || !data.customer) {
|
if (!data || !data.customer) {
|
||||||
return <div>No customer..</div>;
|
return <div>No customer..</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
const { customer } = data;
|
const { customer } = data
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Main>
|
<Main>
|
||||||
<Breadcrumbs
|
<Breadcrumbs
|
||||||
backLink={""}
|
backLink={''}
|
||||||
breadcrumbs={[
|
breadcrumbs={[
|
||||||
{ link: "/customers", text: "Customers" },
|
{ link: '/customers', text: 'Customers' },
|
||||||
{ link: `customers/${customerId}`, text: customer.name },
|
{ link: `customers/${customerId}`, text: customer.name }
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<HeaderSection>
|
<HeaderSection>
|
||||||
@@ -57,7 +57,7 @@ export function Customer() {
|
|||||||
id="current-tab"
|
id="current-tab"
|
||||||
name="current-tab"
|
name="current-tab"
|
||||||
className="block w-full py-2 pl-3 pr-10 text-base border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
className="block w-full py-2 pl-3 pr-10 text-base border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||||
defaultValue={"ok"}
|
defaultValue={'ok'}
|
||||||
>
|
>
|
||||||
<option>1</option>
|
<option>1</option>
|
||||||
<option>2</option>
|
<option>2</option>
|
||||||
@@ -71,10 +71,10 @@ export function Customer() {
|
|||||||
className={({ isActive }) => {
|
className={({ isActive }) => {
|
||||||
return classNames(
|
return classNames(
|
||||||
isActive
|
isActive
|
||||||
? "border-blue-500 text-blue-600"
|
? 'border-blue-500 text-blue-600'
|
||||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
|
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300',
|
||||||
"whitespace-nowrap pb-4 px-1 border-b-2 font-medium text-sm"
|
'whitespace-nowrap pb-4 px-1 border-b-2 font-medium text-sm'
|
||||||
);
|
)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tab.name}
|
{tab.name}
|
||||||
@@ -95,5 +95,5 @@ export function Customer() {
|
|||||||
<CustomerAddComment />
|
<CustomerAddComment />
|
||||||
</div>
|
</div>
|
||||||
</Main>
|
</Main>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,35 @@
|
|||||||
import { ChatAltIcon } from "@heroicons/react/solid";
|
import { ChatAltIcon } from '@heroicons/react/solid'
|
||||||
import { useGetCustomerCommentsSubscription } from "../utils/__generated__/graphql";
|
import { useGetCustomerCommentsSubscription } from '../utils/__generated__/graphql'
|
||||||
import { useParams } from "react-router";
|
import { useParams } from 'react-router-dom'
|
||||||
import { nhost } from "../utils/nhost";
|
import { nhost } from '../utils/nhost'
|
||||||
import { PhotographIcon } from "@heroicons/react/outline";
|
import { PhotographIcon } from '@heroicons/react/outline'
|
||||||
import prettyBytes from "pretty-bytes";
|
import prettyBytes from 'pretty-bytes'
|
||||||
import { formatDistanceToNow, parseISO } from "date-fns";
|
import { formatDistanceToNow, parseISO } from 'date-fns'
|
||||||
|
|
||||||
export function CustomerActivities() {
|
export function CustomerActivities() {
|
||||||
const { customerId } = useParams();
|
const { customerId } = useParams<{ customerId: string }>()
|
||||||
|
|
||||||
const { data, loading } = useGetCustomerCommentsSubscription({
|
const { data, loading } = useGetCustomerCommentsSubscription({
|
||||||
variables: {
|
variables: {
|
||||||
where: {
|
where: {
|
||||||
customerId: {
|
customerId: {
|
||||||
_eq: customerId,
|
_eq: customerId
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
console.log({ data });
|
console.log({ data })
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data || !data.customerComments) {
|
if (!data || !data.customerComments) {
|
||||||
return <div>no comments</div>;
|
return <div>no comments</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
const { customerComments } = data;
|
const { customerComments } = data
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flow-root">
|
<div className="flow-root">
|
||||||
@@ -54,10 +54,7 @@ export function CustomerActivities() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<span className="absolute -bottom-0.5 -right-1 bg-white rounded-tl px-0.5 py-px">
|
<span className="absolute -bottom-0.5 -right-1 bg-white rounded-tl px-0.5 py-px">
|
||||||
<ChatAltIcon
|
<ChatAltIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
||||||
className="w-5 h-5 text-gray-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
@@ -69,7 +66,7 @@ export function CustomerActivities() {
|
|||||||
</div>
|
</div>
|
||||||
<p className="mt-0.5 text-sm text-gray-500">
|
<p className="mt-0.5 text-sm text-gray-500">
|
||||||
{formatDistanceToNow(parseISO(comment.createdAt), {
|
{formatDistanceToNow(parseISO(comment.createdAt), {
|
||||||
addSuffix: true,
|
addSuffix: true
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -80,24 +77,22 @@ export function CustomerActivities() {
|
|||||||
<div
|
<div
|
||||||
className="flex items-center mt-3 text-sm text-gray-700 cursor-pointer"
|
className="flex items-center mt-3 text-sm text-gray-700 cursor-pointer"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const { presignedUrl, error } =
|
const { presignedUrl, error } = await nhost.storage.getPresignedUrl({
|
||||||
await nhost.storage.getPresignedUrl({
|
fileId: comment.file!.id
|
||||||
fileId: comment.file!.id,
|
})
|
||||||
});
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return alert(error.message);
|
return alert(error.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
window.open(presignedUrl?.url, "_blank");
|
window.open(presignedUrl?.url, '_blank')
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<PhotographIcon className="w-5 mr-1 text-gray-500" />
|
<PhotographIcon className="w-5 mr-1 text-gray-500" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{comment.file.name},{" "}
|
{comment.file.name}, {prettyBytes(comment.file.size as number)}
|
||||||
{prettyBytes(comment.file.size as number)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -106,9 +101,9 @@ export function CustomerActivities() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
);
|
)
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,31 @@
|
|||||||
import { useState } from "react";
|
import { useState } from 'react'
|
||||||
import { useParams } from "react-router";
|
import { useParams } from 'react-router-dom'
|
||||||
import { nhost } from "../utils/nhost";
|
import { nhost } from '../utils/nhost'
|
||||||
import { useInsertCustomerCommentMutation } from "../utils/__generated__/graphql";
|
import { useInsertCustomerCommentMutation } from '../utils/__generated__/graphql'
|
||||||
|
|
||||||
export function CustomerAddComment() {
|
export function CustomerAddComment() {
|
||||||
const [text, setText] = useState("");
|
const [text, setText] = useState('')
|
||||||
const [file, setFile] = useState<null | File>(null);
|
const [file, setFile] = useState<null | File>(null)
|
||||||
|
|
||||||
const { customerId } = useParams();
|
const { customerId } = useParams<{ customerId: string }>()
|
||||||
const [insertCustomerComment, { loading }] =
|
const [insertCustomerComment, { loading }] = useInsertCustomerCommentMutation()
|
||||||
useInsertCustomerCommentMutation();
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
|
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
|
|
||||||
let fileMetadata;
|
let fileMetadata
|
||||||
if (file) {
|
if (file) {
|
||||||
const fileUploadRes = await nhost.storage.upload({
|
const fileUploadRes = await nhost.storage.upload({
|
||||||
file,
|
file,
|
||||||
bucketId: "customerComments",
|
bucketId: 'customerComments'
|
||||||
});
|
})
|
||||||
|
|
||||||
if (fileUploadRes.error) {
|
if (fileUploadRes.error) {
|
||||||
alert(`error: ${fileUploadRes.error}`);
|
alert(`error: ${fileUploadRes.error}`)
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fileMetadata = fileUploadRes.fileMetadata;
|
fileMetadata = fileUploadRes.fileMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
await insertCustomerComment({
|
await insertCustomerComment({
|
||||||
@@ -34,21 +33,18 @@ export function CustomerAddComment() {
|
|||||||
customerComment: {
|
customerComment: {
|
||||||
text,
|
text,
|
||||||
customerId,
|
customerId,
|
||||||
fileId: fileMetadata ? fileMetadata.id : null,
|
fileId: fileMetadata ? fileMetadata.id : null
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
setText("");
|
setText('')
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-lg mx-auto">
|
<div className="max-w-lg mx-auto">
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<label
|
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
|
||||||
htmlFor="email"
|
|
||||||
className="block text-sm font-medium text-gray-700"
|
|
||||||
>
|
|
||||||
Comment
|
Comment
|
||||||
</label>
|
</label>
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
@@ -57,7 +53,7 @@ export function CustomerAddComment() {
|
|||||||
name="about"
|
name="about"
|
||||||
rows={3}
|
rows={3}
|
||||||
className="block w-full max-w-lg border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
className="block w-full max-w-lg border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||||
defaultValue={""}
|
defaultValue={''}
|
||||||
value={text}
|
value={text}
|
||||||
onChange={(e) => setText(e.target.value)}
|
onChange={(e) => setText(e.target.value)}
|
||||||
/>
|
/>
|
||||||
@@ -94,7 +90,7 @@ export function CustomerAddComment() {
|
|||||||
className="sr-only"
|
className="sr-only"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e.target.files && e.target.files.length > 0) {
|
if (e.target.files && e.target.files.length > 0) {
|
||||||
setFile(e.target.files[0]);
|
setFile(e.target.files[0])
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -104,9 +100,7 @@ export function CustomerAddComment() {
|
|||||||
{file ? (
|
{file ? (
|
||||||
<div>{file.name}</div>
|
<div>{file.name}</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">PNG, JPG, GIF up to 10MB</p>
|
||||||
PNG, JPG, GIF up to 10MB
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -124,5 +118,5 @@ export function CustomerAddComment() {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
import { Main } from "./ui/Main";
|
import { Main } from './ui/Main'
|
||||||
import { Breadcrumbs } from "./ui/Breadcrumbs";
|
import { Breadcrumbs } from './ui/Breadcrumbs'
|
||||||
import { HeaderSection } from "./ui/HeaderSection";
|
import { HeaderSection } from './ui/HeaderSection'
|
||||||
import { PageHeader } from "./ui/PageHeader";
|
import { PageHeader } from './ui/PageHeader'
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from 'react-router-dom'
|
||||||
import { useGetCustomersSubscription } from "../utils/__generated__/graphql";
|
import { useGetCustomersSubscription } from '../utils/__generated__/graphql'
|
||||||
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/solid";
|
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid'
|
||||||
|
|
||||||
export function Customers() {
|
export function Customers() {
|
||||||
return (
|
return (
|
||||||
<Main>
|
<Main>
|
||||||
<Breadcrumbs
|
<Breadcrumbs backLink={''} breadcrumbs={[{ link: '/customers', text: 'Customers' }]} />
|
||||||
backLink={""}
|
|
||||||
breadcrumbs={[{ link: "/customers", text: "Customers" }]}
|
|
||||||
/>
|
|
||||||
<HeaderSection>
|
<HeaderSection>
|
||||||
<PageHeader>Customers</PageHeader>
|
<PageHeader>Customers</PageHeader>
|
||||||
<div className="flex flex-shrink-0 mt-4 md:mt-0 md:ml-4">
|
<div className="flex flex-shrink-0 mt-4 md:mt-0 md:ml-4">
|
||||||
@@ -31,11 +28,11 @@ export function Customers() {
|
|||||||
<CustomersList />
|
<CustomersList />
|
||||||
</div>
|
</div>
|
||||||
</Main>
|
</Main>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function CustomersList() {
|
function CustomersList() {
|
||||||
const { data } = useGetCustomersSubscription();
|
const { data } = useGetCustomersSubscription()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -57,17 +54,14 @@ function CustomersList() {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{data?.customers.map((customer, i) => {
|
{data?.customers.map((customer, i) => {
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr key={customer.id} className={i % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
|
||||||
key={customer.id}
|
|
||||||
className={i % 2 === 0 ? "bg-white" : "bg-gray-50"}
|
|
||||||
>
|
|
||||||
<td className="text-sm font-medium text-gray-900 whitespace-nowrap">
|
<td className="text-sm font-medium text-gray-900 whitespace-nowrap">
|
||||||
<Link to={customer.id} className="block px-6 py-4">
|
<Link to={customer.id} className="block px-6 py-4">
|
||||||
{customer.name}
|
{customer.name}
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
)
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -88,9 +82,9 @@ function CustomersList() {
|
|||||||
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-700">
|
<p className="text-sm text-gray-700">
|
||||||
Showing <span className="font-medium">1</span> to{" "}
|
Showing <span className="font-medium">1</span> to{' '}
|
||||||
<span className="font-medium">10</span> of{" "}
|
<span className="font-medium">10</span> of <span className="font-medium">97</span>{' '}
|
||||||
<span className="font-medium">97</span> results
|
results
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -136,5 +130,5 @@ function CustomersList() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Main } from "./ui/Main";
|
import { Main } from './ui/Main'
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
return <Main>Dashboard</Main>;
|
return <Main>Dashboard</Main>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +1,64 @@
|
|||||||
import { useState } from "react";
|
import { useState } from 'react'
|
||||||
import { Main } from "./ui/Main";
|
import { Main } from './ui/Main'
|
||||||
import { Breadcrumbs } from "./ui/Breadcrumbs";
|
import { Breadcrumbs } from './ui/Breadcrumbs'
|
||||||
import { HeaderSection } from "./ui/HeaderSection";
|
import { HeaderSection } from './ui/HeaderSection'
|
||||||
import { PageHeader } from "./ui/PageHeader";
|
import { PageHeader } from './ui/PageHeader'
|
||||||
import {
|
import { useGetCompanyWhereQuery, useInsertCustomerMutation } from '../utils/__generated__/graphql'
|
||||||
useGetCompanyWhereQuery,
|
import { nhost } from '../utils/nhost'
|
||||||
useInsertCustomerMutation,
|
import { useNavigate } from 'react-router-dom'
|
||||||
} from "../utils/__generated__/graphql";
|
|
||||||
import { nhost } from "../utils/nhost";
|
|
||||||
import { useNavigate } from "react-router";
|
|
||||||
|
|
||||||
export function NewCustomer() {
|
export function NewCustomer() {
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState('')
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState('')
|
||||||
const [addressLine1, setAddressLine1] = useState("");
|
const [addressLine1, setAddressLine1] = useState('')
|
||||||
|
|
||||||
const user = nhost.auth.getUser();
|
const user = nhost.auth.getUser()
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate()
|
||||||
|
|
||||||
const { data } = useGetCompanyWhereQuery({
|
const { data } = useGetCompanyWhereQuery({
|
||||||
variables: {
|
variables: {
|
||||||
where: {
|
where: {
|
||||||
companyUsers: {
|
companyUsers: {
|
||||||
userId: {
|
userId: {
|
||||||
_eq: user?.id,
|
_eq: user?.id
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
const [insertCustomer, { loading }] = useInsertCustomerMutation();
|
const [insertCustomer, { loading }] = useInsertCustomerMutation()
|
||||||
|
|
||||||
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
|
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
|
|
||||||
console.log("handle submit");
|
console.log('handle submit')
|
||||||
|
|
||||||
let res;
|
let res
|
||||||
try {
|
try {
|
||||||
res = await insertCustomer({
|
res = await insertCustomer({
|
||||||
variables: {
|
variables: {
|
||||||
customer: {
|
customer: {
|
||||||
name,
|
name,
|
||||||
addressLine1,
|
addressLine1,
|
||||||
companyId: data?.companies[0].id,
|
companyId: data?.companies[0].id
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return alert(`error: ${error}`);
|
return alert(`error: ${error}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
navigate(`/customers/${res.data?.insertCustomer?.id}`);
|
navigate(`/customers/${res.data?.insertCustomer?.id}`)
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Main>
|
<Main>
|
||||||
<Breadcrumbs
|
<Breadcrumbs
|
||||||
backLink={""}
|
backLink={''}
|
||||||
breadcrumbs={[
|
breadcrumbs={[
|
||||||
{ link: "/customers", text: "Customers" },
|
{ link: '/customers', text: 'Customers' },
|
||||||
{ link: "/new-customer", text: "New Customer" },
|
{ link: '/new-customer', text: 'New Customer' }
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<HeaderSection>
|
<HeaderSection>
|
||||||
@@ -73,10 +70,7 @@ export function NewCustomer() {
|
|||||||
<div className="pt-12">
|
<div className="pt-12">
|
||||||
<div className="grid grid-cols-1 mt-6 gap-y-6 gap-x-4 sm:grid-cols-6">
|
<div className="grid grid-cols-1 mt-6 gap-y-6 gap-x-4 sm:grid-cols-6">
|
||||||
<div className="sm:col-span-3">
|
<div className="sm:col-span-3">
|
||||||
<label
|
<label htmlFor="first-name" className="block text-sm font-medium text-gray-700">
|
||||||
htmlFor="first-name"
|
|
||||||
className="block text-sm font-medium text-gray-700"
|
|
||||||
>
|
|
||||||
Name
|
Name
|
||||||
</label>
|
</label>
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
@@ -93,10 +87,7 @@ export function NewCustomer() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sm:col-span-3">
|
<div className="sm:col-span-3">
|
||||||
<label
|
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
|
||||||
htmlFor="email"
|
|
||||||
className="block text-sm font-medium text-gray-700"
|
|
||||||
>
|
|
||||||
Email address
|
Email address
|
||||||
</label>
|
</label>
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
@@ -113,10 +104,7 @@ export function NewCustomer() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sm:col-span-6">
|
<div className="sm:col-span-6">
|
||||||
<label
|
<label htmlFor="street-address" className="block text-sm font-medium text-gray-700">
|
||||||
htmlFor="street-address"
|
|
||||||
className="block text-sm font-medium text-gray-700"
|
|
||||||
>
|
|
||||||
Street address
|
Street address
|
||||||
</label>
|
</label>
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
@@ -147,5 +135,5 @@ export function NewCustomer() {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Main>
|
</Main>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useNhostAuth } from '@nhost/react'
|
import { useNhostAuth } from '@nhost/react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Navigate, useLocation } from 'react-router'
|
import { Navigate, useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
export function RequireAuth({ children }: { children: JSX.Element }) {
|
export function RequireAuth({ children }: { children: JSX.Element }) {
|
||||||
const { isAuthenticated, isLoading } = useNhostAuth()
|
const { isAuthenticated, isLoading } = useNhostAuth()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useNhostAuth } from '@nhost/react'
|
import { useNhostAuth } from '@nhost/react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useNavigate } from 'react-router'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { nhost } from '../utils/nhost'
|
import { nhost } from '../utils/nhost'
|
||||||
|
|
||||||
export function ResetPassword() {
|
export function ResetPassword() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useNhostAuth } from '@nhost/react'
|
import { useNhostAuth } from '@nhost/react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useNavigate } from 'react-router'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { nhost } from '../utils/nhost'
|
import { nhost } from '../utils/nhost'
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useNhostAuth } from '@nhost/react'
|
import { useNhostAuth } from '@nhost/react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useNavigate } from 'react-router'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { nhost } from '../utils/nhost'
|
import { nhost } from '../utils/nhost'
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ export function SignUp() {
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-center py-4">
|
<div className="py-4 text-center">
|
||||||
Already have an account?{' '}
|
Already have an account?{' '}
|
||||||
<Link to="/sign-in" className="text-blue-600 hover:text-blue-500">
|
<Link to="/sign-in" className="text-blue-600 hover:text-blue-500">
|
||||||
Sign In
|
Sign In
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/solid";
|
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid'
|
||||||
import classNames from "classnames";
|
import classNames from 'classnames'
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
type BreadcrumbsProps = {
|
type BreadcrumbsProps = {
|
||||||
backLink: string;
|
backLink: string
|
||||||
breadcrumbs: Breadcrumb[];
|
breadcrumbs: Breadcrumb[]
|
||||||
};
|
}
|
||||||
|
|
||||||
type Breadcrumb = {
|
type Breadcrumb = {
|
||||||
link: string;
|
link: string
|
||||||
text: string;
|
text: string
|
||||||
};
|
}
|
||||||
|
|
||||||
export function Breadcrumbs(props: BreadcrumbsProps) {
|
export function Breadcrumbs(props: BreadcrumbsProps) {
|
||||||
const { backLink, breadcrumbs } = props;
|
const { backLink, breadcrumbs } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -32,13 +32,10 @@ export function Breadcrumbs(props: BreadcrumbsProps) {
|
|||||||
<nav className="hidden sm:flex" aria-label="Breadcrumb">
|
<nav className="hidden sm:flex" aria-label="Breadcrumb">
|
||||||
<ol className="flex items-center space-x-4">
|
<ol className="flex items-center space-x-4">
|
||||||
{breadcrumbs.map((breadcrumb, i) => {
|
{breadcrumbs.map((breadcrumb, i) => {
|
||||||
const isFirstItem = i === 0;
|
const isFirstItem = i === 0
|
||||||
const classes = classNames(
|
const classes = classNames('text-sm font-medium text-gray-500 hover:text-gray-700', {
|
||||||
"text-sm font-medium text-gray-500 hover:text-gray-700",
|
'ml-4': !isFirstItem
|
||||||
{
|
})
|
||||||
"ml-4": !isFirstItem,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={i}>
|
<li key={i}>
|
||||||
@@ -54,10 +51,10 @@ export function Breadcrumbs(props: BreadcrumbsProps) {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
);
|
)
|
||||||
})}
|
})}
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import React from "react";
|
import React from 'react'
|
||||||
|
|
||||||
export function HeaderSection({ children }: { children: React.ReactNode }) {
|
export function HeaderSection({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return <div className="mt-2 md:flex md:items-center md:justify-between">{children}</div>
|
||||||
<div className="mt-2 md:flex md:items-center md:justify-between">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import React from "react";
|
import React from 'react'
|
||||||
|
|
||||||
export function Main({ children }: { children: React.ReactNode }) {
|
export function Main({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return <div className="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">{children}</div>
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">{children}</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import React from 'react'
|
||||||
|
|
||||||
export function PageHeader({ children }: { children: React.ReactNode }) {
|
export function PageHeader({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
@@ -7,5 +7,5 @@ export function PageHeader({ children }: { children: React.ReactNode }) {
|
|||||||
{children}
|
{children}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
10
examples/react-apollo-crm/src/env.d.ts
vendored
Normal file
10
examples/react-apollo-crm/src/env.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly VITE_NHOST_URL: string
|
||||||
|
// more env variables...
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv
|
||||||
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
mutation insertCustomerComment(
|
mutation insertCustomerComment($customerComment: customerComments_insert_input!) {
|
||||||
$customerComment: customerComments_insert_input!
|
|
||||||
) {
|
|
||||||
insertCustomerComment(object: $customerComment) {
|
insertCustomerComment(object: $customerComment) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
|
||||||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||||
monospace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React from "react";
|
import React from 'react'
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from 'react-dom'
|
||||||
import "./index.css";
|
import './index.css'
|
||||||
import App from "./App";
|
import App from './App'
|
||||||
import reportWebVitals from "./reportWebVitals";
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
import { BrowserRouter } from "react-router-dom";
|
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
@@ -11,10 +10,5 @@ ReactDOM.render(
|
|||||||
<App />
|
<App />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById("root")
|
document.getElementById('root')
|
||||||
);
|
)
|
||||||
|
|
||||||
// If you want to start measuring performance in your app, pass a function
|
|
||||||
// to log results (for example: reportWebVitals(console.log))
|
|
||||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
|
||||||
reportWebVitals();
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user