Compare commits
325 Commits
@nhost/cli
...
@nhost/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
8a8c67db92 | ||
|
|
13935ebdc4 | ||
|
|
357ba89d53 | ||
|
|
7e34805eb4 | ||
|
|
52782ee550 | ||
|
|
089d7fb0a2 | ||
|
|
9df131201e | ||
|
|
067d8a692c | ||
|
|
824060e7f6 | ||
|
|
0fe7b8f0fb | ||
|
|
78f096a738 | ||
|
|
4635a145c1 | ||
|
|
1446a8f13b | ||
|
|
ff75998e93 | ||
|
|
9cc044ca9f | ||
|
|
c65e44b8d6 | ||
|
|
9ec73b4c22 | ||
|
|
e4eda9e967 | ||
|
|
94b70e0ce9 | ||
|
|
d108dff4f4 | ||
|
|
7a8e771a72 | ||
|
|
f8fb4bbedd | ||
|
|
90e38b1cc5 | ||
|
|
9c04dad57c | ||
|
|
c6b1c33a8e | ||
|
|
65b36eff13 | ||
|
|
cac6088016 | ||
|
|
c192cb9503 | ||
|
|
94ff290264 | ||
|
|
aff80db515 | ||
|
|
6e2c991b2e | ||
|
|
78781ebfec | ||
|
|
f6093a619f | ||
|
|
63d6059981 | ||
|
|
022d49fb25 | ||
|
|
a8e6187360 | ||
|
|
276d6b10dd | ||
|
|
62461a2f20 | ||
|
|
81ec16d77b | ||
|
|
5a059c1e9e | ||
|
|
28bbde6142 | ||
|
|
05f01e45ec | ||
|
|
b1bd405a5e | ||
|
|
a295b5b1e6 | ||
|
|
eece559771 | ||
|
|
cd0e4d1908 | ||
|
|
7bf678df9f | ||
|
|
3bd1aa4d53 | ||
|
|
f3cca4997b | ||
|
|
0fd7a487d6 | ||
|
|
1bb032c1e7 | ||
|
|
2c97db68b5 | ||
|
|
714f2872ee | ||
|
|
65fc26a0e8 | ||
|
|
86a56f28c1 | ||
|
|
6e8abe28d6 | ||
|
|
39925ff5ca | ||
|
|
583a77ed0d | ||
|
|
e704831500 | ||
|
|
a8f82e8133 | ||
|
|
95948dd5b9 | ||
|
|
247b69c952 | ||
|
|
7d15b76402 | ||
|
|
b1ae65fd72 | ||
|
|
0063fd1840 | ||
|
|
743a7e6507 | ||
|
|
1687f7af04 | ||
|
|
078652861f | ||
|
|
39840cfd95 | ||
|
|
102c99e491 | ||
|
|
a3702a644e | ||
|
|
db65fea706 | ||
|
|
ffe9123b48 | ||
|
|
f112ea2115 | ||
|
|
4963153def | ||
|
|
940a36a68f | ||
|
|
77b109b3df | ||
|
|
15907d65e6 | ||
|
|
7d7d16fa71 | ||
|
|
3f39e48cbd | ||
|
|
97ade32869 | ||
|
|
8583af8290 | ||
|
|
a28193a6ba | ||
|
|
60d85e5a69 | ||
|
|
9d6c64430a | ||
|
|
519d1bf5cb | ||
|
|
5ffb0320b5 | ||
|
|
50d2413554 | ||
|
|
7d275aad90 | ||
|
|
6607e73cc2 | ||
|
|
b4bac161a5 | ||
|
|
37d15377c8 | ||
|
|
8ee1df3be4 | ||
|
|
47ffca945e | ||
|
|
d60f5e623c | ||
|
|
6f80643ee0 | ||
|
|
8d5084725d | ||
|
|
693498dd09 | ||
|
|
4d36a966ea | ||
|
|
239a075f1d | ||
|
|
931194812e | ||
|
|
c8f80c58f3 | ||
|
|
7fdb5aee0a | ||
|
|
1710808fef | ||
|
|
696815d4a8 | ||
|
|
5cc9be00b6 | ||
|
|
28dae23a91 | ||
|
|
7819e20cf4 | ||
|
|
6be3758668 | ||
|
|
658c67faf4 | ||
|
|
e7f3a5f6e0 | ||
|
|
7135aee78b | ||
|
|
587eaff734 | ||
|
|
7cf875f4b8 | ||
|
|
657cfb91c5 | ||
|
|
103dd6e98e | ||
|
|
3c8caa680b | ||
|
|
1bcee357fe | ||
|
|
b729aa9290 | ||
|
|
57780ee645 | ||
|
|
aad8d22380 | ||
|
|
85d33c4de0 | ||
|
|
ab3e2dcee9 | ||
|
|
12f4504b61 | ||
|
|
71d7a11c96 | ||
|
|
16a6c5073e | ||
|
|
3fcc86792a | ||
|
|
27909128e4 | ||
|
|
72371c72a1 | ||
|
|
d878414b10 | ||
|
|
9b840f7c4a | ||
|
|
4fd09b4080 | ||
|
|
bdb786fa83 | ||
|
|
d42be972b4 | ||
|
|
5920c830b3 | ||
|
|
5fc16653c0 | ||
|
|
900ec48889 | ||
|
|
83d3c90f43 | ||
|
|
cf20ee5a8f | ||
|
|
9180154325 | ||
|
|
1ae025b745 | ||
|
|
ba538a4ad9 | ||
|
|
0e5e47b8f8 | ||
|
|
113beed447 | ||
|
|
6eeb9d2e65 | ||
|
|
3db2959bc2 | ||
|
|
16fcc08b0a | ||
|
|
5b098c8ef4 | ||
|
|
058956bdcb | ||
|
|
d3384614b4 | ||
|
|
0064fccb12 | ||
|
|
6efd45fcb7 | ||
|
|
4420c0e070 | ||
|
|
354b07947a | ||
|
|
2fa5c10e14 | ||
|
|
94124c7754 | ||
|
|
e405b738a6 | ||
|
|
947b7e037f | ||
|
|
cd6f37f2a6 | ||
|
|
39df4d5b9c | ||
|
|
63ee1d7659 | ||
|
|
eb33952760 | ||
|
|
e91215bbac | ||
|
|
ccaa4c4bba | ||
|
|
ab36f90cec | ||
|
|
cfbe2db430 | ||
|
|
6838ac6201 | ||
|
|
0caf43037d | ||
|
|
4ed626d5b5 | ||
|
|
9ff9abee6a | ||
|
|
9d3f0521a5 | ||
|
|
744fd6929f | ||
|
|
f43f52e766 | ||
|
|
fd4c54ee91 | ||
|
|
b30ff6f507 | ||
|
|
ff7ae21a87 | ||
|
|
b2c398df22 | ||
|
|
888192282f | ||
|
|
945b557dea | ||
|
|
4031d8a9e1 | ||
|
|
c77aa16181 | ||
|
|
ea2fb2e9a4 | ||
|
|
e147487e27 | ||
|
|
6f52652e10 | ||
|
|
1a8d9b5c28 | ||
|
|
9111299ddd | ||
|
|
1c7f520073 | ||
|
|
dff37a4cd0 | ||
|
|
96a572379e | ||
|
|
da3bbf2e10 | ||
|
|
a11fa372ff | ||
|
|
6d2c7b26c0 | ||
|
|
d2d3ba6eb7 | ||
|
|
e688600ea2 | ||
|
|
d9aec711c4 | ||
|
|
9bd01e756f | ||
|
|
8f7643a90e | ||
|
|
50b9d763ae | ||
|
|
63cb1f0ce6 | ||
|
|
7c70b1823d | ||
|
|
47c57ff665 | ||
|
|
1cb330016b | ||
|
|
497652d1b4 | ||
|
|
20eb7aa381 | ||
|
|
4a3c2f92b1 | ||
|
|
5647e64265 | ||
|
|
c113debf46 | ||
|
|
3f0ae4a58c | ||
|
|
3d5b8183e8 | ||
|
|
789ef8f783 | ||
|
|
94df175ca3 | ||
|
|
c8bcefb0e5 | ||
|
|
fc52f59eb8 | ||
|
|
a80389e5c7 | ||
|
|
923276422b | ||
|
|
7c9192f3a0 | ||
|
|
51d139b7aa | ||
|
|
8fe1bdb6f7 | ||
|
|
5b288bc0d1 | ||
|
|
27cd769c76 | ||
|
|
5bb370869d | ||
|
|
ec68f64db4 | ||
|
|
4cf8f146c9 | ||
|
|
61cf317541 | ||
|
|
a2066c9b41 | ||
|
|
817b152704 | ||
|
|
cc73494c91 | ||
|
|
64ed4083b9 | ||
|
|
77e8c58cc6 | ||
|
|
0cd2eab309 | ||
|
|
7d8c843c74 | ||
|
|
da1c2d6914 | ||
|
|
3a949301f9 | ||
|
|
585eebab49 | ||
|
|
45c3e4686e | ||
|
|
52f2e67952 | ||
|
|
929774aa5b | ||
|
|
e711e338e7 | ||
|
|
d2aae774a0 | ||
|
|
322e8a1b07 | ||
|
|
d154f8d71b | ||
|
|
0931afd84c | ||
|
|
837548cfd5 | ||
|
|
a6cabbca79 | ||
|
|
82f19fe717 | ||
|
|
82be281153 | ||
|
|
0112ca775f | ||
|
|
b30b812b93 | ||
|
|
bace64c306 | ||
|
|
be49b641e3 | ||
|
|
5ac8c2f516 | ||
|
|
168ae1d82b | ||
|
|
b4a2e28fc3 | ||
|
|
ca3ae21286 | ||
|
|
9f30c1af09 | ||
|
|
e1f9f64910 | ||
|
|
2634dd8335 | ||
|
|
42b4c78d4c | ||
|
|
54174c1b0f | ||
|
|
9fad359ae9 | ||
|
|
1cbf460223 | ||
|
|
8a3aa007b8 | ||
|
|
21b02a39e4 | ||
|
|
0d8afde2c3 | ||
|
|
f366158d02 | ||
|
|
2c8aac2123 | ||
|
|
2d9f6fbcfc | ||
|
|
31c845823d | ||
|
|
f949da055e | ||
|
|
0cd1654f88 | ||
|
|
a71ae28192 | ||
|
|
bd56c3522b | ||
|
|
a00152b8b6 | ||
|
|
4235eb812e | ||
|
|
ed145234b2 | ||
|
|
7c2597ddc7 | ||
|
|
2b1f8182f2 | ||
|
|
ded9e7637a | ||
|
|
c42fb85bae | ||
|
|
33edc4291b | ||
|
|
df89d804c5 | ||
|
|
89da44d715 | ||
|
|
789faad645 | ||
|
|
8c7267cbee | ||
|
|
96c12ffff1 | ||
|
|
783729a6f6 | ||
|
|
60d4dbabdf | ||
|
|
a77ddcdbc2 | ||
|
|
f4c8a776a4 | ||
|
|
efbaf08483 |
@@ -25,6 +25,6 @@ esbuild
|
||||
platform: 'browser',
|
||||
format: 'esm',
|
||||
sourcemap: true,
|
||||
target: 'esnext'
|
||||
target: 'es2019'
|
||||
})
|
||||
.catch(() => process.exit(1))
|
||||
|
||||
@@ -38,10 +38,11 @@ export default defineConfig({
|
||||
fileName: 'index'
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['react'],
|
||||
external: ['react', '@nhost/react'],
|
||||
output: {
|
||||
globals: {
|
||||
react: 'react'
|
||||
react: 'react',
|
||||
'@nhost/react': '@nhost/react'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30
.github/workflows/changesets.yaml
vendored
30
.github/workflows/changesets.yaml
vendored
@@ -16,29 +16,33 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Cache pnpm modules
|
||||
uses: actions/cache@v2
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: pnpm/action-setup@v2.1.0
|
||||
with:
|
||||
version: 6.30.1
|
||||
run_install: true
|
||||
|
||||
version: 6.32.3
|
||||
# run_install: true
|
||||
- name: Use Node.js 17
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '17.8.0'
|
||||
cache: 'pnpm'
|
||||
- name: Pick the right npm version
|
||||
# * See: https://github.com/pnpm/pnpm/issues/4348
|
||||
run: npm install --global npm@8.4
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Create PR or Publish release
|
||||
id: changesets
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
version: pnpm ci:version
|
||||
version: pnpm run ci:version
|
||||
commit: 'chore: update versions'
|
||||
title: 'chore: update versions'
|
||||
publish: pnpm release
|
||||
publish: pnpm run release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
15
.github/workflows/contributors.yaml
vendored
Normal file
15
.github/workflows/contributors.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
name: Add contributors
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
contrib-readme-job:
|
||||
runs-on: ubuntu-latest
|
||||
name: A job to automate contrib in readme
|
||||
steps:
|
||||
- name: Contribute List
|
||||
uses: akhilmhdh/contributors-readme-action@v2.3.4
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
59
.github/workflows/tests.yaml
vendored
59
.github/workflows/tests.yaml
vendored
@@ -27,45 +27,44 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x, 14.x, 16.x]
|
||||
|
||||
node-version: [14, 16]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Cache pnpm modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-
|
||||
|
||||
- uses: pnpm/action-setup@v2.0.1
|
||||
with:
|
||||
version: 6.30.1
|
||||
run_install: true
|
||||
|
||||
- name: Cache turbo
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ./node_modules/.cache
|
||||
key: ${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.node-version }}
|
||||
|
||||
- name: Install nhost CLI
|
||||
run: curl -L https://raw.githubusercontent.com/nhost/cli/main/get.sh | bash
|
||||
|
||||
- name: Start Nhost Backend
|
||||
run: |
|
||||
cd examples/testing-project
|
||||
cp -R examples/testing-project /tmp/
|
||||
cd /tmp/testing-project
|
||||
nhost dev &
|
||||
|
||||
nhost dev -d --no-browser &
|
||||
- name: Wait for Nhost Backend to start
|
||||
run: |
|
||||
pnpm dlx wait-on http://localhost:1337/v1/auth/healthz -i 500 -t 120000
|
||||
- uses: pnpm/action-setup@v2.2.1
|
||||
with:
|
||||
version: 6.32.3
|
||||
|
||||
- run: pnpm run ci
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Cache turbo
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ./node_modules/.cache/turbo
|
||||
key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
turbo-${{ github.job }}-${{ github.ref_name }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Wait for Nhost
|
||||
run: pnpm run wait
|
||||
|
||||
- name: Build, tests and lint
|
||||
run: pnpm run ci
|
||||
|
||||
228
README.md
228
README.md
@@ -1,8 +1,8 @@
|
||||
<div align="center">
|
||||
<img width="237" src="https://raw.githubusercontent.com/nhost/nhost/main/assets/logo.png"/>
|
||||

|
||||
|
||||
<br />
|
||||
<br />
|
||||
<div align="center">
|
||||
|
||||
# Nhost
|
||||
|
||||
<a href="https://docs.nhost.io/get-started">Quickstart</a>
|
||||
<span> • </span>
|
||||
@@ -20,9 +20,9 @@
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
**Nhost is a serverless backend for web and mobile apps** and is built with the following things in mind:
|
||||
**Nhost is a open-source GraphQL backend,** built with the following things in mind:
|
||||
|
||||
- Open Source
|
||||
- Open-Source
|
||||
- Developer Productivity
|
||||
- SQL
|
||||
- GraphQL
|
||||
@@ -36,6 +36,15 @@ Nhost consists of open source software:
|
||||
- Serverless Functions: Node.js (JavaScript and TypeScript)
|
||||
- [Nhost CLI](https://docs.nhost.io/reference/cli) for local development
|
||||
|
||||
## Architecture of Nhost
|
||||
|
||||
<div align="center">
|
||||
<br />
|
||||
<img src="assets/nhost-diagram.png"/>
|
||||
<br />
|
||||
<br />
|
||||
</div>
|
||||
|
||||
Visit [https://docs.nhost.io](http://docs.nhost.io) for the complete documentation.
|
||||
|
||||
# How to get started
|
||||
@@ -92,8 +101,8 @@ Nhost libraries and tools
|
||||
- [JavaScript/TypeScript SDK](https://docs.nhost.io/reference/sdk)
|
||||
- [Dart and Flutter SDK](https://github.com/nhost/nhost-dart)
|
||||
- [Nhost CLI](https://docs.nhost.io/reference/cli)
|
||||
- [Nhost React Auth](https://docs.nhost.io/reference/supporting-libraries/react-auth)
|
||||
- [Nhost React Apollo](https://docs.nhost.io/reference/supporting-libraries/react-apollo)
|
||||
- [Nhost React](https://docs.nhost.io/reference/react)
|
||||
- [Nhost Next.js](https://docs.nhost.io/reference/nextjs)
|
||||
|
||||
## Community ❤️
|
||||
|
||||
@@ -101,7 +110,7 @@ First and foremost: **Star and watch this repository** to stay up-to-date.
|
||||
|
||||
Also, follow Nhost on [GitHub Discussions](https://github.com/nhost/nhost/discussions), our [Blog](https://nhost.io/blog), and on [Twitter](https://twitter.com/nhostio). You can chat with the team and other members on [Discord](https://discord.com/invite/9V7Qb2U) and follow our tutorials and other video material at [YouTube](https://www.youtube.com/channel/UCJ7irtvV9Y0EQMxpabb6ntg?view_as=subscriber).
|
||||
|
||||
## Nhost is Open Source
|
||||
### Nhost is Open Source
|
||||
|
||||
This repository, and most of our other open source projects, are licensed under the MIT license.
|
||||
|
||||
@@ -113,6 +122,203 @@ Here are some ways of contributing to making Nhost better:
|
||||
- Join our [Discord](https://discord.com/invite/9V7Qb2U) and connect with other members to share and learn from.
|
||||
- Send a pull request to any of our [open source repositories](https://github.com/nhost) on Github. Check our [contribution guide](https://github.com/nhost/nhost/blob/main/CONTRIBUTING.md) for more details about how to contribute. We're looking forward to your contribution!
|
||||
|
||||
## Security
|
||||
### Contributors
|
||||
|
||||
If you discover a security vulnerability within Nhost, please e-mail [security@nhost.io](mailto:security@nhost.io). All security vulnerabilities will be promptly addressed.
|
||||
<!-- readme: contributors -start -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/plmercereau">
|
||||
<img src="https://avatars.githubusercontent.com/u/24897252?v=4" width="100;" alt="plmercereau"/>
|
||||
<br />
|
||||
<sub><b>Pilou</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/elitan">
|
||||
<img src="https://avatars.githubusercontent.com/u/331818?v=4" width="100;" alt="elitan"/>
|
||||
<br />
|
||||
<sub><b>Johan Eliasson</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/szilarddoro">
|
||||
<img src="https://avatars.githubusercontent.com/u/310881?v=4" width="100;" alt="szilarddoro"/>
|
||||
<br />
|
||||
<sub><b>Szilárd Dóró</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/nunopato">
|
||||
<img src="https://avatars.githubusercontent.com/u/1523504?v=4" width="100;" alt="nunopato"/>
|
||||
<br />
|
||||
<sub><b>Nuno Pato</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/subatuba21">
|
||||
<img src="https://avatars.githubusercontent.com/u/34824571?v=4" width="100;" alt="subatuba21"/>
|
||||
<br />
|
||||
<sub><b>Subha Das</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/guicurcio">
|
||||
<img src="https://avatars.githubusercontent.com/u/20285232?v=4" width="100;" alt="guicurcio"/>
|
||||
<br />
|
||||
<sub><b>Guido Curcio</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/sebagudelo">
|
||||
<img src="https://avatars.githubusercontent.com/u/43288271?v=4" width="100;" alt="sebagudelo"/>
|
||||
<br />
|
||||
<sub><b>Sebagudelo</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/mrinalwahal">
|
||||
<img src="https://avatars.githubusercontent.com/u/9859731?v=4" width="100;" alt="mrinalwahal"/>
|
||||
<br />
|
||||
<sub><b>Mrinal Wahal</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/gdangelo">
|
||||
<img src="https://avatars.githubusercontent.com/u/4352286?v=4" width="100;" alt="gdangelo"/>
|
||||
<br />
|
||||
<sub><b>Grégory D'Angelo</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/FuzzyReason">
|
||||
<img src="https://avatars.githubusercontent.com/u/62517920?v=4" width="100;" alt="FuzzyReason"/>
|
||||
<br />
|
||||
<sub><b>Vadim Smirnov</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/subhendukundu">
|
||||
<img src="https://avatars.githubusercontent.com/u/20059141?v=4" width="100;" alt="subhendukundu"/>
|
||||
<br />
|
||||
<sub><b>Subhendu Kundu</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/chrtze">
|
||||
<img src="https://avatars.githubusercontent.com/u/3797215?v=4" width="100;" alt="chrtze"/>
|
||||
<br />
|
||||
<sub><b>Christopher Möller</b></sub>
|
||||
</a>
|
||||
</td></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">
|
||||
<a href="https://github.com/jerryjappinen">
|
||||
<img src="https://avatars.githubusercontent.com/u/1101002?v=4" width="100;" alt="jerryjappinen"/>
|
||||
<br />
|
||||
<sub><b>Jerry Jäppinen</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/mustafa-hanif">
|
||||
<img src="https://avatars.githubusercontent.com/u/30019262?v=4" width="100;" alt="mustafa-hanif"/>
|
||||
<br />
|
||||
<sub><b>Mustafa Hanif</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/Savinvadim1312">
|
||||
<img src="https://avatars.githubusercontent.com/u/16936043?v=4" width="100;" alt="Savinvadim1312"/>
|
||||
<br />
|
||||
<sub><b>Savin Vadim</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/ahmic">
|
||||
<img src="https://avatars.githubusercontent.com/u/13452362?v=4" width="100;" alt="ahmic"/>
|
||||
<br />
|
||||
<sub><b>Amir Ahmic</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/akd-io">
|
||||
<img src="https://avatars.githubusercontent.com/u/30059155?v=4" width="100;" alt="akd-io"/>
|
||||
<br />
|
||||
<sub><b>Anders Kjær Damgaard</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/rustyb">
|
||||
<img src="https://avatars.githubusercontent.com/u/53086?v=4" width="100;" alt="rustyb"/>
|
||||
<br />
|
||||
<sub><b>Colin Broderick</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/dohomi">
|
||||
<img src="https://avatars.githubusercontent.com/u/489221?v=4" width="100;" alt="dohomi"/>
|
||||
<br />
|
||||
<sub><b>Dominic Garms</b></sub>
|
||||
</a>
|
||||
</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">
|
||||
<a href="https://github.com/alveshelio">
|
||||
<img src="https://avatars.githubusercontent.com/u/8176422?v=4" width="100;" alt="alveshelio"/>
|
||||
<br />
|
||||
<sub><b>Helio Alves</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/nkhdo">
|
||||
<img src="https://avatars.githubusercontent.com/u/26102306?v=4" width="100;" alt="nkhdo"/>
|
||||
<br />
|
||||
<sub><b>Hoang Do</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/ghoshnirmalya">
|
||||
<img src="https://avatars.githubusercontent.com/u/6391763?v=4" width="100;" alt="ghoshnirmalya"/>
|
||||
<br />
|
||||
<sub><b>Nirmalya Ghosh</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<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">
|
||||
<a href="https://github.com/quentin-decre">
|
||||
<img src="https://avatars.githubusercontent.com/u/1137511?v=4" width="100;" alt="quentin-decre"/>
|
||||
<br />
|
||||
<sub><b>Quentin Decré</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/komninoschat">
|
||||
<img src="https://avatars.githubusercontent.com/u/29049104?v=4" width="100;" alt="komninoschat"/>
|
||||
<br />
|
||||
<sub><b>Komninos</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
</table>
|
||||
<!-- readme: contributors -end -->
|
||||
|
||||
BIN
assets/nhost-diagram.png
Normal file
BIN
assets/nhost-diagram.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": ["next", "prettier"],
|
||||
"rules": {
|
||||
"@next/next/no-img-element": "off",
|
||||
"import/no-default-export": "off",
|
||||
"react/self-closing-comp": "warn",
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
"react/no-unescaped-entities": "off",
|
||||
"react/prop-types": "off",
|
||||
"jsx-a11y/anchor-is-valid": "off",
|
||||
"no-console": "warn"
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"settings": { "react": { "version": "detect" } }
|
||||
}
|
||||
22
docs/.gitignore
vendored
22
docs/.gitignore
vendored
@@ -1,2 +1,20 @@
|
||||
!lib
|
||||
!.prettierignore
|
||||
# Dependencies
|
||||
/node_modules
|
||||
|
||||
# Production
|
||||
/build
|
||||
|
||||
# Generated files
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
node_modules/
|
||||
.next/
|
||||
11
docs/.prettierrc.json
Normal file
11
docs/.prettierrc.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false,
|
||||
"endOfLine": "auto",
|
||||
"semi": true
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
# nhost-documentation
|
||||
|
||||
## null
|
||||
### Patch Changes
|
||||
|
||||
- 03562af: Build in CommonJS and ESM instead of UMD and ESM as the UMD bundle generated by the default Vite lib build mode doesn't work with NodeJS
|
||||
@@ -1,43 +1,37 @@
|
||||
# Nhost Documentation
|
||||
# Nhost Docs
|
||||
|
||||
## Get started
|
||||
This documentation describes how to build, start and test the documentation locally.
|
||||
|
||||
1. Install dependencies: `yarn`
|
||||
2. Start dev server: `yarn dev`
|
||||
### Installation
|
||||
|
||||
## NOTES;
|
||||
|
||||
The content is copied from the main `nhost/nhost` repo. This repo is only to modify styles/react components.
|
||||
|
||||
## Structure
|
||||
|
||||
The `order.ts` file contains the main order for the entire structure of `posts`. The keys are `categories` and the values are `subcategories` in which contains the order the posts.
|
||||
|
||||
```
|
||||
export const orderTwo = {
|
||||
'get-started': {
|
||||
'quick-start': ['index', 'schema', 'javascript-client', 'permissions'],
|
||||
authentication: ['index'],
|
||||
'cli-workflow': ['index', 'workflow-setup', 'install-cli', 'local-changes', 'metadata-and-serverless-functions'],
|
||||
upgrade: ['index']
|
||||
},
|
||||
platform: {
|
||||
database: ['index', 'permissions', 'graphql'],
|
||||
authentication: ['index', 'user-management', 'sign-in-methods', 'social-login', 'email-templates'],
|
||||
storage: ['index'],
|
||||
'serverless-functions': ['index', 'event-triggers'],
|
||||
nhost: ['index', 'environment-variables', 'github-integration', 'local-development']
|
||||
},
|
||||
reference: {
|
||||
sdk: ['index', 'graphql', 'authentication', 'storage', 'functions'],
|
||||
cli: ['index'],
|
||||
'supporting-libraries': ['react-apollo', 'react-auth']
|
||||
}
|
||||
};
|
||||
```bash
|
||||
$ pnpm i
|
||||
```
|
||||
|
||||
Metadata such as the `title` of the file that appears on the nav is on the frontmatter of each markdown file. The file name becomes the final url. Each top-level folder appears on the header as main navigation, each subfolder becomes a main subcategory of the nav and posts are included under each subcategory.
|
||||
### Local Development
|
||||
|
||||
In order to create a new file you place it its proper subcategory and modify the category on the `order.ts` file such as `sdk: ["javascript-sdk", "react-auth", "react-apollo"],` -> `sdk: ["javascript-sdk", "react-auth", "vue"]`
|
||||
```bash
|
||||
$ pnpm start
|
||||
```
|
||||
|
||||
Each subCategory e.g. `reference` or `tutorials` has an `index.mdx` file. If a new subcategory is added, a file has to be created for it.
|
||||
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
$ pnpm build
|
||||
```
|
||||
|
||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||
|
||||
### Serve
|
||||
|
||||
```bash
|
||||
$ pnpm serve
|
||||
```
|
||||
|
||||
This command serves the static content from the `build` directory.
|
||||
|
||||
### Contributing
|
||||
|
||||
All pull requests are greatly appreciated! See our [contributing guide](https://github.com/nhost/nhost/blob/main/CONTRIBUTING.md) to get started.
|
||||
|
||||
3
docs/babel.config.js
Normal file
3
docs/babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
|
||||
};
|
||||
@@ -1,81 +0,0 @@
|
||||
import Text from '@/components/ui/Text'
|
||||
import { motion } from 'framer-motion'
|
||||
import { useRouter } from 'next/dist/client/router'
|
||||
import React, { useState } from 'react'
|
||||
import createKebabCase from '../utils/createKebabCase'
|
||||
import Permalink from './icons/Permalink'
|
||||
|
||||
export interface AnchorLinkProps {
|
||||
children?: any
|
||||
id?: string
|
||||
size?: 'tiny' | 'small' | 'normal' | 'large' | 'big' | 'heading'
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function AnchorLink({ children, id, size, className }: AnchorLinkProps) {
|
||||
const {
|
||||
query: { category, subcategory, post }
|
||||
} = useRouter()
|
||||
const [showPermaLink, setShowPermalink] = useState(false)
|
||||
|
||||
const isQuoted = typeof children !== 'string'
|
||||
|
||||
return (
|
||||
<div
|
||||
id={
|
||||
id
|
||||
? children.split('/')[1]
|
||||
: createKebabCase(
|
||||
`#${isQuoted ? (children.props ? children.props.children : children) : children}`
|
||||
)
|
||||
}
|
||||
className={className}
|
||||
onMouseOver={() => setShowPermalink(true)}
|
||||
onMouseLeave={() => setShowPermalink(false)}
|
||||
>
|
||||
<span id={createKebabCase(`${children}`)} className={'flex flex-row relative'}>
|
||||
{showPermaLink ? (
|
||||
<motion.span
|
||||
className="absolute self-center w-4 h-4 align-middle -left-5"
|
||||
onClick={() => {
|
||||
navigator.clipboard
|
||||
.writeText(
|
||||
`https://docs.nhost.io/${category}/${subcategory}/${post}/${
|
||||
id
|
||||
? id
|
||||
: createKebabCase(
|
||||
`#${
|
||||
isQuoted
|
||||
? children.props
|
||||
? children.props.children
|
||||
: children
|
||||
: children
|
||||
}`
|
||||
)
|
||||
}`
|
||||
)
|
||||
.catch((e) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(e)
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Permalink className="w-4 h-4" />
|
||||
</motion.span>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<Text
|
||||
variant="a"
|
||||
href={createKebabCase(
|
||||
`#${isQuoted ? (children.props ? children.props.children : children) : children}`
|
||||
)}
|
||||
color="greyscaleDark"
|
||||
className="font-medium break-all"
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import React from 'react'
|
||||
import { useState } from 'react'
|
||||
|
||||
import Check from './icons/Check'
|
||||
import Copy from './icons/Copy'
|
||||
|
||||
export default function Command({ children }) {
|
||||
const [copied, setCopied] = useState(false)
|
||||
return (
|
||||
<div className="my-1 flex-row inline-flex self-center text-xs bg-gray-50 pl-2 pr-1.5 text-gray-900 font-mono leading-6 py-0.25 border border-gray-200 rounded-md">
|
||||
<span className="text-verydark mr-1.5 self-center">$</span>
|
||||
{children}
|
||||
<button
|
||||
className="ml-1.5 self-center inline-block cursor-pointer"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(children).catch((e) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(e)
|
||||
})
|
||||
setCopied(true)
|
||||
setTimeout(() => {
|
||||
setCopied(false)
|
||||
}, 1000)
|
||||
}}
|
||||
>
|
||||
{/* <Tooltip text={"Copied!"}> */}
|
||||
{copied ? (
|
||||
<Check className="w-3.5 h-3.5 mr-0.5 text-greenDark transition-colors self-center" />
|
||||
) : (
|
||||
<Copy className="w-4 h-4 text-gray-500 transition-colors hover:text-gray-900" />
|
||||
)}
|
||||
{/* </Tooltip> */}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
export function Container({ children }) {
|
||||
return (
|
||||
<div className="mx-10 px-2 sm:px-10 md:px-20 lg:px-0 flex flex-row md:max-w-container pb-20 md:mx-auto mt-8 lg:space-x-20">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import markdownStyles from '@/styles/markdown-styles.module.css'
|
||||
import { DOCS_GITHUB_ENDPOINT } from '@/utils/constants'
|
||||
import { MDXRemote } from 'next-mdx-remote'
|
||||
import { useRouter } from 'next/dist/client/router'
|
||||
import React from 'react'
|
||||
|
||||
import GithubIcon from './icons/GithubIcon'
|
||||
import Button from './ui/Button/Button'
|
||||
import Text from './ui/Text/Text'
|
||||
|
||||
function getGithubLink(category, subcategory, post) {
|
||||
if (post) return `${DOCS_GITHUB_ENDPOINT}${category}/${subcategory}/${post}.mdx`
|
||||
else if (subcategory) return `${DOCS_GITHUB_ENDPOINT}${category}/${subcategory}/index.mdx`
|
||||
else {
|
||||
return `${DOCS_GITHUB_ENDPOINT}${category}/index.mdx`
|
||||
}
|
||||
}
|
||||
|
||||
export function Content({ mdxSource, components, frontmatter }) {
|
||||
const router = useRouter()
|
||||
return (
|
||||
<div className="flex flex-col w-full h-full mt-2">
|
||||
<div className="flex flex-row mb-4 place-content-between">
|
||||
<Text color="greyscaleDark" className="font-medium cursor-pointer" size="heading">
|
||||
{frontmatter.title}
|
||||
</Text>
|
||||
<div className="self-center hidden md:block">
|
||||
<Button
|
||||
Component="a"
|
||||
variant="secondary"
|
||||
className="invisible md:visible"
|
||||
href={getGithubLink(router.query.category, router.query.subcategory, router.query.post)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
type={null}
|
||||
>
|
||||
Edit This Page
|
||||
<GithubIcon className="w-3.5 h-3.5 ml-1.5 text-greyscaleDark self-center" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={markdownStyles['markdown']}>
|
||||
<MDXRemote {...mdxSource} components={components} lazy />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { withRouter } from 'next/router'
|
||||
import Link from 'next/link'
|
||||
import React, { Children } from 'react'
|
||||
|
||||
const CustomLink = ({ router, children, ...props }) => {
|
||||
const child = Children.only(children)
|
||||
|
||||
let className = child.props.className || ''
|
||||
const pathname = `/${router.query.category}/${router.query.post}`
|
||||
|
||||
if (pathname === props.href && props.activeClassName) {
|
||||
className = `${className} ${props.activeClassName}`.trim()
|
||||
}
|
||||
|
||||
delete props.activeClassName
|
||||
|
||||
// @ts-ignore
|
||||
return <Link {...props}>{React.cloneElement(child, { className })}</Link>
|
||||
}
|
||||
|
||||
export default withRouter(CustomLink)
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function Divider() {
|
||||
return <div className="divider mt-6 mb-4 order-2" />
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
import Button from '@/components/ui/Button'
|
||||
import siteLinks from '@/data/siteLinks.json'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { Newsletter } from './Newsletter'
|
||||
|
||||
// import Input from './ui/Input/Input';
|
||||
export default function Footer() {
|
||||
const [email, setEmail] = useState('')
|
||||
|
||||
return (
|
||||
<div className="bg-verydark">
|
||||
<div className="max-w-mxcontainer px-5 mx-auto">
|
||||
<div className="flex flex-col pt-20">
|
||||
{/* Logo and CTA */}
|
||||
<div className="place-content-between flex flex-row">
|
||||
<div className="">
|
||||
<img
|
||||
src="/logos/nhost-footer-logo.svg"
|
||||
width={141.57}
|
||||
height={48}
|
||||
alt="Nhost white logo"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row self-center">
|
||||
<Button
|
||||
Component="a"
|
||||
variant="secondary"
|
||||
className="md:visible invisible mr-2 text-white cursor-pointer"
|
||||
href="mailto:hello@nhost.io"
|
||||
type={null}
|
||||
>
|
||||
Contact Us
|
||||
</Button>
|
||||
<Button
|
||||
Component="a"
|
||||
variant="primary"
|
||||
href="https://app.nhost.io"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="cursor-pointer"
|
||||
type={null}
|
||||
>
|
||||
<span className="md:block hidden">Sign up or Log in</span>
|
||||
<span className="md:hidden">Sign up</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/* All links */}
|
||||
{/* @FIX: space-x on the firSubscribest one. */}
|
||||
<div className="font-display md:flex-row flex flex-col mt-12">
|
||||
<div className="gap-14 md:grid-flow-col md:grid-cols-5 grid grid-flow-row grid-cols-1">
|
||||
{siteLinks.siteLinks.map((siteLink, i) => {
|
||||
return (
|
||||
<FooterLinks
|
||||
key={siteLink.text + i}
|
||||
title={siteLink.text}
|
||||
links={siteLink.links}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<Newsletter />
|
||||
{/* <Newsletter email={email} setEmail={setEmail} /> */}
|
||||
{/* Socials */}
|
||||
{/* @FIX: mt is 103px */}
|
||||
<div className="md:mx-0 place-content-between font-display md:flex-row flex flex-col pb-2 mx-auto mt-24">
|
||||
<div className="pb-2">
|
||||
<ul className="flex flex-row space-x-6">
|
||||
<li className="items-center self-center align-middle">
|
||||
<a href="https://github.com/nhost" target="_blank" rel="noreferrer">
|
||||
<img src="/logos/Github.svg" width={25} height={25} alt="Nhost on GitHub" />
|
||||
</a>
|
||||
</li>
|
||||
<li className="items-center self-center align-middle">
|
||||
<a href="https://twitter.com/nhostio" target="_blank" rel="noreferrer">
|
||||
<img src="/logos/Twitter.svg" width={25} height={25} alt="Nhost on Twitter" />
|
||||
</a>
|
||||
</li>
|
||||
<li className="items-center self-center align-middle">
|
||||
<a
|
||||
href="https://www.linkedin.com/company/nhost/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img src="/logos/Linkedin.svg" width={25} height={25} alt="Nhost in LinkedIn" />
|
||||
</a>
|
||||
</li>
|
||||
<li className="items-center self-center align-middle">
|
||||
<a href="https://discord.com/invite/9V7Qb2U" target="_blank" rel="noreferrer">
|
||||
<img
|
||||
src="/logos/Discord.svg"
|
||||
width={25}
|
||||
height={25}
|
||||
alt="Nhost community on Discord"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="md:pt-0 md:space-y-0 md:flex-row flex flex-col pt-2 space-y-4 text-xs font-medium text-white">
|
||||
<a
|
||||
className="translucent self-center"
|
||||
href="https://nhost.io/privacy-policy"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Privacy Policy
|
||||
</a>
|
||||
<a
|
||||
className="md:pl-6 translucent self-center"
|
||||
href="https://nhost.io/terms-of-service"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Terms of Service
|
||||
</a>
|
||||
|
||||
<a
|
||||
className="md:pl-6 translucent self-center"
|
||||
href="https://nhost.io"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
nhost.io 2022
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
interface FooterLinkProps {
|
||||
title: string
|
||||
links: Links[]
|
||||
}
|
||||
interface Links {
|
||||
name: string
|
||||
href: string
|
||||
}
|
||||
function FooterLinks({ title, links }: FooterLinkProps) {
|
||||
return (
|
||||
<div>
|
||||
{/* color */}
|
||||
<h1 className="font-medium text-gray-700 uppercase">{title}</h1>
|
||||
<ul className="mt-4 space-y-4">
|
||||
{links.map((link) => {
|
||||
return (
|
||||
<li key={link.name} className="text-white font-normal text-sm+ cursor-pointer">
|
||||
<Link href={link.href}>{link.name}</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
import { useNavData } from '@/components/NavDataContext'
|
||||
import { ArrowLeftIcon, MenuIcon } from '@heroicons/react/outline'
|
||||
import clsx from 'clsx'
|
||||
import { useRouter } from 'next/dist/client/router'
|
||||
import Link from 'next/link'
|
||||
import React, { MouseEvent, useEffect, useState } from 'react'
|
||||
import Button from '../components/ui/Button'
|
||||
import { Nav } from './Nav'
|
||||
|
||||
export default function Header() {
|
||||
const [mobileMenu, setMobileMenu] = useState(false)
|
||||
const router = useRouter()
|
||||
const GithubStarsCounter = () => {
|
||||
const repoUrl = `https://api.github.com/repos/nhost/nhost`
|
||||
const [count, setCount] = useState(null)
|
||||
const format = (n: number) => (n > 1000 ? `${(n / 1000).toFixed(1)}k` : n)
|
||||
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
const data = await fetch(repoUrl).then((res) => res.json())
|
||||
setCount(data.stargazers_count)
|
||||
})()
|
||||
}, [repoUrl])
|
||||
|
||||
return (
|
||||
<a
|
||||
className="text-base font-medium leading-snug flex flex-row items-center justify-center px-2.5 py-1.5 rounded opacity-50 hover:opacity-100 mr-8"
|
||||
href="https://github.com/nhost/nhost"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
className="mr-2"
|
||||
src="/logos/Github2.svg"
|
||||
width={20}
|
||||
height={20}
|
||||
alt="Nhost on GitHub"
|
||||
/>
|
||||
{count === null ? 0 : format(count)}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
function handleMobileMenuOpen() {
|
||||
setMobileMenu(true)
|
||||
}
|
||||
|
||||
function handleMobileMenuClose() {
|
||||
setMobileMenu(false)
|
||||
}
|
||||
|
||||
if (mobileMenu) {
|
||||
return <MobileNav onClose={handleMobileMenuClose} />
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="bg-white md:max-w-full menu-card rounded-md px-4 py-0.5 mx-2">
|
||||
<div className="md:max-w-header2 mx-auto font-display flex flex-row antialiased">
|
||||
<div className="flex flex-row w-full mx-auto place-content-between py-2">
|
||||
<div className="flex flex-row">
|
||||
<button
|
||||
className="md:hidden w-8 h-8 flex items-center justify-center cursor-pointer text-greyscaleDark"
|
||||
aria-label="Open menu"
|
||||
onClick={handleMobileMenuOpen}
|
||||
>
|
||||
<MenuIcon className="h-6 w-6" />
|
||||
</button>
|
||||
|
||||
<Link href="/get-started" passHref>
|
||||
<a className="hidden ml-3 sm:ml-0 self-center md:flex flex-row cursor-pointer">
|
||||
<img src="/images/nhost-docs.svg" width={110} height={35} alt="Nhost white logo" />
|
||||
<h1 className="self-center ml-6 font-medium text-greyscaleDark">DOCS</h1>
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<div className="ml-20 hidden md:flex flex-row self-center ">
|
||||
<ul className="flex flex-row items-center self-center antialiased font-medium text-greyscaleGrey font-display">
|
||||
<Link href="/get-started" passHref={true}>
|
||||
<a
|
||||
className={clsx(
|
||||
'cursor-pointer text-base- self-center hover:text-greyscaleDark transition-colors duration-200 py-3',
|
||||
router.query.category === 'get-started' && 'text-greyscaleDark'
|
||||
)}
|
||||
>
|
||||
Get Started
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/platform" passHref={true}>
|
||||
<a
|
||||
className={clsx(
|
||||
'ml-12 cursor-pointer text-base- self-center hover:text-greyscaleDark transition-colors duration-200 py-3',
|
||||
router.query.category === 'platform' && 'text-greyscaleDark'
|
||||
)}
|
||||
>
|
||||
Platform
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<Link href="/reference" passHref={true}>
|
||||
<a
|
||||
className={clsx(
|
||||
'ml-12 cursor-pointer text-base- self-center hover:text-greyscaleDark transition-colors duration-200 py-3',
|
||||
router.query.category === 'reference' && 'text-greyscaleDark'
|
||||
)}
|
||||
>
|
||||
Reference
|
||||
</a>
|
||||
</Link>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden sm:flex self-center">
|
||||
<GithubStarsCounter />
|
||||
<Button
|
||||
className="self-center"
|
||||
variant="primary"
|
||||
href={'https://app.nhost.io'}
|
||||
Component="a"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
type={null}
|
||||
>
|
||||
Go to Nhost
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
export type MobileNavProps = {
|
||||
onClose?: VoidFunction
|
||||
}
|
||||
|
||||
export function MobileNav({ onClose }: MobileNavProps) {
|
||||
const { getConvolutedNavByCategory } = useNavData()
|
||||
const router = useRouter()
|
||||
const [selectedMenuSlug, setSelectedMenuSlug] = useState<string | null>(null)
|
||||
const [selectedMenuName, setSelectedMenuName] = useState<string | null>(null)
|
||||
|
||||
function handleMenuSelect(event: MouseEvent<HTMLAnchorElement>, slug: string, name: string) {
|
||||
event.preventDefault()
|
||||
|
||||
setSelectedMenuSlug(slug)
|
||||
setSelectedMenuName(name)
|
||||
}
|
||||
|
||||
function clearMenuSelection() {
|
||||
setSelectedMenuSlug(null)
|
||||
setSelectedMenuName(null)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white menu-card rounded-lg px-4 pb-6 max-w-full mx-2">
|
||||
<div className="flex flex-col w-full py-3 mx-auto">
|
||||
<div className="grid grid-flow-col justify-between items-center">
|
||||
{!selectedMenuSlug && (
|
||||
<>
|
||||
<button
|
||||
className="w-8 h-8 flex items-center justify-center cursor-pointer text-greyscaleDark"
|
||||
aria-label="Close menu"
|
||||
onClick={onClose}
|
||||
>
|
||||
<MenuIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
|
||||
<Link href="/get-started" passHref>
|
||||
<a
|
||||
className="ml-3 sm:ml-0 self-center flex flex-row cursor-pointer"
|
||||
onClick={onClose}
|
||||
>
|
||||
<img
|
||||
src="/images/nhost-docs.svg"
|
||||
width={110}
|
||||
height={35}
|
||||
alt="Nhost white logo"
|
||||
/>
|
||||
<h1 className="self-center ml-5 font-medium text-greyscaleDark">DOCS</h1>
|
||||
</a>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
{selectedMenuSlug && (
|
||||
<button
|
||||
className="ml-2 h-8 grid grid-flow-col gap-2 items-center justify-center cursor-pointer text-greyscaleDark"
|
||||
aria-label="Go back to main menu"
|
||||
onClick={clearMenuSelection}
|
||||
>
|
||||
<ArrowLeftIcon className="h-4 w-4" aria-hidden="true" />{' '}
|
||||
<span className="font-medium text-base-">{selectedMenuName}</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Placeholder for making logo appear correctly in the middle */}
|
||||
<div className="w-8 h-8" />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col py-6 mt-4 border-divide border-t border-b">
|
||||
{!selectedMenuSlug && (
|
||||
<ul className="flex flex-col font-medium text-greyscaleDark text-base- font-display space-y-4 text-left px-4">
|
||||
<li
|
||||
className={clsx(
|
||||
'cursor-pointer text-base- hover:text-greyscaleDark transition-colors duration-200 text-left ',
|
||||
router.query.category === 'get-started' && 'text-greyscaleDark'
|
||||
)}
|
||||
>
|
||||
<Link href="/get-started" passHref>
|
||||
<a
|
||||
className="block"
|
||||
onClick={(event) => handleMenuSelect(event, 'get-started', 'Get Started')}
|
||||
>
|
||||
Get Started
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
<li
|
||||
className={clsx(
|
||||
'cursor-pointer text-base- hover:text-greyscaleDark transition-colors duration-200 text-left',
|
||||
router.query.category === 'platform' && 'text-greyscaleDark'
|
||||
)}
|
||||
>
|
||||
<Link href="/platform">
|
||||
<a
|
||||
className="block"
|
||||
onClick={(event) => handleMenuSelect(event, 'platform', 'Platform')}
|
||||
>
|
||||
Platform
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
<li
|
||||
className={clsx(
|
||||
'cursor-pointer text-base- hover:text-greyscaleDark transition-colors duration-200',
|
||||
router.query.category === 'reference' && 'text-greyscaleDark'
|
||||
)}
|
||||
>
|
||||
<Link href="/reference">
|
||||
<a
|
||||
className="block"
|
||||
onClick={(event) => handleMenuSelect(event, 'reference', 'Reference')}
|
||||
>
|
||||
Reference
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{selectedMenuSlug && (
|
||||
<Nav
|
||||
category={selectedMenuSlug}
|
||||
categoryTitle={selectedMenuName}
|
||||
convolutedNav={getConvolutedNavByCategory(selectedMenuSlug)}
|
||||
onMenuSelected={onClose}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sm:flex self-center py-2">
|
||||
<Button
|
||||
className="self-center"
|
||||
variant="primary"
|
||||
href="https://app.nhost.io"
|
||||
Component="a"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
type={null}
|
||||
>
|
||||
Go to Nhost
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import Text from '@/components/ui/Text/Text'
|
||||
import createKebabCase from '@/utils/createKebabCase'
|
||||
import clsx from 'clsx'
|
||||
import { useRouter } from 'next/dist/client/router'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
||||
export function HeadingsNavigation(props) {
|
||||
const {
|
||||
query: { category, subcategory, post }
|
||||
} = useRouter()
|
||||
|
||||
return (
|
||||
<div className="hidden xl:flex flex-col mt-10 sticky top-20 w-full h-full pb-12 pl-4">
|
||||
<Text className="font-medium" color="greyscaleDark" size="normal">
|
||||
On this page
|
||||
</Text>
|
||||
<ul className="space-y-2 mt-2 pl-1">
|
||||
{props.headings.map((heading) => {
|
||||
return (
|
||||
<Link
|
||||
passHref
|
||||
key={heading.name}
|
||||
href={`/${category}/${subcategory}/${post}#${createKebabCase(heading.name)}`}
|
||||
>
|
||||
<li
|
||||
className={clsx(
|
||||
'text-blue hover:text-darkBlue transition-all duration-300 cursor-pointer hover:translate-x-0.5 transform',
|
||||
heading.depth === 1 && 'text-sm font-medium',
|
||||
heading.depth === 2 && 'pl-3 text-sm font-normal',
|
||||
heading.depth === 3 && 'pl-7 text-xs font-normal'
|
||||
)}
|
||||
>
|
||||
{heading.name}
|
||||
</li>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import { lightNhostTheme } from '@/data/lightTheme'
|
||||
import { useState } from 'react'
|
||||
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'
|
||||
import js from 'react-syntax-highlighter/dist/cjs/languages/hljs/javascript'
|
||||
|
||||
import Check from '../icons/Check'
|
||||
import Copy from '../icons/Copy'
|
||||
|
||||
// @ts-ignore -> add to types
|
||||
// @ts-ignore -> add to types
|
||||
SyntaxHighlighter.registerLanguage('js', js)
|
||||
// TODO highlight JSX
|
||||
SyntaxHighlighter.registerLanguage('jsx', js)
|
||||
|
||||
export interface CodeEditorProps {
|
||||
code: string
|
||||
fileName: string
|
||||
className: string
|
||||
fixed: boolean
|
||||
gradient: boolean
|
||||
deploy: boolean
|
||||
url?: string
|
||||
children: any
|
||||
}
|
||||
|
||||
const CodeEditor = (props: CodeEditorProps) => {
|
||||
const { children, url } = props
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="relative min-w-full pb-0 my-4 rounded-md">
|
||||
<div className="absolute right-0">
|
||||
<button
|
||||
className="ml-1.5 self-center inline-block cursor-pointer rounded-md mt-2 mr-2"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(children).catch((e) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(e)
|
||||
})
|
||||
setCopied(true)
|
||||
setTimeout(() => {
|
||||
setCopied(false)
|
||||
}, 1000)
|
||||
}}
|
||||
>
|
||||
{/* <Tooltip text={"Copied!"}> */}
|
||||
{copied ? (
|
||||
<Check className="w-3.5 h-3.5 mr-0.5 text-greenDark transition-colors self-center" />
|
||||
) : (
|
||||
<Copy className="w-4 h-4 text-gray-500 transition-colors hover:text-gray-900" />
|
||||
)}
|
||||
{/* </Tooltip> */}
|
||||
</button>
|
||||
</div>
|
||||
<SyntaxHighlighter
|
||||
style={lightNhostTheme}
|
||||
wrapLongLines={true}
|
||||
wrapLines={true}
|
||||
lineProps={{
|
||||
style: { wordBreak: 'break-all', whiteSpace: 'pre-wrap' }
|
||||
}}
|
||||
customStyle={{
|
||||
paddingLeft: '12px',
|
||||
fontSize: '13px'
|
||||
}}
|
||||
className="pt-2 rounded-md"
|
||||
showLineNumbers={false}
|
||||
>
|
||||
{children}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CodeEditor
|
||||
@@ -1,154 +0,0 @@
|
||||
import AnchorLink, { AnchorLinkProps } from '@/components/AnchorLink'
|
||||
import CodeComponent, { CodeEditorProps } from '@/components/MDX/CodeComponent'
|
||||
import Text, { TextProps } from '@/components/ui/Text'
|
||||
import clsx from 'clsx'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import React, { DetailedHTMLProps, HTMLProps, PropsWithChildren } from 'react'
|
||||
|
||||
import Command from '../Command'
|
||||
import Divider from '../Divider'
|
||||
|
||||
function Note({ children }: PropsWithChildren<unknown>) {
|
||||
return (
|
||||
<div className="px-5 py-5 my-5 space-y-2 text-white rounded-md bg-verydark">
|
||||
<Text className="text-white">Note</Text>
|
||||
<Text className="text-white">{children}</Text>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Video({
|
||||
src,
|
||||
...props
|
||||
}: DetailedHTMLProps<HTMLProps<HTMLSourceElement>, HTMLSourceElement>) {
|
||||
return (
|
||||
<div className="flex justify-center mx-10 my-8">
|
||||
<video width="800" controls>
|
||||
<source src={src} type="video/mp4" {...props} />
|
||||
</video>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const CustomLink = ({
|
||||
className,
|
||||
children,
|
||||
href,
|
||||
...props
|
||||
}: DetailedHTMLProps<HTMLProps<HTMLAnchorElement>, HTMLAnchorElement>) => {
|
||||
const isInternalLink = href && ['./', '../', '/', '#'].some((symbol) => href.startsWith(symbol))
|
||||
|
||||
if (isInternalLink) {
|
||||
return (
|
||||
<Link href={href} passHref>
|
||||
<a className={clsx('font-medium text-blue', className)} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
target="_blank"
|
||||
className={clsx('font-medium text-blue', className)}
|
||||
rel="noopener noreferrer"
|
||||
href={href}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
const components = {
|
||||
img: (props: DetailedHTMLProps<HTMLProps<HTMLImageElement>, HTMLImageElement>) => {
|
||||
return (
|
||||
<span className="block mx-10 mt-5 ">
|
||||
<img src={props.src} alt={props.alt} className="mx-auto mt-2" />
|
||||
</span>
|
||||
)
|
||||
},
|
||||
Video,
|
||||
Image,
|
||||
Text,
|
||||
Note,
|
||||
code: (props: CodeEditorProps) => {
|
||||
if (props.className && props.className.includes('language')) {
|
||||
return <CodeComponent {...props} />
|
||||
} else {
|
||||
return <Command>{props.children}</Command>
|
||||
}
|
||||
},
|
||||
Divider,
|
||||
a: CustomLink,
|
||||
h1: (props: AnchorLinkProps) => {
|
||||
return (
|
||||
<>
|
||||
<Divider />
|
||||
<AnchorLink {...props} className="text-3xl cursor-pointer md:text-4xl" />
|
||||
</>
|
||||
)
|
||||
},
|
||||
h2: (props: AnchorLinkProps) => {
|
||||
return (
|
||||
<div className="mt-10">
|
||||
<AnchorLink {...props} className="cursor-pointer text-lg sm:text-xl md:text-2.5xl" />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
h3: (props: AnchorLinkProps) => {
|
||||
return (
|
||||
<div className="mt-8">
|
||||
<AnchorLink {...props} className="text-lg cursor-pointer" />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
h4: (props: AnchorLinkProps) => {
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<AnchorLink {...props} className="font-bold cursor-pointer text-base-" />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
p: (props: TextProps) => {
|
||||
return (
|
||||
<Text
|
||||
variant="body"
|
||||
size="small"
|
||||
color="dark"
|
||||
className="my-2 antialiased leading-6"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
},
|
||||
th: ({
|
||||
className,
|
||||
...props
|
||||
}: DetailedHTMLProps<HTMLProps<HTMLTableCellElement>, HTMLTableCellElement>) => {
|
||||
return <th className={clsx('font-display', className)} {...props} />
|
||||
},
|
||||
td: ({
|
||||
className,
|
||||
...props
|
||||
}: DetailedHTMLProps<HTMLProps<HTMLTableCellElement>, HTMLTableCellElement>) => {
|
||||
return <td className={clsx('font-display', className)} {...props} />
|
||||
},
|
||||
Mermaid: ({ chart }) => {
|
||||
const [html, setHtml] = React.useState('')
|
||||
React.useEffect(() => {
|
||||
const start = async () => {
|
||||
const mermaid = (await import('mermaid')).default
|
||||
mermaid.mermaidAPI.render(uuid(), chart, (svgCode) => setHtml(svgCode))
|
||||
}
|
||||
start()
|
||||
}, [chart])
|
||||
return chart ? <div dangerouslySetInnerHTML={{ __html: html }} /> : null
|
||||
}
|
||||
}
|
||||
|
||||
let currentId = 0
|
||||
const uuid = () => `mermaid-${(currentId++).toString()}`
|
||||
|
||||
export default components
|
||||
@@ -1,3 +0,0 @@
|
||||
export function Main({ children }) {
|
||||
return <div className="flex flex-col w-full lg:min-w-body lg:w-body">{children}</div>
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
import Text from '@/components/ui/Text'
|
||||
import clsx from 'clsx'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { ParsedUrlQuery } from 'querystring'
|
||||
import React, { MouseEvent } from 'react'
|
||||
import { fixTitle } from '../utils/fixTitle'
|
||||
import { NavItem } from './NavDataContext'
|
||||
|
||||
export type NavProps = {
|
||||
/**
|
||||
* Class name to apply to the wrapper element.
|
||||
*/
|
||||
className?: string
|
||||
/**
|
||||
* Category slug.
|
||||
*/
|
||||
category: string
|
||||
/**
|
||||
* The category title.
|
||||
*/
|
||||
categoryTitle: string
|
||||
/**
|
||||
* Convoluted navigation.
|
||||
*/
|
||||
convolutedNav: NavItem[]
|
||||
/**
|
||||
* Function to be called when a menu item is selected.
|
||||
*/
|
||||
onMenuSelected?: (event?: MouseEvent<HTMLAnchorElement, MouseEvent>) => void
|
||||
}
|
||||
|
||||
export function Nav({ className, onMenuSelected, ...props }: NavProps) {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div className={clsx('lg:min-w-nav lg:w-nav flex-col space-y-5 antialiased mt-1', className)}>
|
||||
<div>
|
||||
<ul>
|
||||
<li
|
||||
className={clsx(
|
||||
'transition duration-300 ease-in-out rounded-md hover:text-black hover:bg-veryLightGray',
|
||||
router.query.category === props.category &&
|
||||
!router.query.subcategory &&
|
||||
!router.query.post &&
|
||||
'bg-veryLightGray'
|
||||
)}
|
||||
>
|
||||
<Link href={`/${props.category}`} passHref>
|
||||
<Text
|
||||
variant="a"
|
||||
color="greyscaleDark"
|
||||
size="normal"
|
||||
className={clsx(
|
||||
'block py-1.5 px-3 transition-colors duration-300 ease-in-out text-greyscaleDark hover:text-dark subpixel-antialiased',
|
||||
'font-medium'
|
||||
)}
|
||||
onClick={onMenuSelected}
|
||||
>
|
||||
{props.categoryTitle}
|
||||
</Text>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{props.convolutedNav.map((elem) => {
|
||||
const parentCategory = props.category.replace(' ', '-')
|
||||
|
||||
return (
|
||||
<div key={elem.category}>
|
||||
<Link href={`/${parentCategory}/${elem.category}/`} passHref>
|
||||
<Text
|
||||
variant="a"
|
||||
color="greyscaleGrey"
|
||||
size="normal"
|
||||
className="block px-3 py-px font-medium capitalize"
|
||||
onClick={onMenuSelected}
|
||||
>
|
||||
{/* Split */}
|
||||
{fixTitle(elem)}
|
||||
</Text>
|
||||
</Link>
|
||||
|
||||
<ul className="mt-1 space-y-1 ">
|
||||
{elem.posts.map((post) => {
|
||||
const pathToLink =
|
||||
post.fileName != 'index'
|
||||
? `/${parentCategory}/${elem.category}/${post.fileName}`
|
||||
: `/${parentCategory}/${elem.category}`
|
||||
|
||||
const shouldHighlight =
|
||||
router.query.subcategory === elem.category && router.query.post === post.fileName
|
||||
|
||||
const shouldHighlightSubcategories =
|
||||
!router.query.post &&
|
||||
post.fileName === 'index' &&
|
||||
elem.category === router.query.subcategory
|
||||
|
||||
return (
|
||||
<li
|
||||
className={clsx(
|
||||
'transition duration-300 ease-in-out rounded-md hover:text-black hover:bg-veryLightGray',
|
||||
(shouldHighlight || shouldHighlightSubcategories) && 'bg-veryLightGray'
|
||||
)}
|
||||
key={pathToLink}
|
||||
>
|
||||
<Link href={pathToLink} passHref>
|
||||
<Text
|
||||
variant="a"
|
||||
color="greyscaleDark"
|
||||
size="normal"
|
||||
className={clsx(
|
||||
'py-1.5 px-3 block transition-colors duration-300 ease-in-out text-greyscaleDark hover:text-dark subpixel-antialiased block',
|
||||
(shouldHighlight || shouldHighlightSubcategories) && 'font-medium'
|
||||
)}
|
||||
onClick={onMenuSelected}
|
||||
>
|
||||
{post.title}
|
||||
</Text>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
import { ParsedUrlQuery } from 'querystring'
|
||||
import { createContext, PropsWithChildren, useContext } from 'react'
|
||||
|
||||
export type Post = {
|
||||
/**
|
||||
* Title of the post.
|
||||
*/
|
||||
title: string
|
||||
/**
|
||||
* File name where the post is located.
|
||||
*/
|
||||
fileName: string
|
||||
/**
|
||||
* Order of posts.
|
||||
*/
|
||||
order: string[]
|
||||
}
|
||||
|
||||
export type NavItem = {
|
||||
/**
|
||||
* Slug of the category.
|
||||
*/
|
||||
category: string
|
||||
/**
|
||||
* List of posts in the category.
|
||||
*/
|
||||
posts: Post[]
|
||||
}
|
||||
|
||||
export type NavDataContextProps = {
|
||||
/**
|
||||
* Category slug.
|
||||
*/
|
||||
category: string
|
||||
/**
|
||||
* The category title.
|
||||
*/
|
||||
categoryTitle: string
|
||||
/**
|
||||
* Convoluted navigation.
|
||||
*/
|
||||
convolutedNav: NavItem[]
|
||||
/**
|
||||
* Available menu items for all categories.
|
||||
*/
|
||||
availableCategoryMenus: {
|
||||
/**
|
||||
* Slug of the category.
|
||||
*/
|
||||
slug: string
|
||||
/**
|
||||
* Menu items of the category.
|
||||
*/
|
||||
items: NavItem[]
|
||||
}[]
|
||||
}
|
||||
|
||||
export const NavDataContext = createContext<NavDataContextProps>(null)
|
||||
|
||||
export function NavDataProvider({ children, ...props }: PropsWithChildren<NavDataContextProps>) {
|
||||
return <NavDataContext.Provider value={props}>{children}</NavDataContext.Provider>
|
||||
}
|
||||
|
||||
export function useNavData() {
|
||||
const context = useContext(NavDataContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error(`"useNavData" must be used within a "NavDataProvider"`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all of the navigation items for the specified category.
|
||||
*
|
||||
* @param slug Slug of the category.
|
||||
* @returns All of the navigation items for the specified category.
|
||||
*/
|
||||
function getConvolutedNavByCategory(slug: string) {
|
||||
return (
|
||||
context.availableCategoryMenus.find(({ slug: category }) => category === slug)?.items ||
|
||||
context.convolutedNav
|
||||
)
|
||||
}
|
||||
|
||||
return { getConvolutedNavByCategory, ...context }
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
import AnchorLink from './AnchorLink'
|
||||
import CustomLink from './CustomLink'
|
||||
import createKebabCase from '../utils/createKebabCase'
|
||||
import Text from '@/components/ui/Text'
|
||||
|
||||
export default function Nav({ headings }: { headings: any }) {
|
||||
return (
|
||||
<div className="flex flex-col space-y-5 mt-9">
|
||||
{headings.map((heading, index) => {
|
||||
return (
|
||||
<NavLink
|
||||
category={heading.category}
|
||||
post={heading.post}
|
||||
headings={heading.content}
|
||||
key={heading.category + index}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function NavLink({ category, headings, post }) {
|
||||
const href = `/${category}/${post.toLowerCase()}`
|
||||
return (
|
||||
<div className="mt-10 font-display" key={category}>
|
||||
<CustomLink href={href} activeClassName="active" key={category}>
|
||||
<Text variant="body" size="small" className="capitalize cursor-pointer text-grayscale">
|
||||
{post.split('-').join(' ')}
|
||||
</Text>
|
||||
</CustomLink>
|
||||
<ul className="space-y-1">
|
||||
{headings.map((heading: { value: string }) => {
|
||||
return (
|
||||
<li className="py-1 capitalize rounded-sm" key={heading.value}>
|
||||
<AnchorLink
|
||||
id={`/${category}/${post.toLowerCase()}#${createKebabCase(heading.value)}`}
|
||||
>{`/${heading.value}`}</AnchorLink>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
import axios from 'axios'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useState } from 'react'
|
||||
|
||||
import Input from './ui/Input/Input'
|
||||
import Loading from './ui/Loading'
|
||||
|
||||
function NewsletterForm(props) {
|
||||
return (
|
||||
<div className="flex flex-row w-64 mt-5">
|
||||
<form
|
||||
className="grid grid-flow-row sm:grid-flow-col gap-4"
|
||||
onSubmit={(e) => props.subscribe(e)}
|
||||
>
|
||||
<Input
|
||||
color="dark"
|
||||
placeholder="Email address"
|
||||
value={props.email}
|
||||
onChange={props.setEmail}
|
||||
type="email"
|
||||
/>
|
||||
<button
|
||||
className="btn-subscribe font-display text-greyscaleDark font-medium cursor-pointer"
|
||||
disabled={!props.email}
|
||||
>
|
||||
{!props.loading ? 'Subscribe' : <Loading />}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function NewsletterError({ errorMessage, retry }) {
|
||||
const formattedError = errorMessage.includes('already a list member')
|
||||
? errorMessage.split('.').slice(0, 2).join('.')
|
||||
: errorMessage
|
||||
return (
|
||||
<div className="grid grid-flow-row md:grid-flow-col gap-4 mt-5">
|
||||
<p className="text-white font-normal text-sm mt-2.5">{formattedError}.</p>
|
||||
<button
|
||||
className="btn-subscribe font-display text-greyscaleDark font-medium cursor-pointer"
|
||||
onClick={() => {
|
||||
retry()
|
||||
}}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function Newsletter() {
|
||||
const [email, setEmail] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
const [success, setSuccess] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const handleSubmit = async function (e) {
|
||||
e.preventDefault()
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
const res = await axios.post('/api/add-email-to-newsletter', {
|
||||
email
|
||||
})
|
||||
if (!res.data.success) {
|
||||
setError(res.data.message)
|
||||
return
|
||||
}
|
||||
setSuccess(res.data.message)
|
||||
} catch (error) {
|
||||
setError(
|
||||
error.message ||
|
||||
"We've encountered an error while subscribing you. Try again in a few seconds"
|
||||
)
|
||||
} finally {
|
||||
setEmail('')
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (success) {
|
||||
let id = setInterval(() => {
|
||||
setSuccess('')
|
||||
}, 5000)
|
||||
return () => clearInterval(id)
|
||||
}
|
||||
}, [success])
|
||||
|
||||
return (
|
||||
<div className="font-display flex flex-col mt-16">
|
||||
<div className="md:px-0 w-full mx-auto">
|
||||
<h1 className="font-medium text-gray-700 uppercase">newsletter</h1>
|
||||
<p className="text-white font-normal text-sm+ mt-2.5">
|
||||
Platform updates and news on web and mobile development.
|
||||
</p>
|
||||
{error ? (
|
||||
<NewsletterError errorMessage={error} retry={() => setError('')} />
|
||||
) : !success ? (
|
||||
<NewsletterForm
|
||||
email={email}
|
||||
setEmail={setEmail}
|
||||
subscribe={handleSubmit}
|
||||
loading={loading}
|
||||
/>
|
||||
) : (
|
||||
<NewsletterSuccess success={success} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function NewsletterSuccess({ success }) {
|
||||
return (
|
||||
<div className="flex flex-row mt-5">
|
||||
<p className="text-white font-normal text-sm mt-2.5">{success}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import Text from '@/components/ui/Text'
|
||||
import React from 'react'
|
||||
|
||||
import Github from '@/components/icons/Github'
|
||||
import { DOCS_GITHUB_ENDPOINT } from '@/utils/constants'
|
||||
|
||||
export function PostMetadata(props) {
|
||||
return (
|
||||
<div className="mt-3 flex flex-row border-t pt-6 place-content-between px-3">
|
||||
<div className="flex flex-row">
|
||||
<Github className="text-blue" />
|
||||
<a
|
||||
className="text-blue text-xs ml-2 self-center"
|
||||
href={`${DOCS_GITHUB_ENDPOINT}${props.category}/${props.subcategory}/${props.post}.mdx`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Edit this page on GitHub
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
{props.frontmatter.updatedAt ? (
|
||||
<div className="flex">
|
||||
<Text size="tiny">Last updated on {props.frontmatter.updatedAt}</Text>
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import ArrowLeft from '@/components/icons/ArrowLeft'
|
||||
import ArrowRight from '@/components/icons/ArrowRight'
|
||||
import Text from '@/components/ui/Text/Text'
|
||||
import { orderTwo } from '@/lib/order'
|
||||
import { useRouter } from 'next/dist/client/router'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
||||
export function SubNavigation({ category, subcategory, post, convolutedNav }) {
|
||||
const router = useRouter()
|
||||
const indexOfSubcategory = Object.keys(orderTwo[category]).indexOf(subcategory)
|
||||
const indexOfPreviousPost = orderTwo[category][subcategory].indexOf(post) - 1
|
||||
let indexOfCurrentPost = orderTwo[category][subcategory].indexOf(post)
|
||||
const previousPost = orderTwo[category][subcategory][indexOfCurrentPost - 1]
|
||||
let indexOfNextPost = orderTwo[category][subcategory].indexOf(post) + 1
|
||||
|
||||
if (!router.query.post) indexOfCurrentPost++ && indexOfNextPost++
|
||||
|
||||
const nextPost = orderTwo[category][subcategory][indexOfCurrentPost + 1]
|
||||
|
||||
const pathLink = `/${category}/${subcategory}/${previousPost === 'index' ? '' : previousPost}`
|
||||
|
||||
return (
|
||||
<div className="flex flex-row mt-10 place-content-between px-2 antialiased">
|
||||
<Link href={pathLink} passHref>
|
||||
<Text variant="a" color="blue" className="font-medium cursor-pointer" size="small">
|
||||
{indexOfCurrentPost === 0 || !router.query.post ? (
|
||||
<></>
|
||||
) : (
|
||||
<div className="flex flex-row self-center hover:-translate-x-1 transform transition-transform duration-500">
|
||||
<ArrowLeft className="self-center mr-1" />
|
||||
{convolutedNav[indexOfSubcategory].posts[indexOfPreviousPost].title}
|
||||
</div>
|
||||
)}
|
||||
</Text>
|
||||
</Link>
|
||||
|
||||
<Link href={`/${category}/${subcategory}/${nextPost}`} passHref>
|
||||
<Text variant="a" size="small" color="blue" className="font-medium cursor-pointer">
|
||||
{nextPost ? (
|
||||
<div className="flex flex-row self-center hover:translate-x-1 transform transition-transform duration-500">
|
||||
{convolutedNav[indexOfSubcategory].posts[indexOfNextPost].title}
|
||||
<ArrowRight className="self-center ml-1" />
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Text>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
export function Tooltip({ text, children, position = '-mx-20', color = '' }) {
|
||||
return (
|
||||
<div className="relative has-tooltip">
|
||||
{children}
|
||||
|
||||
<span
|
||||
className={`z-50 px-1.5 py-0.5 text-sm bg-verydark -my-12 -mx-9 text-white rounded-sm shadow-2xl border tooltip font-medium`}
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
<svg
|
||||
className="absolute z-50 w-3 h-2 text-verydark transform tooltip -top-2 right-0.5"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 255 255"
|
||||
xmlSpace="preserve"
|
||||
>
|
||||
<polygon
|
||||
className="border border-white fill-current text-lightbrand"
|
||||
points="0,0 127.5,127.5 255,0"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import CaretRight from '@/components/icons/CaretRight'
|
||||
import Text from '@/components/ui/Text/Text'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
||||
export function TopNavigation(props) {
|
||||
const category = props.category.split('-').join(' ')
|
||||
|
||||
function uppercaseEdgeCases(subcategory) {
|
||||
switch (subcategory) {
|
||||
case 'sdk':
|
||||
return 'SDK'
|
||||
case 'cli':
|
||||
return 'CLI'
|
||||
default:
|
||||
return subcategory
|
||||
}
|
||||
}
|
||||
|
||||
const subcategory = props.subcategory.split('-').join(' ')
|
||||
|
||||
return (
|
||||
<div className="flex flex-row w-full">
|
||||
<Link href={`/${props.category}`} passHref>
|
||||
<Text
|
||||
variant="a"
|
||||
color="grey"
|
||||
className="self-center font-medium capitalize transition-colors duration-200 cursor-pointer hover:text-greyscaleDark"
|
||||
size="normal"
|
||||
>
|
||||
{category}
|
||||
</Text>
|
||||
</Link>
|
||||
<CaretRight className="self-center text-greyscaleGrey mx-1" />
|
||||
<Link href={`/${props.category}/${props.subcategory}`} passHref>
|
||||
<Text
|
||||
color="grey"
|
||||
className="self-center font-medium capitalize transition-colors duration-200 cursor-pointer hover:text-greyscaleDark"
|
||||
size="normal"
|
||||
>
|
||||
{uppercaseEdgeCases(subcategory)}
|
||||
</Text>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import * as React from 'react'
|
||||
|
||||
function ArrowLeft(props: JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg width={16} height={16} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M13.5 8h-11M7 3.5L2.5 8 7 12.5"
|
||||
stroke="#0052CD"
|
||||
strokeWidth={1.5}
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default ArrowLeft
|
||||
@@ -1,16 +0,0 @@
|
||||
import * as React from 'react'
|
||||
|
||||
function ArrowRight(props: JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg width={16} height={16} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M2.5 8h11M9 3.5L13.5 8 9 12.5"
|
||||
stroke="#0052CD"
|
||||
strokeWidth={1.5}
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default ArrowRight
|
||||
@@ -1,11 +0,0 @@
|
||||
import * as React from 'react'
|
||||
|
||||
function CaretRight(props: JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg width={16} height={16} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path d="M6 3l5 5-5 5" stroke="currentColor" strokeWidth={1.5} strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default CaretRight
|
||||
@@ -1,11 +0,0 @@
|
||||
import * as React from 'react'
|
||||
|
||||
function Check(props: any) {
|
||||
return (
|
||||
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path d="M13.5 4.5l-7 7L3 8" stroke="currentColor" strokeWidth={2} strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default Check
|
||||
@@ -1,11 +0,0 @@
|
||||
import * as React from 'react'
|
||||
|
||||
function Check2(props: any) {
|
||||
return (
|
||||
<svg viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path d="M27 9L13 23l-7-7" stroke="#0D3777" strokeWidth={2} strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default Check2
|
||||
@@ -1,20 +0,0 @@
|
||||
const Copy = ({ ...props }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default Copy
|
||||
@@ -1,22 +0,0 @@
|
||||
const Github = ({ ...props }) => {
|
||||
return (
|
||||
<div className="cursor-pointer">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22" />
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Github
|
||||
@@ -1,23 +0,0 @@
|
||||
import * as React from 'react'
|
||||
|
||||
function GithubIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={14}
|
||||
height={14}
|
||||
viewBox="0 0 14 14"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7 0a7 7 0 00-2.213 13.642c.35.065.478-.152.478-.337 0-.166-.006-.607-.01-1.19-1.947.422-2.357-.94-2.357-.94-.319-.808-.778-1.023-.778-1.023-.635-.434.048-.426.048-.426.703.05 1.073.722 1.073.722.624 1.07 1.638.76 2.037.581.063-.452.244-.76.444-.935-1.554-.177-3.188-.778-3.188-3.46 0-.764.273-1.39.72-1.878-.072-.177-.312-.89.07-1.853 0 0 .586-.188 1.924.718A6.705 6.705 0 017 3.385c.595.003 1.194.08 1.753.236 1.336-.906 1.923-.718 1.923-.718.382.964.142 1.676.07 1.853a2.7 2.7 0 01.72 1.878c0 2.69-1.638 3.281-3.197 3.454.251.216.475.644.475 1.297 0 .935-.009 1.69-.009 1.92 0 .187.127.405.482.337A7 7 0 007 0z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default GithubIcon
|
||||
@@ -1,20 +0,0 @@
|
||||
const Help = ({ ...props }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default Help
|
||||
@@ -1,36 +0,0 @@
|
||||
import * as React from 'react'
|
||||
|
||||
function Logo(props: JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg viewBox="0 0 95 32" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<g clipPath="url(#prefix__clip0)">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M50.657 15.218h4.792v-3.801h2.535v9.752h-2.535v-3.802h-4.792v3.802H48.21v-9.752h2.446v3.801zm16.996-3.801H63.74c-1.084 0-1.744.204-2.243.703-.497.496-.688 1.126-.688 2.238v3.874c0 1.112.19 1.74.688 2.238.497.498 1.157.703 2.243.703h3.913c1.084 0 1.744-.205 2.243-.703.5-.496.688-1.126.688-2.238v-3.874c0-1.112-.19-1.74-.688-2.239-.5-.498-1.159-.702-2.243-.702zm.483 6.872c0 .57-.19.73-.879.73h-3.121c-.688 0-.88-.16-.88-.73v-3.992c0-.571.192-.731.88-.731h3.121c.705 0 .88.145.88.73v3.993zm8.512-2.97h3.501c1.084 0 1.758.205 2.243.703.439.44.688 1.155.688 1.93v.586c0 .776-.25 1.476-.688 1.93-.485.498-1.145.703-2.243.703h-6.681v-2.15h6.271c.688 0 .88-.16.88-.73v-.292c0-.571-.192-.731-.88-.731h-3.5c-1.087 0-1.76-.207-2.243-.703-.439-.44-.689-1.154-.689-1.93v-.585c0-.776.248-1.477.689-1.93.485-.499 1.142-.703 2.242-.703h6.333v2.149h-5.923c-.688 0-.879.16-.879.73v.292c0 .572.191.732.88.732zm7.983-1.753v-2.15h9.963v2.15h-3.75v7.603h-2.448v-7.603H84.63z"
|
||||
fill="#21324B"
|
||||
/>
|
||||
<path
|
||||
d="M42.355 11.34h-3.913c-1.084 0-1.744.204-2.243.703-.497.496-.688 1.126-.688 2.236v6.888h2.447V14.22c0-.57.191-.73.88-.73h3.121c.704 0 .879.146.879.73v6.948h2.448V14.28c0-1.112-.191-1.74-.689-2.236-.497-.5-1.156-.705-2.242-.705z"
|
||||
fill="#0052CD"
|
||||
/>
|
||||
<g clipPath="url(#prefix__clip1)">
|
||||
<path
|
||||
d="M27.208 6.858L16.055.43a3.268 3.268 0 00-3.246 0 3.245 3.245 0 00-1.621 2.803v.839l-.727-.42a3.268 3.268 0 00-3.246 0A3.246 3.246 0 005.594 6.46v.838l-.727-.419a3.268 3.268 0 00-3.245 0A3.246 3.246 0 000 9.683v20.136a1.526 1.526 0 002.47 1.195L8 26.66l8.53 4.914a1.548 1.548 0 001.526 0c.47-.272.763-.776.763-1.319V18.14a5.595 5.595 0 00-2.797-4.835l-2.797-1.612V3.236a1.21 1.21 0 011.815-1.045l11.153 6.425a3.562 3.562 0 011.78 3.076v15.089c0 .43-.232.83-.605 1.045l-2.955 1.703V14.914a5.595 5.595 0 00-2.797-4.834L14.75 6.125v2.343l5.849 3.37a3.559 3.559 0 011.78 3.076v15.492c0 .54.292 1.047.763 1.319a1.549 1.549 0 001.526 0l3.719-2.143c1-.577 1.622-1.65 1.622-2.805V11.688a5.609 5.609 0 00-2.801-4.83zM15 15.062a3.559 3.559 0 011.78 3.077v11.24L9.718 25.31l2.267-1.782a3.214 3.214 0 001.236-2.542V14.04l1.78 1.023zm-3.813-2.197v8.117c0 .373-.169.72-.461.948l-8.693 6.84V9.68a1.209 1.209 0 011.814-1.045L5.594 9.64v14.39l2.033-1.6V6.458a1.209 1.209 0 011.815-1.045l1.745 1.004v4.102L9.155 9.347v2.345l2.034 1.173z"
|
||||
fill="#0052CD"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="prefix__clip0">
|
||||
<path fill="#fff" d="M0 0h94.582v32H0z" />
|
||||
</clipPath>
|
||||
<clipPath id="prefix__clip1">
|
||||
<path fill="#fff" d="M0 0h30.009v31.927H0z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default Logo
|
||||
@@ -1,15 +0,0 @@
|
||||
const Permalink = ({ ...props }) => {
|
||||
return (
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M5.878 10.12l4.243-4.242M9.06 11.182L7.293 12.95A3 3 0 013.05 8.707l1.768-1.768M11.182 9.06l1.768-1.768A3 3 0 008.707 3.05L6.939 4.818"
|
||||
stroke="#21324B"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default Permalink
|
||||
@@ -1,21 +0,0 @@
|
||||
import * as React from 'react'
|
||||
|
||||
function Vector(props: JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={260}
|
||||
height={117}
|
||||
viewBox="0 0 260 117"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M35.5 1h1V0h-1v1zM254 87l-5.773 10h11.546L254 87zM.5 2h2.188V0H.5v2zm6.563 0h4.375V0H7.061v2zm8.75 0h4.374V0h-4.375v2zm8.75 0h4.375V0h-4.375v2zm8.75 0H35.5V0h-2.188v2zM34.5 1v1.982h2V1h-2zm0 5.946v3.965h2V6.946h-2zm0 7.929v3.964h2v-3.964h-2zm0 7.929v3.964h2v-3.964h-2zm0 7.928v3.964h2v-3.964h-2zm0 7.929v3.964h2v-3.964h-2zm0 7.928v3.965h2v-3.965h-2zm0 7.929v3.964h2v-3.964h-2zm0 7.928v3.965h2v-3.965h-2zm0 7.929v3.964h2v-3.964h-2zm0 7.929v3.964h2v-3.964h-2zm0 7.928v3.964h2v-3.964h-2zm0 7.929v3.964h2v-3.964h-2zm0 7.928v3.965h2v-3.965h-2zm0 7.929V112h2v-1.982h-2zm0 1.982c0 .676.135 1.323.38 1.914l1.847-.766A2.985 2.985 0 0136.5 112h-2zm3.086 4.62c.59.245 1.238.38 1.914.38v-2a2.99 2.99 0 01-1.148-.227l-.766 1.847zm1.914.38h2.024v-2H39.5v2zm6.072 0h4.048v-2h-4.048v2zm8.096 0h4.048v-2h-4.048v2zm8.096 0h4.048v-2h-4.048v2zm8.097 0h4.048v-2H69.86v2zm8.096 0h4.048v-2h-4.048v2zm8.096 0H90.1v-2h-4.048v2zm8.096 0h4.048v-2H94.15v2zm8.096 0h4.048v-2h-4.048v2zm8.096 0h4.048v-2h-4.048v2zm8.097 0h4.048v-2h-4.048v2zm8.096 0h4.048v-2h-4.048v2zm8.096 0h4.048v-2h-4.048v2zm8.096 0h4.048v-2h-4.048v2zm8.096 0h4.048v-2h-4.048v2zm8.096 0h4.048v-2h-4.048v2zm8.096 0h4.049v-2h-4.049v2zm8.097 0h4.048v-2h-4.048v2zm8.096 0h4.048v-2h-4.048v2zm8.096 0h4.048v-2h-4.048v2zm8.096 0h4.048v-2h-4.048v2zm8.096 0h4.048v-2h-4.048v2zm8.096 0h4.049v-2h-4.049v2zm8.097 0h4.048v-2h-4.048v2zm8.096 0h4.048v-2h-4.048v2zm8.096 0h4.048v-2h-4.048v2zm8.096 0H250v-2h-2.024v2zm2.024 0c.676 0 1.323-.135 1.914-.38l-.766-1.847A2.987 2.987 0 01250 115v2zm4.62-3.086c.245-.591.38-1.238.38-1.914h-2c0 .409-.081.796-.227 1.148l1.847.766zM255 112v-2.083h-2V112h2zm0-6.25v-4.167h-2v4.167h2zm0-8.333V93.25h-2v4.167h2z"
|
||||
fill="#C2CAD6"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default Vector
|
||||
@@ -1,97 +0,0 @@
|
||||
.root {
|
||||
@apply font-display flex px-2 py-1.6;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: 22px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.primary {
|
||||
@apply bg-blue font-display flex font-medium text-white;
|
||||
font-size: 15px;
|
||||
border-radius: 4px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.danger {
|
||||
@apply py-2.5 px-2.5 text-red font-display font-medium;
|
||||
height: 36px;
|
||||
border-radius: 4px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border: 1px solid #c2cad6;
|
||||
}
|
||||
|
||||
.blue {
|
||||
@apply text-blue;
|
||||
}
|
||||
|
||||
.red {
|
||||
@apply text-red;
|
||||
}
|
||||
|
||||
.root:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
.root[data-active] {
|
||||
@apply bg-accent-6;
|
||||
}
|
||||
|
||||
.loading {
|
||||
@apply bg-accent-1 text-accent-3 border-accent-2 cursor-not-allowed;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
@apply font-display text-greyscaleDark bg-white;
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
border: 1px solid #c2cad6;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.menu {
|
||||
@apply font-display text-greyscaleDark cursor-pointer;
|
||||
font-family: Inter;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
}
|
||||
.dark {
|
||||
@apply font-medium text-white;
|
||||
background: #21324b;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.dark .disabled {
|
||||
@apply font-medium text-white;
|
||||
background: #21324b;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.secondary .disabled {
|
||||
@apply font-medium;
|
||||
color: rgba(9, 34, 72, 0.4);
|
||||
}
|
||||
.disabled,
|
||||
.disabled:hover {
|
||||
@apply text-gray-400 cursor-not-allowed;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.small {
|
||||
width: 51px;
|
||||
height: 24px;
|
||||
font-family: Inter;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
.transparent {
|
||||
@apply px-0 py-1 bg-transparent border-0;
|
||||
}
|
||||
.border {
|
||||
@apply px-2 py-1.5;
|
||||
border: 1px solid #c2cad6;
|
||||
border-radius: 4px;
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import cn from 'classnames'
|
||||
import React, { ButtonHTMLAttributes, forwardRef, JSXElementConstructor, useRef } from 'react'
|
||||
import mergeRefs from 'react-merge-refs'
|
||||
|
||||
import s from './Button.module.css'
|
||||
|
||||
// import Loading from "../components/ui/Loading";
|
||||
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
href?: string
|
||||
className?: string
|
||||
variant?: 'primary' | 'dark' | 'secondary' | 'menu' | 'danger'
|
||||
color?: 'blue' | 'red'
|
||||
active?: boolean
|
||||
type?: 'submit' | 'reset' | 'button'
|
||||
Component?: string | JSXElementConstructor<any>
|
||||
width?: string | number
|
||||
loading?: boolean
|
||||
disabled?: boolean
|
||||
small?: boolean
|
||||
transparent?: boolean
|
||||
target?: string
|
||||
rel?: string
|
||||
onClick?: any
|
||||
border?: boolean
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
export const Button: React.FC<ButtonProps> = forwardRef((props, buttonRef) => {
|
||||
const {
|
||||
className,
|
||||
variant,
|
||||
children,
|
||||
active,
|
||||
width,
|
||||
small,
|
||||
href,
|
||||
color,
|
||||
border,
|
||||
loading = false,
|
||||
disabled = false,
|
||||
transparent = false,
|
||||
style = {},
|
||||
type = 'button',
|
||||
Component = 'button',
|
||||
...rest
|
||||
} = props
|
||||
const ref = useRef<typeof Component>(null)
|
||||
|
||||
const rootClassName = cn(
|
||||
s.root,
|
||||
{
|
||||
[s.primary]: variant === 'primary',
|
||||
[s.secondary]: variant === 'secondary',
|
||||
[s.menu]: variant === 'menu',
|
||||
[s.dark]: variant === 'dark',
|
||||
[s.danger]: variant === 'danger',
|
||||
[s.loading]: loading,
|
||||
[s.disabled]: disabled,
|
||||
[s.small]: small,
|
||||
[s.transparent]: transparent,
|
||||
[s.blue]: color === 'blue',
|
||||
[s.red]: color === 'red',
|
||||
[s.border]: border
|
||||
},
|
||||
className
|
||||
)
|
||||
|
||||
return (
|
||||
<Component
|
||||
aria-pressed={active}
|
||||
data-variant={variant}
|
||||
ref={mergeRefs([ref, buttonRef])}
|
||||
className={rootClassName}
|
||||
disabled={disabled}
|
||||
type={type}
|
||||
href={href}
|
||||
style={{
|
||||
width,
|
||||
...style
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
)
|
||||
})
|
||||
|
||||
export default Button
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './Button'
|
||||
@@ -1,31 +0,0 @@
|
||||
.root {
|
||||
@apply border-input text-dark focus:ring-dark focus:border-dark flex px-2 py-2 text-xs rounded-md shadow-sm;
|
||||
border: 1px solid #c2cad6;
|
||||
}
|
||||
|
||||
.root:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
.dark {
|
||||
flex: none;
|
||||
width: 247px;
|
||||
height: 36px;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
color: white;
|
||||
|
||||
/* Translucent white/White light (20) */
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
|
||||
/* Inside Auto Layout */
|
||||
order: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.disabled,
|
||||
.disabled:hover {
|
||||
@apply text-accent-3 cursor-not-allowed;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import cn from 'classnames'
|
||||
import s from './Input.module.css'
|
||||
import React, { InputHTMLAttributes } from 'react'
|
||||
|
||||
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
className?: string
|
||||
onChange?: (...args: any[]) => any
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
type Color = 'dark' | 'light'
|
||||
|
||||
const Input: React.FC<InputProps> = (props) => {
|
||||
const { className, children, onChange, color, disabled, placeholder = '', ...rest } = props
|
||||
|
||||
const rootClassName = cn(
|
||||
s.root,
|
||||
{ [s.disabled]: disabled, [s.dark]: color === 'dark' },
|
||||
className
|
||||
)
|
||||
|
||||
const handleOnChange = (e: any) => {
|
||||
if (onChange) {
|
||||
onChange(e.target.value)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
className={rootClassName}
|
||||
onChange={handleOnChange}
|
||||
disabled={disabled}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
placeholder={placeholder}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Input
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './Input'
|
||||
@@ -1,19 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function Loading() {
|
||||
return (
|
||||
<svg
|
||||
className="w-5 h-5 text-dark animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
.body {
|
||||
font-family: Inter;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.dark {
|
||||
@apply text-dark;
|
||||
}
|
||||
|
||||
.greyscaleDark {
|
||||
@apply text-greyscaleDark;
|
||||
}
|
||||
|
||||
.white {
|
||||
@apply text-white;
|
||||
}
|
||||
|
||||
.greyscaleGrey {
|
||||
@apply text-greyscaleGrey;
|
||||
}
|
||||
|
||||
.grey {
|
||||
@apply text-grayscale;
|
||||
}
|
||||
|
||||
.red {
|
||||
@apply text-red;
|
||||
}
|
||||
|
||||
.blue {
|
||||
@apply text-blue;
|
||||
}
|
||||
|
||||
.heading {
|
||||
@apply font-display;
|
||||
}
|
||||
|
||||
.pageHeading {
|
||||
@apply pt-1 pb-4 text-2xl font-bold leading-7 tracking-wide;
|
||||
}
|
||||
|
||||
.a {
|
||||
font-family: Inter;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.tiny {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.normal {
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.large {
|
||||
font-size: 18px;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.big {
|
||||
font-size: 26px;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.heading {
|
||||
font-size: 36px;
|
||||
line-height: 48px;
|
||||
}
|
||||
|
||||
.subHeading {
|
||||
font-family: Inter;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sectionHeading {
|
||||
@apply text-lg font-medium font-display;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.item {
|
||||
font-family: Inter;
|
||||
font-style: normal;
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
import cn from 'classnames'
|
||||
import React, {
|
||||
CSSProperties,
|
||||
ForwardedRef,
|
||||
forwardRef,
|
||||
FunctionComponent,
|
||||
JSXElementConstructor
|
||||
} from 'react'
|
||||
import mergeRefs from 'react-merge-refs'
|
||||
import s from './Text.module.css'
|
||||
|
||||
export interface TextProps {
|
||||
variant?: Variant
|
||||
className?: string
|
||||
style?: CSSProperties
|
||||
children?: React.ReactNode | any
|
||||
color?: Color
|
||||
html?: string
|
||||
size?: Size
|
||||
target?: any
|
||||
rel?: any
|
||||
href?: string
|
||||
onClick?: () => any
|
||||
name?: any
|
||||
}
|
||||
|
||||
type Variant = 'heading' | 'body' | 'pageHeading' | 'sectionHeading' | 'item' | 'subHeading' | 'a'
|
||||
|
||||
type Size = 'tiny' | 'small' | 'normal' | 'large' | 'big' | 'heading'
|
||||
|
||||
type Color = 'dark' | 'grey' | 'blue' | 'greyscaleDark' | 'greyscaleGrey' | 'red' | 'white'
|
||||
|
||||
export const Text: FunctionComponent<TextProps> = forwardRef(function DefaultText(
|
||||
{
|
||||
style,
|
||||
className = '',
|
||||
variant = 'body',
|
||||
color,
|
||||
children,
|
||||
html,
|
||||
onClick,
|
||||
size,
|
||||
rel,
|
||||
href,
|
||||
target,
|
||||
name
|
||||
},
|
||||
ref: ForwardedRef<HTMLElement>
|
||||
) {
|
||||
const componentsMap: {
|
||||
[P in Variant]: React.ComponentType<any> | string
|
||||
} = {
|
||||
body: 'div',
|
||||
heading: 'h1',
|
||||
pageHeading: 'h1',
|
||||
sectionHeading: 'h2',
|
||||
subHeading: 'h3',
|
||||
item: 'p',
|
||||
a: 'a'
|
||||
}
|
||||
|
||||
const Component:
|
||||
| JSXElementConstructor<any>
|
||||
| React.ReactElement<any>
|
||||
| React.ComponentType<any>
|
||||
| string = componentsMap![variant!]
|
||||
|
||||
const htmlContentProps = html
|
||||
? {
|
||||
dangerouslySetInnerHTML: { __html: html }
|
||||
}
|
||||
: {}
|
||||
|
||||
const aProps =
|
||||
variant === 'a'
|
||||
? {
|
||||
rel,
|
||||
href,
|
||||
target
|
||||
}
|
||||
: {}
|
||||
|
||||
return (
|
||||
<Component
|
||||
ref={ref}
|
||||
className={cn(
|
||||
s.root,
|
||||
{
|
||||
[s.body]: variant === 'body',
|
||||
[s.a]: variant === 'a',
|
||||
[s.heading]: variant === 'heading',
|
||||
[s.pageHeading]: variant === 'pageHeading',
|
||||
[s.sectionHeading]: variant === 'sectionHeading',
|
||||
[s.subHeading]: variant === 'subHeading',
|
||||
[s.item]: variant === 'item',
|
||||
[s.dark]: color === 'dark',
|
||||
[s.greyscaleDark]: color === 'greyscaleDark',
|
||||
[s.grey]: color === 'grey',
|
||||
[s.blue]: color === 'blue',
|
||||
[s.tiny]: size === 'tiny',
|
||||
[s.small]: size === 'small',
|
||||
[s.normal]: size === 'normal',
|
||||
[s.large]: size === 'large',
|
||||
[s.big]: size === 'big',
|
||||
[s.heading]: size === 'heading',
|
||||
[s.greyscaleGrey]: color === 'greyscaleGrey',
|
||||
[s.red]: color === 'red',
|
||||
[s.white]: color === 'white'
|
||||
},
|
||||
className
|
||||
)}
|
||||
onClick={onClick}
|
||||
style={style}
|
||||
{...htmlContentProps}
|
||||
{...aProps}
|
||||
name={name}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
)
|
||||
})
|
||||
|
||||
export default Text
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './Text'
|
||||
export { default } from './Text'
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './Button/Button'
|
||||
export * from './Text/Text'
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
title: 'Social login'
|
||||
---
|
||||
|
||||
Nhost Auth supports the following social login providers:
|
||||
|
||||
- GitHub
|
||||
- Google
|
||||
- Facebook
|
||||
- LinkedIn
|
||||
|
||||
---
|
||||
|
||||
## Enabling social login
|
||||
|
||||
To start with social login, select your app in Nhost Console and go to **Users** → **Login settings**.
|
||||
|
||||
Enabling any of the supported login providers requires a developer account for the selected login provider. Follow the on-screen instructions to enable the login method of your choosing.
|
||||
|
||||
---
|
||||
|
||||
## Implementing login experience
|
||||
|
||||
To implement social login in your app, use the [Nhost JavaScript SDK](/reference/sdk) and the `signIn()` method:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'github'
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## OAuth scopes
|
||||
|
||||
Scopes are a mechanism in OAuth to allow or limit an application's access to a user's account.
|
||||
|
||||
By default, Nhost sets the scope to get the name, email and avatar for each user. Editing scope is not currently supported.
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
title: 'The Nhost platform'
|
||||
---
|
||||
|
||||
This section:
|
||||
|
||||
### Database
|
||||
|
||||
- [Schema](/platform/database)
|
||||
- [Permissions](/platform/database/permissions)
|
||||
- [GraphQL](/platform/database/graphql)
|
||||
|
||||
### Authentication
|
||||
|
||||
- [Authentication overview](/platform/authentication)
|
||||
- [User management](/platform/authentication/user-management)
|
||||
- [Sign-in methods](/platform/authentication/sign-in-methods)
|
||||
- [Social login](/platform/authentication/social-login)
|
||||
- [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,30 +0,0 @@
|
||||
---
|
||||
title: 'Reference'
|
||||
---
|
||||
|
||||
In this section:
|
||||
|
||||
### Nhost SDK
|
||||
|
||||
- [Overview](/reference/sdk)
|
||||
- [GraphQL](/reference/sdk/graphql)
|
||||
- [Authentication](/reference/sdk/authentication)
|
||||
- [Storage](/reference/sdk/storage)
|
||||
- [Functions](/reference/sdk/functions)
|
||||
|
||||
### React
|
||||
|
||||
- [Getting started](./reference/react)
|
||||
- [Hooks](./reference/react/hooks)
|
||||
- [Protecting routes](./reference/react/protecting-routes)
|
||||
- [Apollo GraphQL](./reference/react/apollo)
|
||||
|
||||
### Next.js
|
||||
|
||||
- [Introduction](./reference/nextjs)
|
||||
- [Configuration](./reference/nextjs/configuration)
|
||||
- [Protecting routes](./reference/nextjs/protecting-routes)
|
||||
|
||||
### Nhost CLI
|
||||
|
||||
- [CLI overview](/reference/cli)
|
||||
@@ -1,101 +0,0 @@
|
||||
---
|
||||
title: 'Configuration'
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuring Nhost with Next.js follows the same logic as React, except we are initializing with `NhostSSR` instead of `Nhost`.
|
||||
Under the hood, `NhostSSR` uses cookies to store the refresh token, and disables auto-refresh and auto-login when running on the server-side.
|
||||
|
||||
```jsx
|
||||
// {project-root}/pages/_app.tsx
|
||||
import type { AppProps } from 'next/app'
|
||||
|
||||
import { NhostSSR, NhostProvider } from '@nhost/nextjs'
|
||||
|
||||
import Header from '../components/Header'
|
||||
|
||||
const nhost = new NhostSSR({ backendUrl: 'my-app.nhost.run' })
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<NhostProvider nhost={nhost} initial={pageProps.nhostSession}>
|
||||
<div>
|
||||
<Header />
|
||||
<Component {...pageProps} />
|
||||
</div>
|
||||
</NhostProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default MyApp
|
||||
```
|
||||
|
||||
## Client-side rendering
|
||||
|
||||
The logic is the same as in a classic React application:
|
||||
|
||||
```jsx
|
||||
// {project-root}/pages/csr-page.jsx
|
||||
import { NextPageContext } from 'next'
|
||||
import React from 'react'
|
||||
|
||||
import { useAccessToken, useAuthenticated, useUserData } from '@nhost/nextjs'
|
||||
|
||||
const ClientSidePage: React.FC = () => {
|
||||
const isAuthenticated = useAuthenticated()
|
||||
const user = useUserData()
|
||||
const accessToken = useAccessToken()
|
||||
if (!isAuthenticated) return <div>User it not authenticated</div>
|
||||
return (
|
||||
<div>
|
||||
<h1>{user?.displayName} is authenticated</h1>
|
||||
<div>Access token: {accessToken}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ClientSidePage
|
||||
```
|
||||
|
||||
## Server-side rendering
|
||||
|
||||
You need to load the session from the server first from `getServerSideProps`. Once it is done, the `_app` component will make sure to load or update the session through `pageProps`.
|
||||
|
||||
```jsx
|
||||
// {project-root}/pages/ssr-page.jsx
|
||||
import { NextPageContext } from 'next'
|
||||
import React from 'react'
|
||||
|
||||
import {
|
||||
getNhostSession,
|
||||
NhostSession,
|
||||
useAccessToken,
|
||||
useAuthenticated,
|
||||
useUserData
|
||||
} from '@nhost/nextjs'
|
||||
|
||||
export async function getServerSideProps(context: NextPageContext) {
|
||||
const nhostSession = await getNhostSession('my-app.nhost.run', context)
|
||||
return {
|
||||
props: {
|
||||
nhostSession
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ServerSidePage: React.FC<{ initial: NhostSession }> = () => {
|
||||
const authenticated = useAuthenticated()
|
||||
const user = useUserData()
|
||||
const accessToken = useAccessToken()
|
||||
if (!authenticated) return <div>User it not authenticated</div>
|
||||
return (
|
||||
<div>
|
||||
<h1>{user?.displayName} is authenticated</h1>
|
||||
<div>Access token: {accessToken}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServerSidePage
|
||||
```
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
title: 'Protecting routes'
|
||||
---
|
||||
|
||||
Create a `auth-protected.js` file:
|
||||
|
||||
```jsx
|
||||
import { useRouter } from 'next/router'
|
||||
import { useAuthLoading, useAuthenticated } from '@nhost/react'
|
||||
|
||||
export function authProtected(Comp) {
|
||||
return function AuthProtected(props) {
|
||||
const router = useRouter()
|
||||
const isLoading = useAuthLoading()
|
||||
const isAuthenticated = useAuthenticated()
|
||||
|
||||
if (isLoading) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
router.push('/login')
|
||||
return null
|
||||
}
|
||||
|
||||
return <Comp {...props} />
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then wrap the Next.js page with `authProtected` to only allow signed in users to access the page.
|
||||
|
||||
```js
|
||||
import { authProtected } from '<some-path>/auth-protected'
|
||||
|
||||
function Index() {
|
||||
return <div>Only signed in users can access this page.</div>
|
||||
}
|
||||
|
||||
export default authProtected(Index)
|
||||
```
|
||||
@@ -1,401 +0,0 @@
|
||||
---
|
||||
title: 'Hooks'
|
||||
---
|
||||
|
||||
## Authentication Hooks
|
||||
|
||||
### Email and Password Sign-Un
|
||||
|
||||
```js
|
||||
const { signUp, isLoading, isSuccess, needsVerification, isError, error } =
|
||||
useEmailPasswordSignUp(email?: string, password?: string, options?: Options )
|
||||
```
|
||||
|
||||
| Name | Type | Notes |
|
||||
| ---------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `signUp` | (email?: string, password?: string) => void | Used for a new user to sign up. The email/password arguments will take precedence over the possible state values used when creating the hook. |
|
||||
| `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. |
|
||||
| `needsVerification` | boolean | Returns `true` if the sign-up has been accepted, but a verificaiton email has been sent and is awaiting. |
|
||||
| `isSuccess` | boolean | Returns `true` if the sign-up suceeded. Returns `false` if the new email needs to be verified first, or if an error occurred. |
|
||||
| `isError` | boolean | Returns `true` if an error occurred. |
|
||||
| `error` | {status: number, error: string, message: string} \| undefined | Provides details about the error. |
|
||||
| `options.locale` | string \| undefined | Locale of the user, in two digits, for instance `en`. |
|
||||
| `options.allowedRoles` | string[] \| undefined | Allowed roles of the user. Must be a subset of the default allowed roles defined in Hasura Auth. |
|
||||
| `options.defaultRole` | string \| undefined | Default role of the user. Must be part of the default allowed roles defined in Hasura Auth. |
|
||||
| `options.displayName` | string \| undefined | |
|
||||
| `options.metadata` | Record<string, unknown> \| undefined | Custom additional user information stored in the `metadata` column. Can be any JSON object. |
|
||||
| `options.redirectTo` | string \| undefined | redirection path in the client application that will be used in the link in the verification email. For instance, if you want to redirect to `https://myapp.com/success`, the `redirectTo` value is `'/success'`. |
|
||||
|
||||
#### Usage
|
||||
|
||||
```jsx
|
||||
import { useState } from 'react'
|
||||
import { useEmailPasswordSignUp } from '@nhost/react'
|
||||
|
||||
const Component = () => {
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const { signUp, isLoading, isSuccess, needsVerification, isError, error } =
|
||||
useEmailPasswordSignUp(email, password)
|
||||
return (
|
||||
<div>
|
||||
<input value={email} onChange={(event) => setEmail(event.target.value)} placeholder="Email" />
|
||||
<input
|
||||
value={password}
|
||||
onChange={(event) => setPassword(event.target.value)}
|
||||
placeholder="Password"
|
||||
/>
|
||||
<button onClick={signUp}>Register</button>
|
||||
{isSuccess && <div>Your account have beed created! You are now authenticated</div>}
|
||||
{needsVerification && (
|
||||
<div>Please check your mailbox and follow the verification link to verify your email</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Email and Password Sign-In
|
||||
|
||||
```js
|
||||
const { signIn, isLoading, needsVerification, isSuccess, isError, error } =
|
||||
useEmailPasswordSignIn(email?: string, password?: string)
|
||||
```
|
||||
|
||||
| Name | Type | Notes |
|
||||
| ------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `signIn` | (email?: string, password?: string) => void | Will try to authenticate. The email/password arguments will take precedence over the possible state values used when creating the hook. |
|
||||
| `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. |
|
||||
| `needsVerification` | boolean | Returns `true` if the user email is still pending verification. |
|
||||
| `isSuccess` | boolean | Returns `true` if the user has successfully authenticated. Returns `false` in case or error or if the new email needs to be verified first. |
|
||||
| `isError` | boolean | Returns `true` if an error occurred. |
|
||||
| `error` | {status: number, error: string, message: string} \| undefined | Provides details about the error. |
|
||||
|
||||
#### Usage
|
||||
|
||||
```jsx
|
||||
import { useState } from 'react'
|
||||
import { useEmailPasswordSignIn } from '@nhost/react'
|
||||
|
||||
const Component = () => {
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const { signIn, isLoading, isSuccess, needsVerification, isError, error } =
|
||||
useEmailPasswordSignIn(email, password)
|
||||
return (
|
||||
<div>
|
||||
<input value={email} onChange={(event) => setEmail(event.target.value)} placeholder="Email" />
|
||||
<input
|
||||
value={password}
|
||||
onChange={(event) => setPassword(event.target.value)}
|
||||
placeholder="Password"
|
||||
/>
|
||||
<button onClick={signUp}>Register</button>
|
||||
{isSuccess && <div>Authentication suceeded</div>}
|
||||
{needsVerification && (
|
||||
<div>
|
||||
You must verify your email to sign in. Check your mailbox and follow the instructions to
|
||||
verify your email.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Oauth Providers
|
||||
|
||||
```js
|
||||
import { useProviderLink } from '@nhost/react'
|
||||
|
||||
const Component = () => {
|
||||
const { github } = useProviderLink()
|
||||
return <a href={github}>Authenticate with GitHub</a>
|
||||
}
|
||||
```
|
||||
|
||||
### Passwordless email authentication
|
||||
|
||||
```js
|
||||
const { signIn, isLoading, isSuccess, isError, error } =
|
||||
useEmailPasswordlessSignIn(email?: string, options?: Options)
|
||||
```
|
||||
|
||||
| Name | Type | Notes |
|
||||
| ---------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `signIn` | (email?: string) => void | Sends a magic link to the given email The email argument will take precedence over the the possible state value used when creating the hook. |
|
||||
| `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. |
|
||||
| `isSuccess` | boolean | Returns `true` if the magic link email user has successfully send. |
|
||||
| `isError` | boolean | Returns `true` if an error occurred. |
|
||||
| `error` | {status: number, error: string, message: string} | Provides details about the error. |
|
||||
| `options.locale` | string \| undefined | Locale of the user, in two digits, for instance `en`. |
|
||||
| `options.allowedRoles` | string[] \| undefined | Allowed roles of the user. Must be a subset of the default allowed roles defined in Hasua Auth. |
|
||||
| `options.defaultRole` | string \| undefined | Default role of the user. Must be part of the default allowed roles defined in Hasura Auth. |
|
||||
| `options.displayName` | string \| undefined |
|
||||
| `options.metadata` | Record<string, unknown> \| undefined | Custom additional user information stored in the `metadata` column. Can be any JSON object. |
|
||||
| `options.redirectTo` | string \| undefined | Redirection path in the client application that will be used in the link in the verification email. For instance, if you want to redirect to `https://myapp.com/success`, the `redirectTo` value is `'/success'`. |
|
||||
|
||||
#### Usage
|
||||
|
||||
```jsx
|
||||
import { useState } from 'react'
|
||||
import { useEmailPasswordlessSignIn } from '@nhost/react'
|
||||
|
||||
const Component = () => {
|
||||
const [email, setEmail] = useState('')
|
||||
const { signIn, isLoading, isSuccess, isError, error } = useEmailPasswordlessSignIn(email)
|
||||
return (
|
||||
<div>
|
||||
<input value={email} onChange={(event) => setEmail(event.target.value)} placeholder="Email" />
|
||||
<button onClick={signUp}>Register</button>
|
||||
{isSuccess && (
|
||||
<div>
|
||||
An email has been sent to {email}. Please check your mailbox and click on the
|
||||
authentication link.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Sign Out
|
||||
|
||||
The `useSignOut` hook accepts an `all` argument that will be used when the `signOut` method will be called. This value can be overriden in calling `signOut(allValue)`.
|
||||
|
||||
```js
|
||||
const { signOut, isSuccess } = useSignOut(all?: boolean)
|
||||
```
|
||||
|
||||
| Name | Type | Notes |
|
||||
| ----------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `signOut` | (all?: boolean) => void | Will log the current user out. If `all` is set to true, it will deactivate the active session from all other devices. The `all` argument will take precedence over the the possible state value used when creating the hook. |
|
||||
| `isSuccess` | boolean | Returns `true` when the user has successfully signed out. |
|
||||
|
||||
#### Usage
|
||||
|
||||
```jsx
|
||||
import { useState } from 'react'
|
||||
import { useSignOut, useAuthenticated } from '@nhost/react'
|
||||
|
||||
const Component = () => {
|
||||
const { signOut, isSuccess } = useSignOut()
|
||||
const authenticated = useAuthenticated()
|
||||
if (authenticated)
|
||||
return (
|
||||
<div>
|
||||
<button onClick={signUp}>Sign Out</button>
|
||||
{isSuccess && <div>You have successfully signed out!</div>}
|
||||
</div>
|
||||
)
|
||||
else return <div>Not authenticated</div>
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication status
|
||||
|
||||
### `useAuthLoading`
|
||||
|
||||
The Nhost client may need some initial steps to determine the authentication status during startup, like fetching a new JWT from an existing refresh token.
|
||||
|
||||
`useAuthLoading` will return `true` until the authentication status is known.
|
||||
|
||||
#### Usage
|
||||
|
||||
```jsx
|
||||
import { useAuthLoading, useAuthenticated } from '@nhost/react'
|
||||
|
||||
const Component = () => {
|
||||
const isLoading = useAuthLoading()
|
||||
const isAuthenticated = useAuthenticated()
|
||||
if (isLoading) return <div>Loading Nhost authentication status...</div>
|
||||
else if (isAuthenticated) return <div>User is authenticated</div>
|
||||
else return <div>Public section</div>
|
||||
}
|
||||
```
|
||||
|
||||
### Get the JWT access token
|
||||
|
||||
<!-- TODO better documentation -->
|
||||
|
||||
```js
|
||||
const accessToken = useAccessToken()
|
||||
```
|
||||
|
||||
## User management
|
||||
|
||||
### Change email
|
||||
|
||||
```js
|
||||
const { changeEmail, isLoading, isSuccess, needsVerification, isError, error } =
|
||||
useChangeEmail(email?: string, options?: { redirectTo?: string })
|
||||
```
|
||||
|
||||
| Name | Type | Notes |
|
||||
| ------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `changeEmail` | (email?: string) => void | Rrequests the email change. The arguement password will take precedence over the the possible state value used when creating the hook. |
|
||||
| `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. |
|
||||
| `needsVerification` | boolean | Returns `true` if the email change has been requested, but that a email has been sent to the user to verify the new email. |
|
||||
| `isError` | boolean | Returns `true` if an error occurred. |
|
||||
| `error` | {status: number, error: string, message: string} \| undefined | Provides details about the error. |
|
||||
| `redirectTo` | string \| undefined | Redirection path in the client application that will be used in the link in the verification email. For instance, if you want to redirect to `https://myapp.com/success`, the `redirectTo` value is `'/success'`. |
|
||||
|
||||
#### Usage
|
||||
|
||||
```jsx
|
||||
import { useState } from 'react'
|
||||
import { useChangeEmail } from '@nhost/react'
|
||||
|
||||
const Component = () => {
|
||||
const [email, setEmail] = useState('')
|
||||
const { changeEmail, isLoading, needsVerification, isError, error } = useChangeEmail(password)
|
||||
return (
|
||||
<div>
|
||||
<input value={email} onChange={(event) => setEmail(event.target.value)} />
|
||||
<button onClick={changeEmail}>Change password</button>
|
||||
{needsVerification && (
|
||||
<div>
|
||||
Please check your mailbox and follow the verification link to confirm your new email
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Change password
|
||||
|
||||
```js
|
||||
const { changePassword, isLoading, isSuccess, isError, error } = useChangePassword(password?: string)
|
||||
```
|
||||
|
||||
| Name | Type | Notes |
|
||||
| ---------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `changePassword` | (password?: string) => void | Requests the password change. The arguement password will take precedence over the the possible state value used when creating the hook. |
|
||||
| `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. |
|
||||
| `isSuccess` | boolean | Returns `true` if the password has beed successfully changed. |
|
||||
| `isError` | boolean | Returns `true` if an error occurred. |
|
||||
| `error` | {status: number, error: string, message: string} \| undefined | Provides details about the error. |
|
||||
|
||||
#### Usage
|
||||
|
||||
```jsx
|
||||
import { useState } from 'react'
|
||||
import { useChangePassword } from '@nhost/react'
|
||||
|
||||
const Component = () => {
|
||||
const [password, setPassword] = useState('')
|
||||
const { changePassword, isLoading, isSuccess, isError, error } = useChangePassword(password)
|
||||
return (
|
||||
<div>
|
||||
<input value={password} onChange={(event) => setPassword(event.target.value)} />
|
||||
<button onClick={changePassword}>Change password</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Reset password
|
||||
|
||||
If a user loses their password, we can resend them an email to authenticate so that they can change it to a new one:
|
||||
|
||||
```js
|
||||
const { resetPassword, isLoading, isSent, isError, error } = useResetPassword(email?: string, options?: { redirectTo?: string })
|
||||
```
|
||||
|
||||
| Name | Type | Notes |
|
||||
| --------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `resetPassword` | (email?: string) => void | Sends an email with a temporary connection link. The arguement email will take precedence over the the possible state value used when creating the hook. |
|
||||
| `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. |
|
||||
| `isSent` | boolean | Returns `true` when the email has been successfully sent. |
|
||||
| `isError` | boolean | Returns `true` if an error occurred. |
|
||||
| `error` | {status: number, error: string, message: string} \| undefined | Provides details about the error. |
|
||||
| `redirectTo` | string \| undefined | Redirection path in the client application that will be used in the link in the verification email. For instance, if you want to redirect to `https://myapp.com/success`, the `redirectTo` value is `'/success'`. |
|
||||
|
||||
#### Usage
|
||||
|
||||
```jsx
|
||||
import { useState } from 'react'
|
||||
import { useResetPassword } from '@nhost/react'
|
||||
|
||||
const Component = () => {
|
||||
const [email, setEamil] = useState('')
|
||||
const { resetPassword, isLoading, isSent, isError, error } = useResetPassword(email?: string)
|
||||
return (
|
||||
<div>
|
||||
<input value={email} onChange={(event) => setEmail(event.target.value)} />
|
||||
<button onClick={resetPassword}>Send reset link</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## User data
|
||||
|
||||
<!-- TODO document -->
|
||||
|
||||
```js
|
||||
const userData = useUserData()
|
||||
```
|
||||
|
||||
### Avatar
|
||||
|
||||
```jsx
|
||||
import { useAvatarUrl } from '@nhost/react'
|
||||
|
||||
const Avatar = () => {
|
||||
const avatar = useAvatarUrl()
|
||||
return <img src={avatar} alt="Avatar" />
|
||||
}
|
||||
```
|
||||
|
||||
### User roles
|
||||
|
||||
```jsx
|
||||
import { useUserRoles, useDefaultRole } from '@nhost/react'
|
||||
|
||||
const Avatar = () => {
|
||||
const roles = useUserRoles()
|
||||
const defaultRole = useDefaultRole()
|
||||
return (
|
||||
<div>
|
||||
Your default role is {defaultRole}. You have the following roles: {roles.join(', ')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Display name
|
||||
|
||||
```jsx
|
||||
import { displayName } from '@nhost/react'
|
||||
|
||||
const Avatar = () => {
|
||||
const displayName = useDisplayName()
|
||||
return <div>Hello, {displayName}</div>
|
||||
}
|
||||
```
|
||||
|
||||
### Email
|
||||
|
||||
```js
|
||||
const email = useEmail()
|
||||
```
|
||||
|
||||
### User Id
|
||||
|
||||
```js
|
||||
const userId = useUserId()
|
||||
```
|
||||
|
||||
### Anonymous user
|
||||
|
||||
```js
|
||||
const isAnonymous = useIsAnonymous()
|
||||
```
|
||||
|
||||
### Locale
|
||||
|
||||
```js
|
||||
const locale = useUserLocale()
|
||||
```
|
||||
@@ -1,58 +0,0 @@
|
||||
---
|
||||
title: 'Getting started'
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
With Yarn:
|
||||
|
||||
```sh
|
||||
yarn add @nhost/react
|
||||
```
|
||||
|
||||
With Npm:
|
||||
|
||||
```sh
|
||||
npm install @nhost/react
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
`@nhost/react` exports a React provider `NhostProvider` that makes the authentication state and the several hooks available in your application. Wrap this component around your whole App.
|
||||
|
||||
```jsx
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
|
||||
import { NhostProvider } from '@nhost/react'
|
||||
import { Nhost } from '@nhost/client'
|
||||
|
||||
import App from './App'
|
||||
|
||||
const nhost = new Nhost({
|
||||
backendUrl: 'http://localhost:1337'
|
||||
})
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<NhostProvider nhost={nhost}>
|
||||
<App />
|
||||
</NhostProvider>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
)
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```js
|
||||
const nhost = new Nhost({ backendUrl, autoSignIn, autoRefreshToken, storageGetter, storageSetter })
|
||||
```
|
||||
|
||||
| Name | Type | Default | Notes |
|
||||
| ------------------ | ----------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `backendUrl` | string | | The Nhost app url, for instance `https://my-app.nhost.run`. When using the CLI, its value is `http://localhost:1337` |
|
||||
| `autoSignIn` | boolean | `true` | If set to `true`, the client will detect credentials in the current URL that could have been sent during an email verification or an Oauth authentication. It will also automatically authenticate all the active tabs in the current browser. |
|
||||
| `autoRefreshToken` | boolean | `true` | If set to `true`, the JWT (access token) will be automatically refreshed before it expires. |
|
||||
| `storageGetter` | (key:string) => string \| null | use localStorage | Nhost stores a refresh token in `localStorage` so the session can be restored when starting the browser. |
|
||||
| `storageSetter` | (key: string, value: string \| null | use localStorage | |
|
||||
@@ -1,44 +0,0 @@
|
||||
---
|
||||
title: 'Protecting routes'
|
||||
---
|
||||
|
||||
## React Router
|
||||
|
||||
You can protect routes by creating an `AuthGate` component when using `@nhost/react` with [React Router](https://reactrouter.com/web/guides/quick-start).
|
||||
|
||||
```jsx
|
||||
import { Redirect } from 'react-router-dom'
|
||||
import { useAuthLoading, useAuthenticated } from '@nhost/react'
|
||||
|
||||
export function AuthGate(children) {
|
||||
const isLoading = useAuthLoading()
|
||||
const isAuthenticated = useAuthenticated()
|
||||
|
||||
if (isLoading) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return <Redirect to="/login" />
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
||||
```
|
||||
|
||||
Then, in your React Router, wrap the `AuthGate` component around the routes you want to protect:
|
||||
|
||||
```jsx
|
||||
<Router>
|
||||
<Switch>
|
||||
<Route path="/login">
|
||||
<Login />
|
||||
</Route>
|
||||
<Route path="/" exact>
|
||||
<AuthGate> // <--- Use AuthGate component like this
|
||||
<div>My protected dashboard</div>
|
||||
</AuthGate>
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
```
|
||||
@@ -1,112 +0,0 @@
|
||||
export const nhostTheme = {
|
||||
hljs: {
|
||||
display: 'block',
|
||||
background: '#F4F7F9',
|
||||
color: '#21324B'
|
||||
},
|
||||
'hljs-tag': {
|
||||
color: '#9C73DF'
|
||||
},
|
||||
'hljs-keyword': {
|
||||
color: '#9C73DF',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
'hljs-selector-tag': {
|
||||
color: '#9C73DF',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
'hljs-literal': {
|
||||
color: '#9C73DF',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
'hljs-strong': {
|
||||
color: '#9C73DF'
|
||||
},
|
||||
'hljs-name': {
|
||||
color: '#9C73DF'
|
||||
},
|
||||
'hljs-code': {
|
||||
color: '#66d9ef'
|
||||
},
|
||||
'hljs-class .hljs-title': {
|
||||
color: 'red'
|
||||
},
|
||||
'hljs-attribute': {
|
||||
color: '#bf79db'
|
||||
},
|
||||
'hljs-symbol': {
|
||||
color: '#bf79db'
|
||||
},
|
||||
'hljs-regexp': {
|
||||
color: '#bf79db'
|
||||
},
|
||||
'hljs-link': {
|
||||
color: '#bf79db'
|
||||
},
|
||||
'hljs-string': {
|
||||
color: '#B7A590'
|
||||
},
|
||||
'hljs-bullet': {
|
||||
color: '#B7A590'
|
||||
},
|
||||
'hljs-subst': {
|
||||
color: '#B7A590'
|
||||
},
|
||||
'hljs-title': {
|
||||
color: '#B7A590',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
'hljs-section': {
|
||||
color: '#B7A590',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
'hljs-emphasis': {
|
||||
color: '#3ECF8E'
|
||||
},
|
||||
'hljs-type': {
|
||||
color: '#3ECF8E',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
'hljs-built_in': {
|
||||
color: '#3ECF8E'
|
||||
},
|
||||
'hljs-builtin-name': {
|
||||
color: '#3ECF8E'
|
||||
},
|
||||
'hljs-selector-attr': {
|
||||
color: '#3ECF8E'
|
||||
},
|
||||
'hljs-selector-pseudo': {
|
||||
color: '#3ECF8E'
|
||||
},
|
||||
'hljs-addition': {
|
||||
color: '#3ECF8E'
|
||||
},
|
||||
'hljs-variable': {
|
||||
color: '#3ECF8E'
|
||||
},
|
||||
'hljs-template-tag': {
|
||||
color: '#3ECF8E'
|
||||
},
|
||||
'hljs-template-variable': {
|
||||
color: '#3ECF8E'
|
||||
},
|
||||
'hljs-comment': {
|
||||
color: '#75715e'
|
||||
},
|
||||
'hljs-quote': {
|
||||
color: '#75715e'
|
||||
},
|
||||
'hljs-deletion': {
|
||||
color: '#75715e'
|
||||
},
|
||||
'hljs-meta': {
|
||||
color: '#75715e'
|
||||
},
|
||||
'hljs-doctag': {
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
'hljs-selector-id': {
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
export const lightNhostTheme = {
|
||||
hljs: {
|
||||
display: 'block',
|
||||
background: '#F4F7F9',
|
||||
color: '#21324B'
|
||||
},
|
||||
'hljs-tag': {
|
||||
color: '#9C73DF'
|
||||
},
|
||||
'hljs-keyword': {
|
||||
color: '#9C73DF',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
'hljs-selector-tag': {
|
||||
color: '#9C73DF',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
'hljs-literal': {
|
||||
color: '#9C73DF',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
'hljs-strong': {
|
||||
color: '#9C73DF'
|
||||
},
|
||||
'hljs-name': {
|
||||
color: '#9C73DF'
|
||||
},
|
||||
'hljs-code': {
|
||||
color: '#66d9ef'
|
||||
},
|
||||
'hljs-class .hljs-title': {
|
||||
color: 'red'
|
||||
},
|
||||
'hljs-attribute': {
|
||||
color: '#bf79db'
|
||||
},
|
||||
'hljs-symbol': {
|
||||
color: '#bf79db'
|
||||
},
|
||||
'hljs-regexp': {
|
||||
color: '#bf79db'
|
||||
},
|
||||
'hljs-link': {
|
||||
color: '#bf79db'
|
||||
},
|
||||
'hljs-string': {
|
||||
color: '#B7A590'
|
||||
},
|
||||
'hljs-bullet': {
|
||||
color: '#B7A590'
|
||||
},
|
||||
'hljs-subst': {
|
||||
color: '#B7A590'
|
||||
},
|
||||
'hljs-title': {
|
||||
color: '#B7A590',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
'hljs-section': {
|
||||
color: '#B7A590',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
'hljs-emphasis': {
|
||||
color: '#3ECF8E'
|
||||
},
|
||||
'hljs-type': {
|
||||
color: '#3ECF8E',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
'hljs-built_in': {
|
||||
color: '#3ECF8E'
|
||||
},
|
||||
'hljs-builtin-name': {
|
||||
color: '#3ECF8E'
|
||||
},
|
||||
'hljs-selector-attr': {
|
||||
color: '#3ECF8E'
|
||||
},
|
||||
'hljs-selector-pseudo': {
|
||||
color: '#3ECF8E'
|
||||
},
|
||||
'hljs-addition': {
|
||||
color: '#3ECF8E'
|
||||
},
|
||||
'hljs-variable': {
|
||||
color: '#3ECF8E'
|
||||
},
|
||||
'hljs-template-tag': {
|
||||
color: '#3ECF8E'
|
||||
},
|
||||
'hljs-template-variable': {
|
||||
color: '#3ECF8E'
|
||||
},
|
||||
'hljs-comment': {
|
||||
color: '#75715e'
|
||||
},
|
||||
'hljs-quote': {
|
||||
color: '#75715e'
|
||||
},
|
||||
'hljs-deletion': {
|
||||
color: '#75715e'
|
||||
},
|
||||
'hljs-meta': {
|
||||
color: '#75715e'
|
||||
},
|
||||
'hljs-doctag': {
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
'hljs-selector-id': {
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"order": [
|
||||
{
|
||||
"id": "get-started",
|
||||
"name": "get-started",
|
||||
"description": "get-started",
|
||||
"pages": [
|
||||
{
|
||||
"route": "quickstart",
|
||||
"display": "Quick Start"
|
||||
},
|
||||
{
|
||||
"route": "app-development",
|
||||
"display": "App Development"
|
||||
},
|
||||
{
|
||||
"route": "workflow",
|
||||
"display": "Workflow"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
export interface Meta {
|
||||
order: CategoryLink[]
|
||||
}
|
||||
|
||||
export interface CategoryLink {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
pages: Page[]
|
||||
}
|
||||
|
||||
interface Page {
|
||||
route: string
|
||||
display: string
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
{
|
||||
"siteLinks": [
|
||||
{
|
||||
"text": "product",
|
||||
"links": [
|
||||
{
|
||||
"name": "Product",
|
||||
"href": "https://nhost.io/#product"
|
||||
},
|
||||
{
|
||||
"name": "Features",
|
||||
"href": "https://nhost.io/#features"
|
||||
},
|
||||
{
|
||||
"name": "Pricing",
|
||||
"href": "https://nhost.io/pricing"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "docs",
|
||||
"links": [
|
||||
{
|
||||
"name": "Get Started",
|
||||
"href": "/get-started"
|
||||
},
|
||||
{
|
||||
"name": "Platform",
|
||||
"href": "/platform"
|
||||
},
|
||||
{
|
||||
"name": "Reference",
|
||||
"href": "/reference"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Frameworks",
|
||||
"links": [
|
||||
{
|
||||
"name": "API Reference",
|
||||
"href": "/reference"
|
||||
},
|
||||
{
|
||||
"name": "JS SDK",
|
||||
"href": "/reference/sdk"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "community",
|
||||
"links": [
|
||||
{
|
||||
"name": "GitHub",
|
||||
"href": "https://github.com/nhost"
|
||||
},
|
||||
{
|
||||
"name": "Discord",
|
||||
"href": "https://discord.com/invite/9V7Qb2U"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export interface SiteLinks {
|
||||
siteLinks: SiteLink[]
|
||||
}
|
||||
|
||||
interface SiteLink {
|
||||
text: string
|
||||
links: string[]
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 'Authenticate users'
|
||||
slug: /get-started/authentication
|
||||
---
|
||||
|
||||
You defined `select` permissions for the `public` role in the previous section. You will now add `insert` and `create` permissions for authenticated users to secure your app's GraphQL API with authentication.
|
||||
@@ -12,7 +13,7 @@ You defined `select` permissions for the `public` role in the previous section.
|
||||
|
||||
Manually create a user by going to your app's **Users** tab (top menu) and clicking on **Add User**.
|
||||
|
||||

|
||||

|
||||
|
||||
You will now use that newly created user to make authenticated requests to the API.
|
||||
|
||||
@@ -23,20 +24,20 @@ You will now use that newly created user to make authenticated requests to the A
|
||||
Add the following code to sign in the new user and request the list of todos again:
|
||||
|
||||
```js
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import { NhostClient } from '@nhost/nhost-js';
|
||||
|
||||
const nhost = new NhostClient({
|
||||
backendUrl: 'https://[app-subdomain].nhost.run'
|
||||
backendUrl: 'https://[app-subdomain].nhost.run',
|
||||
})(async () => {
|
||||
// Sign in user
|
||||
const signInResponse = await nhost.auth.signIn({
|
||||
email: 'joe@example.com',
|
||||
password: 'securepassword'
|
||||
})
|
||||
password: 'securepassword',
|
||||
});
|
||||
|
||||
// Handle sign-in error
|
||||
if (signInResponse.error) {
|
||||
throw signInResponse.error
|
||||
throw signInResponse.error;
|
||||
}
|
||||
|
||||
// Get todos
|
||||
@@ -49,10 +50,10 @@ const nhost = new NhostClient({
|
||||
is_completed
|
||||
}
|
||||
}
|
||||
`)
|
||||
`);
|
||||
|
||||
console.log(JSON.stringify(todos.data, null, 2))
|
||||
})()
|
||||
console.log(JSON.stringify(todos.data, null, 2));
|
||||
})();
|
||||
```
|
||||
|
||||
Why is the return value `null`? Because when making GraphQL requests as an authenticated user, the `user` role is assumed.
|
||||
@@ -67,7 +68,7 @@ Why is the return value `null`? Because when making GraphQL requests as an authe
|
||||
|
||||
We won't use the `public` role anymore, so let's remove all permission for that role.
|
||||
|
||||

|
||||

|
||||
|
||||
Now we'll add permissions for the `user` role.
|
||||
|
||||
@@ -79,12 +80,12 @@ First, we'll set the **Insert permission**.
|
||||
|
||||
A user can only insert `name` because all other columns will be set automatically. More specifically, `user_id` is set to the user's id making the request (`x-hasura-user-id`) and is configured in the `Column presets` section. See the image below.
|
||||
|
||||

|
||||

|
||||
|
||||
### Select permission
|
||||
|
||||
For **Select permission**, set a **custom check** so users can only select todos where `user_id` is the same as their user id. In other words: users are only allowed to select their own todos. See the image below.
|
||||
|
||||

|
||||

|
||||
|
||||
Now rerun the app. New todos are inserted, and only todos for the user are fetched and displayed. Your backend is successfully secured!
|
||||
4
docs/docs/get-started/cli-workflow/_category_.json
Normal file
4
docs/docs/get-started/cli-workflow/_category_.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "CLI",
|
||||
"position": 8
|
||||
}
|
||||
@@ -12,9 +12,9 @@ In the previous tutorials, we tested various parts of Nhost, such as:
|
||||
|
||||
All changes we did to our database and API happened directly in production of our Nhost app.
|
||||
|
||||
It’s not ideal for making changes in production because you might break things, which will affect all users of your app.
|
||||
It's not ideal for making changes in production because you might break things, which will affect all users of your app.
|
||||
|
||||
Instead, it’s recommended to make changes and test your app locally before deploying those changes to production.
|
||||
Instead, it's recommended to make changes and test your app locally before deploying those changes to production.
|
||||
|
||||
To do changes locally, we need to have a complete Nhost app running locally, which the Nhost CLI does.
|
||||
|
||||
@@ -26,8 +26,8 @@ The Nhost CLI matches your production application in a local environment, this w
|
||||
2. Push changes to GitHub.
|
||||
3. Nhost automatically applies changes to production.
|
||||
|
||||
## What you’ll learn in this guide:
|
||||
## What you'll learn in this guide:
|
||||
|
||||
- Use the Nhost CLI to create a local environment
|
||||
- Connect a GitHub repository with a Nhost app
|
||||
- Deploy local changes to production
|
||||
- Deploy local changes to production
|
||||
@@ -4,13 +4,13 @@ title: 'Install the CLI'
|
||||
|
||||
Install the Nhost CLI using the following command:
|
||||
|
||||
```sql
|
||||
```bash
|
||||
sudo curl -L https://raw.githubusercontent.com/nhost/cli/main/get.sh | bash
|
||||
```
|
||||
|
||||
Initialize a new Nhost App locally:
|
||||
|
||||
```sql
|
||||
```bash
|
||||
nhost init -n "nhost-example-app" && cd nhost-example-app
|
||||
```
|
||||
|
||||
@@ -28,10 +28,10 @@ git push -u origin main
|
||||
|
||||
Now go back to the **Nhost Console** and click **Deployments**. You just made a new deployment to your Nhost app!
|
||||
|
||||

|
||||

|
||||
|
||||
If you click on the deployment you can see that nothing was really deployed. That’s because we just made a change to the README file.
|
||||
|
||||

|
||||

|
||||
|
||||
Let's do some local backend changes!
|
||||
@@ -4,11 +4,13 @@ title: 'Local changes'
|
||||
|
||||
Start Nhost locally:
|
||||
|
||||
```sql
|
||||
```bash
|
||||
nhost dev
|
||||
```
|
||||
|
||||
>💡 Make sure you have [Docker](https://www.docker.com/get-started) installed on your computer. It’s required for Nhost to work.
|
||||
:::tip
|
||||
Make sure you have [Docker](https://www.docker.com/get-started) installed on your computer. It’s required for Nhost to work.
|
||||
:::
|
||||
|
||||
The `nhost dev` command will automatically start a complete Nhost environment locally on your computer using:
|
||||
|
||||
@@ -23,18 +25,22 @@ You use this local environment to do changes and testing before you deploy your
|
||||
|
||||
Running `nhost dev` also starts the Hasura Console.
|
||||
|
||||
:::tip
|
||||
It's important that you use the Hasura Console that is started automatically when you do changes. This way, changes are automatically tracked for you.
|
||||
:::
|
||||
|
||||
>💡 It’s important that you use the Hasura Console that is started automatically when you do changes. This way, changes are automatically tracked for you.
|
||||
|
||||

|
||||

|
||||
|
||||
In the Hasura Console, create a new table `customers` with two columns:
|
||||
|
||||
- id
|
||||
- name
|
||||
|
||||
<Video src="/videos/cli-workflow/hasura-create-customers-table.mp4">
|
||||
</Video>
|
||||
<video
|
||||
src="/videos/cli-workflow/hasura-create-customers-table.mp4"
|
||||
width="100%"
|
||||
controls
|
||||
/>
|
||||
|
||||
When we created the `customers` table there was also a migration created automatically. The migration was created at under `nhost/migrations/default`.
|
||||
|
||||
@@ -50,12 +56,11 @@ This database migration has only been applied locally, meaning, you created the
|
||||
|
||||
To apply the local change to production we need to commit the changes and push it to GitHub. Nhost will then automatically pick up the change in the repository and apply the changes.
|
||||
|
||||
<aside>
|
||||
💡 You can commit and push files in another terminal while still having `nhost dev` running.
|
||||
:::tip
|
||||
You can commit and push files in another terminal while still having `nhost dev` running.
|
||||
:::
|
||||
|
||||
</aside>
|
||||
|
||||
```sql
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "Initialized Nhost and added a customers table"
|
||||
git push
|
||||
@@ -63,14 +68,14 @@ git push
|
||||
|
||||
Head over to the **Deployments** tab in the **Nhost console** to see the deployment.
|
||||
|
||||

|
||||

|
||||
|
||||
Once the deployment finishes the `customers` table is created in production.
|
||||
|
||||

|
||||

|
||||
|
||||
We’ve now completed the recommended workflow with Nhost:
|
||||
We've now completed the recommended workflow with Nhost:
|
||||
|
||||
1. Develop locally using the Nhost CLI.
|
||||
2. Push changes to GitHub.
|
||||
3. Nhost deploys changes to production.
|
||||
3. Nhost deploys changes to production.
|
||||
@@ -10,18 +10,18 @@ There are three things the CLI and the GitHub integration track and applies to p
|
||||
2. Hasura Metadata
|
||||
3. Serverless Functions
|
||||
|
||||
For this section, let’s do one change to the Hasura metadata and create one serverless function
|
||||
For this section, let's do one change to the Hasura metadata and create one serverless function
|
||||
|
||||
### Hasura Metadata
|
||||
|
||||
We’ll add permissions to the `users` table, making sure users can only see their own data. For this, go to the `auth` schema and click on the `users` table. then click on **Permissions** and enter a new role **user** and create a new **select** permission for that role**.**
|
||||
We'll add permissions to the `users` table, making sure users can only see their own data. For this, go to the `auth` schema and click on the `users` table. then click on **Permissions** and enter a new role **user** and create a new **select** permission for that role**.**
|
||||
|
||||
Create the permission **with custom check**:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": {
|
||||
"_eq" : "X-Hasura-User-Id"
|
||||
"_eq": "X-Hasura-User-Id"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -36,12 +36,15 @@ Select the following columns:
|
||||
|
||||
Then click **Save permissions**.
|
||||
|
||||
<Video src="/videos/cli-workflow/hasura-user-permissions.mp4">
|
||||
</Video>
|
||||
<video
|
||||
src="/videos/cli-workflow/hasura-user-permissions.mp4"
|
||||
width="100%"
|
||||
controls
|
||||
/>
|
||||
|
||||
Now, let’s do a `git status` again to confirm the permission changes we did was tracked locally in your git repository.
|
||||
Now, let's do a `git status` again to confirm the permission changes we did was tracked locally in your git repository.
|
||||
|
||||

|
||||

|
||||
|
||||
We can now commit this change:
|
||||
|
||||
@@ -50,13 +53,13 @@ git add -A
|
||||
git commit -m "added permission for uses"
|
||||
```
|
||||
|
||||
Now let’s create a serverless function before we push all changes to GitHub so Nhost can deploy our changes.
|
||||
Now let's create a serverless function before we push all changes to GitHub so Nhost can deploy our changes.
|
||||
|
||||
### Serverless Function
|
||||
|
||||
A serverless function is a pieces of code written in JavaScript or TypeScript that take an HTTP request and returns a response.
|
||||
|
||||
Here’s an example:
|
||||
Here's an example:
|
||||
|
||||
```bash
|
||||
import { Request, Response } from 'express'
|
||||
@@ -68,7 +71,7 @@ export default (req: Request, res: Response) => {
|
||||
|
||||
Serverless functions are placed in the `functions/` folder of your repository. Every file will become its own endpoint.
|
||||
|
||||
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.
|
||||
|
||||
```bash
|
||||
npm install express
|
||||
@@ -76,7 +79,7 @@ npm install express
|
||||
yarn add express
|
||||
```
|
||||
|
||||
We’ll use TypeScript so we’ll install two type definitions too:
|
||||
We'll use TypeScript so we'll install two type definitions too:
|
||||
|
||||
```bash
|
||||
npm install -d @types/node @types/express
|
||||
@@ -84,9 +87,9 @@ npm install -d @types/node @types/express
|
||||
yarn add -D @types/node @types/express
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
import { Request, Response } from 'express';
|
||||
@@ -98,11 +101,11 @@ export default (req: Request, res: Response) => {
|
||||
};
|
||||
```
|
||||
|
||||
We can now test the function locally. Locally, the backend URL is `http://localhost:1337`. Functions are under `/v1/functions`. And every function’s path and filename becomes an API endpoint.
|
||||
We can now test the function locally. Locally, the backend URL is `http://localhost:1337`. Functions are under `/v1/functions`. And every function's path and filename becomes an API endpoint.
|
||||
|
||||
This means our function `functions/time.ts` is at `http://localhost:1337/v1/functions/time`.
|
||||
|
||||
Let’s use curl to test our new function:
|
||||
Let's use curl to test our new function:
|
||||
|
||||
```bash
|
||||
curl http://localhost:1337/v1/functions/time
|
||||
@@ -116,9 +119,9 @@ curl http://localhost:1337/v1/functions/time\?name\=Johan
|
||||
Hello Johan! It's now: Sun, 06 Feb 2022 17:44:48 GMT
|
||||
```
|
||||
|
||||
Again, let’s use `git status` to see the changes we did to create our serverless function.
|
||||
Again, let's use `git status` to see the changes we did to create our serverless function.
|
||||
|
||||
Now let’s commit the changes and push them to GitHub.
|
||||
Now let's commit the changes and push them to GitHub.
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
@@ -128,32 +131,32 @@ git push
|
||||
|
||||
In the Nhost Console, click on the new deployment to see details.
|
||||
|
||||

|
||||

|
||||
|
||||
After Nhost has finished deploying your changes we can test them in production. First let’s confirm that the user permissions are applied.
|
||||
After Nhost has finished deploying your changes we can test them in production. First let's confirm that the user permissions are applied.
|
||||
|
||||

|
||||

|
||||
|
||||
Then, let’s confirm that the serverless function was deployed. Again, we’ll use curl:
|
||||
Then, let's confirm that the serverless function was deployed. Again, we'll use curl:
|
||||
|
||||
```bash
|
||||
curl https://your-backend-url.nhost.run/v1/functions/time\?name\=Johan
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
## Conclusion
|
||||
|
||||
In this tutorial we have installed the Nhost CLI and created a local Nhost environment to do local development and testing.
|
||||
|
||||
In the local environment we’ve made changes to our database, to Hasura’s metadata and created a serverless function.
|
||||
In the local environment we've made changes to our database, to Hasura's metadata and created a serverless function.
|
||||
|
||||
We’ve connected a GitHub repository and pushed our changes to GitHub.
|
||||
We've connected a GitHub repository and pushed our changes to GitHub.
|
||||
|
||||
We’ve seen Nhost automatically deploying our changes and we’ve verified that the changes were applied.
|
||||
We've seen Nhost automatically deploying our changes and we've verified that the changes were applied.
|
||||
|
||||
In summary, we’ve set up a productive environment using the recommended Nhost workflow:
|
||||
In summary, we've set up a productive environment using the recommended Nhost workflow:
|
||||
|
||||
1. Develop locally using the Nhost CLI.
|
||||
2. Push changes to GitHub.
|
||||
3. Nhost deploys changes to production.
|
||||
3. Nhost deploys changes to production.
|
||||
@@ -8,19 +8,24 @@ What follows is a detailed tutorial on how you setup Nhost for this workflow
|
||||
|
||||
Create a **new Nhost app** for this tutorial.
|
||||
|
||||
> It’s important that you create a **new** Nhost app for this guide instead of reusing an old Nhost app because we want to start with a clean Nhost app.
|
||||
:::tip
|
||||
It's important that you create a **new** Nhost app for this guide instead of reusing an old Nhost app because we want to start with a clean Nhost app.
|
||||
:::
|
||||
|
||||

|
||||

|
||||
|
||||
### Create new GitHub Repository
|
||||
|
||||
Create a new GitHub repository for your new Nhost app. The repo can be either private or public.
|
||||
|
||||

|
||||

|
||||
|
||||
## Connect GitHub Repository to Nhost App
|
||||
|
||||
In the Nhost Console, go to the dashboard of your Nhost app and click **Connect to GitHub**.
|
||||
|
||||
<Video src="/videos/cli-workflow/connect-github-repo.mp4">
|
||||
</Video>
|
||||
<video
|
||||
src="/videos/cli-workflow/connect-github-repo.mp4"
|
||||
width="100%"
|
||||
controls
|
||||
/>
|
||||
36
docs/docs/get-started/index.md
Normal file
36
docs/docs/get-started/index.md
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: 'Welcome to Nhost'
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
Nhost is an open-source, real-time, server-less backend platform for building reliable apps that scale with your business.
|
||||
|
||||
---
|
||||
|
||||
## Components
|
||||
|
||||
Nhost uses an opinionated set of open-source components.
|
||||
|
||||
#### Database
|
||||
|
||||
Your application gets its own PostgreSQL database, the world's most advanced relational database.
|
||||
|
||||
#### GraphQL API
|
||||
|
||||
Highly performant and real-time GraphQL API with Hasura.
|
||||
|
||||
#### Authentication and storage
|
||||
|
||||
User management & file storage seamlessly integrated with Hasura permissions.
|
||||
|
||||
#### Serverless functions
|
||||
|
||||
JavaScript and TypeScript functions run your custom code in the backend.
|
||||
|
||||
---
|
||||
|
||||
## Get started
|
||||
|
||||
Follow our [Quick start](/get-started/quick-start) guide to build your first app.
|
||||
|
||||
Check out [Nhost on GitHub](https://github.com/nhost/nhost). Give us a star, and feel free to open a discussion for any feature requests as well.
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 'Create your app'
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
Let's create a simple todo-app using Nhost. In a todo-app, a user should be able to create list items for their account (CRUD) and not have anyone else see them (permissions).
|
||||
@@ -28,7 +29,7 @@ Press the **"New App"** button on the console's home page. Choose a name and pic
|
||||
|
||||
You'll be all set with the Default Workspace and the Free plan for now.
|
||||
|
||||

|
||||

|
||||
|
||||
Creating a new app takes around 20 seconds or so. During this time, Nhost sets up your app's entire backend and infrastructure.
|
||||
|
||||
@@ -18,9 +18,17 @@ In this guide, we'll keep the example simple. We're not using a frontend framewo
|
||||
|
||||
Create a new folder called `nhost-todos`, and initialize a new JavaScript app there:
|
||||
|
||||
```sh
|
||||
Using npm package manager
|
||||
|
||||
```bash
|
||||
npm init -y
|
||||
# or
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
Using Yarn package manager
|
||||
|
||||
```bash
|
||||
yarn init -y
|
||||
```
|
||||
|
||||
@@ -28,9 +36,16 @@ yarn init -y
|
||||
|
||||
Install Nhost JavaScript SDK:
|
||||
|
||||
```sh
|
||||
Using npm package manager
|
||||
|
||||
```bash
|
||||
npm install @nhost/nhost-js
|
||||
# or
|
||||
```
|
||||
|
||||
or
|
||||
Using Yarn package manager
|
||||
|
||||
```bash
|
||||
yarn add @nhost/nhost-js
|
||||
```
|
||||
|
||||
@@ -43,18 +58,18 @@ In the new directory, create a file called `index.js`.
|
||||
Enter the following code into this file. It will initialize a new `NhostClient` that will interact with your backend:
|
||||
|
||||
```js
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import { NhostClient } from '@nhost/nhost-js';
|
||||
|
||||
const nhost = new NhostClient({
|
||||
backendUrl: 'https://[app-subdomain].nhost.run' // replace this with the backend URL of your app
|
||||
})
|
||||
backendUrl: 'https://[app-subdomain].nhost.run', // replace this with the backend URL of your app
|
||||
});
|
||||
|
||||
console.log(nhost.graphql.getUrl())
|
||||
console.log(nhost.graphql.getUrl());
|
||||
```
|
||||
|
||||
Run the code in your terminal. You should see your app's GraphQL endpoint URL:
|
||||
|
||||
```sh
|
||||
```bash
|
||||
➜ node index.js
|
||||
|
||||
https://[app-subdomain].nhost.run/v1/graphql
|
||||
@@ -65,10 +80,10 @@ https://[app-subdomain].nhost.run/v1/graphql
|
||||
If you now add the following GraphQL query to the client, let's see what happens when you run the updated version:
|
||||
|
||||
```js
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import { NhostClient } from '@nhost/nhost-js';
|
||||
|
||||
const nhost = new NhostClient({
|
||||
backendUrl: 'https://[app-subdomain].nhost.run'
|
||||
backendUrl: 'https://[app-subdomain].nhost.run',
|
||||
})(async () => {
|
||||
// nhost.graphql.request returns a promise, so we use await here
|
||||
const todos = await nhost.graphql.request(`
|
||||
@@ -80,14 +95,14 @@ const nhost = new NhostClient({
|
||||
is_completed
|
||||
}
|
||||
}
|
||||
`)
|
||||
`);
|
||||
|
||||
// Print todos to console
|
||||
console.log(JSON.stringify(todos.data, null, 2))
|
||||
})()
|
||||
console.log(JSON.stringify(todos.data, null, 2));
|
||||
})();
|
||||
```
|
||||
|
||||
```sh
|
||||
```bash
|
||||
➜ node index.js
|
||||
|
||||
null
|
||||
@@ -18,11 +18,11 @@ In Hasura Console, go to the **Data** tab, click on the **todos** table, then cl
|
||||
|
||||
Add the following permissions:
|
||||
|
||||

|
||||

|
||||
|
||||
Rerun the program. Now you see all todos.
|
||||
|
||||
```sh
|
||||
```bash
|
||||
➜ node index.js
|
||||
|
||||
{
|
||||
@@ -14,7 +14,7 @@ Go to the **Data** tab on your app's dashboard and select **Open Hasura**. Remem
|
||||
|
||||
The Hasura Console of your app's dedicated Hasura instance will open in a new tab. You can use Hasura Console to manage your app's schema, data, permissions, and event triggers.
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
@@ -27,7 +27,7 @@ You should see all your database tables on the left-hand side of the screen. You
|
||||
|
||||
If you open the `auth` schema, you'll see that your app already has a `users` table, so you don't have to create one.
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
@@ -44,7 +44,7 @@ In Hasura Console, go to the **data** tab, then click **Create Table**. Name thi
|
||||
|
||||
Using frequently used columns ensures the columns get the right name, type, and default value.
|
||||
|
||||

|
||||

|
||||
|
||||
### Add custom columns
|
||||
|
||||
@@ -55,7 +55,7 @@ Add two more columns manually:
|
||||
|
||||
Make sure to set the default value of `is_completed` to `false`.
|
||||
|
||||

|
||||

|
||||
|
||||
This is all we need! A new table will be created when you click **Add Table**.
|
||||
|
||||
@@ -65,7 +65,7 @@ This is all we need! A new table will be created when you click **Add Table**.
|
||||
|
||||
Go to the **Insert Row** tab to add some data to your database.
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
@@ -10,22 +10,24 @@ Upgrading from Nhost v1 to v2 requires database schema and Hasura metadata chang
|
||||
|
||||
### Create a new Nhost v2 app locally
|
||||
|
||||
> Make sure you have the [Nhost CLI](/reference/cli) installed
|
||||
:::tip
|
||||
Make sure you have the [Nhost CLI](/reference/cli) installed
|
||||
:::
|
||||
|
||||
```sh
|
||||
```bash
|
||||
nhost init my-nhost-v2-app
|
||||
cd my-nhost-v2-app
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
Inside the `nhost/` folder of your app, run:
|
||||
|
||||
```sh
|
||||
```bash
|
||||
hasura migrate create init --from-server --endpoint=[v1-endpoint] --admin-secret=[v1-admin-secret]
|
||||
|
||||
hasura metadata export --endpoint=[v1-endpoint] --admin-secret=[v1-admin-secret]
|
||||
@@ -35,7 +37,9 @@ hasura metadata export --endpoint=[v1-endpoint] --admin-secret=[v1-admin-secret]
|
||||
|
||||
Make the following changes manually to your migrations.
|
||||
|
||||
> The migration file is located at `nhost/migrations/[timestamp]/up.sql`.
|
||||
:::tip
|
||||
The migration file is located at `nhost/migrations/[timestamp]/up.sql`.
|
||||
:::
|
||||
|
||||
- Add `OR REPLACE` after `CREATE` for the `public.set_current_timestamp_updated_at` function
|
||||
- Delete all `auth.*` tables and functions (if any).
|
||||
@@ -46,7 +50,9 @@ Make the following changes manually to your migrations.
|
||||
|
||||
Make the following changes manually to your metadata.
|
||||
|
||||
> The metadata is located at `nhost/metadata/tables.yaml`.
|
||||
:::tip
|
||||
The metadata is located at `nhost/metadata/tables.yaml`.
|
||||
:::
|
||||
|
||||
- Delete tracking all tables in the `auth` schema.
|
||||
- Delete tracking the `public.users` table.
|
||||
@@ -56,11 +62,13 @@ Make the following changes manually to your metadata.
|
||||
|
||||
Start Nhost locally using the [CLI](/reference/cli). From the root of your app, run:
|
||||
|
||||
```sh
|
||||
```bash
|
||||
nhost -d
|
||||
```
|
||||
|
||||
> Running Nhost applies your local database migrations and Hasura metadata.
|
||||
:::tip
|
||||
Running Nhost applies your local database migrations and Hasura metadata.
|
||||
:::
|
||||
|
||||
### Restart Auth and Storage containers
|
||||
|
||||
@@ -70,7 +78,7 @@ Open Docker UI and restart Hasura Auth and Hasura Storage. Restarting those cont
|
||||
|
||||
Delete the local migrations and metadata.
|
||||
|
||||
```
|
||||
```bash
|
||||
rm -rf nhost/migrations nhost/metadata
|
||||
```
|
||||
|
||||
@@ -80,13 +88,17 @@ Update `config: 2` to `config: 3` in `nhost/config.yaml`.
|
||||
|
||||
### Pull migrations and metadata from our local instance
|
||||
|
||||
> 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.
|
||||
In the `nhost/` folder, run the following command:
|
||||
|
||||
```
|
||||
```bash
|
||||
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
|
||||
|
||||
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" }
|
||||
}
|
||||
5
docs/docs/platform/authentication/_category_.json
Normal file
5
docs/docs/platform/authentication/_category_.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"label": "Authentication",
|
||||
"position": 4,
|
||||
"link": { "id": "platform/authentication/index", "type": "doc" }
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 'Email templates'
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
The following emails can be sent as part of the authentication flow:
|
||||
@@ -17,7 +18,7 @@ If you have developed custom email templates, you must make them available over
|
||||
|
||||
Go to **Users -> Login settings** and scroll down to **Custom email templates**, and set the URL to where your templates are located:
|
||||
|
||||

|
||||

|
||||
|
||||
You only need to define the base URL to point to your hosted templates. The UI will give you a hint about where Nhost will look for your actual template files.
|
||||
|
||||
@@ -1,38 +1,37 @@
|
||||
---
|
||||
title: 'Overview'
|
||||
title: Authentication
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
Nhost provides a ready-to-use authentication service, integrated with Nhost JavaScript SDK. This makes it easy to build login flows with multiple login methods.
|
||||
# Authentication
|
||||
|
||||
---
|
||||
Nhost provides a ready-to-use authentication service, integrated with Nhost JavaScript SDK. This makes it easy to build login flows with multiple sign-in methods.
|
||||
|
||||
## Getting started
|
||||
## Getting Started
|
||||
|
||||
Using [Nhost JavaScript SDK](/reference/sdk), sign up a new user:
|
||||
Sign up a user with the [Nhost JavaScript SDK](/reference/sdk):
|
||||
|
||||
```js
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
import { NhostClient } from '@nhost/nhost-js';
|
||||
|
||||
const nhost = new NhostClient({
|
||||
backendUrl: 'https://[app-subdomain].nhost.run'
|
||||
})
|
||||
backendUrl: 'https://[app-subdomain].nhost.run',
|
||||
});
|
||||
|
||||
await nhost.auth.signUp({
|
||||
email: 'joe@nhost.io',
|
||||
password: 'secret-password'
|
||||
})
|
||||
password: 'secret-password',
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How it works
|
||||
|
||||
1. A user signs up and the user information is added to the `auth.users` table
|
||||
2. Nhost returns an access token (JWT token) with the user's information. The access token is cryptographically signed.
|
||||
1. A user signs up and the user information is added to the `auth.users` table.
|
||||
2. Nhost returns an [access token](#access-tokens) (JWT token) and the user's information.
|
||||
3. The user sends a request to the GraphQL API together with the access token.
|
||||
4. The GraphQL API reviews the access token to ensure the user is authorized to send the request.
|
||||
|
||||
Nhost's authentication service is integrated with your database. All users are saved in the app's database under the `auth` schema and can be accessed using GraphQL:
|
||||
Nhost's authentication service is integrated with your database. All users are stored in the app's database under the `auth` schema and can be accessed using GraphQL:
|
||||
|
||||
```graphql
|
||||
query {
|
||||
@@ -45,13 +44,11 @@ query {
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tokens
|
||||
|
||||
Nhost authentication uses two tokens: Access tokens and refresh tokens.
|
||||
|
||||
[Nhost JavaScript SDK](/sdk/) automatically handles access and refresh tokens.
|
||||
[Nhost JavaScript SDK](/reference/sdk) automatically handles access and refresh tokens.
|
||||
|
||||
### Access tokens
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "OAuth providers",
|
||||
"position": 3
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: Sign in with Facebook
|
||||
sidebar_position: 2
|
||||
slug: /platform/authentication/sign-in-with-facebook
|
||||
---
|
||||
|
||||
Follow this guide to sign in users with Facebook with your Nhost App.
|
||||
|
||||
<p align="center">
|
||||
<img
|
||||
alt="Facebook Sign In Preview"
|
||||
src="/img/social-providers/facebook-preview.svg"
|
||||
width={480}
|
||||
height={267}
|
||||
/>
|
||||
</p>
|
||||
|
||||
## Create Facebook account
|
||||
|
||||
- Create a new [Facebook account](https://www.facebook.com/) if you don't have one already.
|
||||
|
||||
## Create Facebook App
|
||||
|
||||
- Go to [Meta for Developers](https://developers.facebook.com/).
|
||||
- Click **My Apps** in the top right
|
||||
- Click **Create App** in the top right.
|
||||
- Select your **app type** (e.g. Consumer).
|
||||
- Click **Next**.
|
||||
- Fill in the **Display name.**
|
||||
- Click **Create app**.
|
||||
|
||||
## Set up Facebook Login
|
||||
|
||||
- Click on Add Product in the left menu.
|
||||
- Click on Setup in the Facebook login card.
|
||||
- **Don't** complete the quickstart. Instead, follow the next step.
|
||||
- Click on **Settings** under **Facebook Login** in the left menu.
|
||||
- Make sure **Embedded Browser OAuth Login** is set to **Yes**.
|
||||
- Fill in **Valid OAuth Redirect URIs** with your **OAuth Callback URL** from Nhost.
|
||||
- Click **Save changes**.
|
||||
|
||||
## Activate Facebook permissions and features
|
||||
|
||||
To make sure we can fetch all user data (email, profile picture and name). For that we need to enable **email** and **public_profile** permissions.
|
||||
|
||||
- Click on App Review and Permission and Features in the left menu
|
||||
- Search and for **email** in the **Search Permissions and Features** search box**.**
|
||||
- Click on Request advanced access and complete the steps.
|
||||
- Search and for **public_profile** in the **Search Permissions and Features** search box**.**
|
||||
- Click on **Request advanced access** and complete the steps.
|
||||
|
||||
## Configure Nhost
|
||||
|
||||
- Click **Settings** and then **Basic** in the left menu.
|
||||
- Copy and paste the **App ID (Client ID)** and **App secret (Client Secret)** from Facebook to your Nhost OAuth settings for Facebook. Make sure the [OAuth provider is enabled in Nhost](/platform/authentication/social-sign-in#enabling-social-sign-in-provider).
|
||||
- Click the checkbox “**I have pasted the redirect URI into Facebook”**.
|
||||
- Click **Confirm settings**.
|
||||
|
||||
## Sign In users in your app
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/sdk) to sign in users in your app:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'facebook',
|
||||
});
|
||||
```
|
||||
52
docs/docs/platform/authentication/oauth-providers/github.mdx
Normal file
52
docs/docs/platform/authentication/oauth-providers/github.mdx
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: Sign in with GitHub
|
||||
sidebar_position: 3
|
||||
slug: /platform/authentication/sign-in-with-github
|
||||
---
|
||||
|
||||
Follow this guide to sign in users with GitHub with your Nhost App.
|
||||
|
||||
<p align="center">
|
||||
<img
|
||||
alt="GitHub Sign In Preview"
|
||||
src="/img/social-providers/github-preview.svg"
|
||||
width={480}
|
||||
height={267}
|
||||
/>
|
||||
</p>
|
||||
|
||||
## Create GitHub account
|
||||
|
||||
- Create a new [GitHub account](https://github.com/signup) if you don't have one already.
|
||||
|
||||
## Create GitHub OAuth App
|
||||
|
||||
- Create a new OAuth application [(direct link)](https://github.com/settings/applications/new) by:
|
||||
- Click on your profile photo in the top right.
|
||||
- Click on Settings
|
||||
- In the left menu, click Developer settings at the bottom.
|
||||
- Click on Oauth Apps in the left menu
|
||||
- Click on New OAuth App button in the top right
|
||||
|
||||
## GitHub OAuth App information
|
||||
|
||||
- Fill in Application Name
|
||||
- Fill in Homepage URL
|
||||
- Fill in **Authorization callback URL** with your OAuth Callbacke URL from Nhost
|
||||
|
||||
## Configure Nhost
|
||||
|
||||
- Click Generate a new client secret to generate a OAuth client secret.
|
||||
- Copy and paste the **Client ID** and **Client Secret** from GitHub to your Nhost OAuth settings for GitHub. Make sure the [OAuth provider is enabled in Nhost](/platform/authentication/social-sign-in#enabling-social-sign-in-provider).
|
||||
- Click the checkbox “**I have pasted the redirect URI into GitHub”**.
|
||||
- Click **Confirm settings**.
|
||||
|
||||
## Sign In users in your app
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/sdk) to sign in users in your app:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'github',
|
||||
});
|
||||
```
|
||||
76
docs/docs/platform/authentication/oauth-providers/google.mdx
Normal file
76
docs/docs/platform/authentication/oauth-providers/google.mdx
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
title: Sign in with Google
|
||||
sidebar_position: 1
|
||||
slug: /platform/authentication/sign-in-with-google
|
||||
---
|
||||
|
||||
Follow this guide to sign in users with Google with your Nhost App.
|
||||
|
||||
<p align="center">
|
||||
<img
|
||||
alt="Google Sign In Preview"
|
||||
src="/img/social-providers/google-preview.svg"
|
||||
width={480}
|
||||
height={267}
|
||||
/>
|
||||
</p>
|
||||
|
||||
## Sign up for Google
|
||||
|
||||
- Sign up for [Google Cloud](https://cloud.google.com/free) if you don’t have one already.
|
||||
|
||||
## Create a Google Cloud Project
|
||||
|
||||
> 💡 You can skip this step if you already have a Google Cloud project you want to use.
|
||||
|
||||
- Create a new Google Cloud project if you don’t already have a project you want to use.
|
||||
|
||||
## Configure OAuth consent screen
|
||||
|
||||
- Search for **OAuth consent screen** in the top search bar in the Google Cloud Console.
|
||||
- Click on **OAuth consent screen** in the search results.
|
||||
- Select User Type **External** and click **CREATE**.
|
||||
|
||||
## **Edit app registration**
|
||||
|
||||
### OAuth consent screen
|
||||
|
||||
- Fill in your App information.
|
||||
- Click **SAVE AND CONTINUE.**
|
||||
|
||||
### Scopes
|
||||
|
||||
- Click **SAVE AND CONTINUE**.
|
||||
|
||||
### Test user
|
||||
|
||||
- Click **SAVE AND CONTINUE**.
|
||||
|
||||
### Summary
|
||||
|
||||
- Click **BACK TO DASHBOARD**.
|
||||
|
||||
## Create credentials
|
||||
|
||||
- Click on **Credentials** under **APIs & Services** in the left menu.
|
||||
- Click **+ CREATE CREDENTIALS** and then **OAuth client ID** in the top menu.
|
||||
- On the **Create OAuth client ID** page for **Application Type** select **Web application**.
|
||||
- Under **Authorized redirect URIs** add your **OAuth Callback URL** from Nhost.
|
||||
- Click **CREATE**.
|
||||
|
||||
## Configure Nhost
|
||||
|
||||
- A modal appears with your Google Client ID and Client secret.
|
||||
- Copy and paste the **Client ID** and **Client Secret** from Google to your Nhost OAuth settings for Google. Make sure the OAuth provider is enabled in Nhost.
|
||||
- Click the checkbox “**I have pasted the redirect URI into Google”**.
|
||||
- Click **Confirm settings**.
|
||||
|
||||
## Sign In users in your app
|
||||
|
||||
Use the Nhost JavaScript client to sign in users in your app:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'google',
|
||||
});
|
||||
```
|
||||
48
docs/docs/platform/authentication/oauth-providers/index.md
Normal file
48
docs/docs/platform/authentication/oauth-providers/index.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
title: 'OAuth Providers'
|
||||
slug: /platform/authentication/social-sign-in
|
||||
---
|
||||
|
||||
Nhost Auth supports the following social sign-in providers:
|
||||
|
||||
- [Google](/platform/authentication/sign-in-with-google)
|
||||
- [Facebook](/platform/authentication/sign-in-with-facebook)
|
||||
- [GitHub](/platform/authentication/sign-in-with-github)
|
||||
- [LinkedIn](/platform/authentication/sign-in-with-linkedin)
|
||||
- [Spotify](/platform/authentication/sign-in-with-spotify)
|
||||
|
||||
---
|
||||
|
||||
## Enabling Social Sign-In Provider
|
||||
|
||||
To start with social sign-in, select your app in Nhost Console and go to **Users** → **Login settings**.
|
||||
|
||||
You need to set client ID and client secret for each provider that you want to enable.
|
||||
|
||||
---
|
||||
|
||||
## Implementing sign-in experience
|
||||
|
||||
Use the [Nhost JavaScript SDK](/reference/sdk) and the `signIn()` method to implement social sign-in in your app,
|
||||
|
||||
Here's an example of how to implement sign-in with GitHub:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'github',
|
||||
});
|
||||
```
|
||||
|
||||
Users are redirected to your Nhost app's **client URL** by default. By default your Nhost app's client URL is set to `http://localhost:3000`. You can change the value of your client URL in the Nhost console by going to **Users** → **Login settings** → **Client URL**.
|
||||
|
||||
---
|
||||
|
||||
## Provider OAuth scopes
|
||||
|
||||
Scopes are a mechanism in OAuth to allow or limit an application's access to a user's account.
|
||||
|
||||
By default, Nhost sets the scope to get the name, email and avatar for each user. Editing scope is not currently supported.
|
||||
|
||||
## Provider OAuth Tokens
|
||||
|
||||
Nhost saves both access and refresh tokens for each user and provider in the `auth.user_providers` table. These tokens can be used to interact with the provider if needed.
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
title: Sign in with LinkedIn
|
||||
sidebar_position: 4
|
||||
slug: /platform/authentication/sign-in-with-linkedin
|
||||
---
|
||||
|
||||
Follow this guide to sign in users with LinkedIn with your Nhost App.
|
||||
|
||||
<p align="center">
|
||||
<img
|
||||
alt="LinkedIn Sign In Preview"
|
||||
src="/img/social-providers/linkedin-preview.svg"
|
||||
width={480}
|
||||
height={267}
|
||||
/>
|
||||
</p>
|
||||
|
||||
## Create LinkedIn account
|
||||
|
||||
- Create a [LinkedIn account](https://linkedin.com/) if you don't have one already.
|
||||
|
||||
## Create LinkedIn OAuth App
|
||||
|
||||
- Go to the [LinkedIn Developer Dashboard](https://www.linkedin.com/developers/apps).
|
||||
- Click on Create App in the top right.
|
||||
- Fill in **App Name**, **LinkedIn Page** and **App Logo**.
|
||||
- Click **“I have read and agree to these terms”**.
|
||||
- Click **Create app** in the bottom right.
|
||||
|
||||
## LinkedIn OAuth App information
|
||||
|
||||
- Click on **Auth** in the top menu.
|
||||
- Click on the **pen icon** under **OAuth 2.0 settings** and right next to **Authorized redirect URLs for your app.**
|
||||
- Click **Add redirect URL**.
|
||||
- Copy and past the **OAuth Callback URL** from Nhost.
|
||||
- Click **Update**.
|
||||
|
||||
## Configure Nhost
|
||||
|
||||
- Copy and paste the **Client ID** and **Client Secret** from LinkedIn to your Nhost OAuth settings for LinkedIn.
|
||||
- Click the checkbox “**I have pasted the redirect URI into LinkedIn”**.
|
||||
- Click **Confirm settings**.
|
||||
|
||||
## Enable Auth for your LinkedIn OAuth App
|
||||
|
||||
- Click on **Products** in the top menu
|
||||
- Click Select on the **Sign In with LinkedIn**.
|
||||
- Check the checkbox **I have read and agree to these terms.**
|
||||
- Click **Add product**.
|
||||
|
||||
## Sign In users in your app
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/sdk) to sign in users in your app:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'linkedin',
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: Sign in with Spotify
|
||||
sidebar_position: 5
|
||||
slug: /platform/authentication/sign-in-with-spotify
|
||||
---
|
||||
|
||||
Follow this guide to sign in users with Spotify with your Nhost App.
|
||||
|
||||
<p align="center">
|
||||
<img
|
||||
alt="Spotify Sign In Preview"
|
||||
src="/img/social-providers/spotify-preview.svg"
|
||||
width={480}
|
||||
height={267}
|
||||
/>
|
||||
</p>
|
||||
|
||||
## Create Spotify account
|
||||
|
||||
- Create a new [Spotify account](https://www.spotify.com/) if you don't have one already.
|
||||
|
||||
## Create Spotify App
|
||||
|
||||
- Go to the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard/).
|
||||
- Click on CREATE AN APP.
|
||||
- Fill in a App name and App description
|
||||
- Check the box to aggre Spotify's [Developer Terms of Service](https://developer.spotify.com/terms) and [Branding Guidelines](https://developer.spotify.com/branding-guidelines).
|
||||
|
||||
## Configure OAuth Callback URL
|
||||
|
||||
- Click EDIT SETTINGS
|
||||
- A modal appears
|
||||
- Fill in **Redirect URIs** with your **OAuth Callback URL** from Nhost.
|
||||
- Click ADD to add the OAuth callback URL.
|
||||
- Click SAVE.
|
||||
|
||||
## Configure Nhost
|
||||
|
||||
- Click SHOW CLIENT SECRET in the Spotify App Dashboard.
|
||||
- Copy and paste the **Client ID** and **Client Secret** from Spotify to your Nhost OAuth settings for Spotify.
|
||||
- Click the checkbox “**I have pasted the redirect URI into Spotify”**.
|
||||
- Click **Confirm settings**.
|
||||
|
||||
## Sign In users in your app
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/sdk) to sign in users in your app:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'spotify',
|
||||
});
|
||||
```
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 'Sign-in methods'
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
Nhost supports a variety of sign-in methods:
|
||||
@@ -13,8 +14,8 @@ To sign in a user with email and password, the user must first sign up:
|
||||
```js
|
||||
await nhost.auth.signUp({
|
||||
email: 'joe@nhost.io',
|
||||
password: 'secret-password'
|
||||
})
|
||||
password: 'secret-password',
|
||||
});
|
||||
```
|
||||
|
||||
If you've turned on email verification in your app's **login settings**, a user will be sent a verification email upon signup. The user must click the verification link in the email before they can log in.
|
||||
@@ -24,8 +25,8 @@ Once a user has been signed up (and optionally verified), you can sign them in:
|
||||
```js
|
||||
await nhost.auth.signIn({
|
||||
email: 'joe@nhost.io',
|
||||
password: 'secret-password'
|
||||
})
|
||||
password: 'secret-password',
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
@@ -42,8 +43,8 @@ Example in JavaScript:
|
||||
|
||||
```js
|
||||
await nhost.auth.signIn({
|
||||
email: 'joe@nhost.io'
|
||||
})
|
||||
email: 'joe@nhost.io',
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
@@ -56,8 +57,8 @@ First, "sign in" the user with a phone number.
|
||||
|
||||
```js
|
||||
await nnhost.auth.signIn({
|
||||
phoneNumber: '+467610337135'
|
||||
})
|
||||
phoneNumber: '+467610337135',
|
||||
});
|
||||
```
|
||||
|
||||
This will create the user if the user does not already exist, and send a One Time Password (OTP) to the user's
|
||||
@@ -68,8 +69,8 @@ Use the OTP to finalize the sign-in:
|
||||
```js
|
||||
await nhost.auth.signIn({
|
||||
phoneNumber: '+467610337135',
|
||||
otp: 'otp-from-sms'
|
||||
})
|
||||
otp: 'otp-from-sms',
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
@@ -78,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.
|
||||
|
||||
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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 'User management'
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
Users are saved in the database in the `auth.users` table and are accessible via the GraphQL API.
|
||||
@@ -24,7 +25,7 @@ Example of getting one user in GraphQL:
|
||||
|
||||
```graphql
|
||||
query {
|
||||
user(id: "some-user-id") {
|
||||
user(id: "<user-id>") {
|
||||
id
|
||||
displayName
|
||||
email
|
||||
@@ -66,7 +67,7 @@ If the user is not signed in, the `public` role will be used.
|
||||
When no request role is specified, the user's default role will be used:
|
||||
|
||||
```js
|
||||
await nhost.graphql.request(QUERY, {})
|
||||
await nhost.graphql.request(QUERY, {});
|
||||
```
|
||||
|
||||
Make a GraphQL request with the `me` role:
|
||||
@@ -77,10 +78,10 @@ await nhost.graphql.request(
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
'x-hasura-role': 'me'
|
||||
}
|
||||
}
|
||||
)
|
||||
'x-hasura-role': 'me',
|
||||
},
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
If the request is not part of the user's roles, the request will fail.
|
||||
4
docs/docs/platform/database/_category_.json
Normal file
4
docs/docs/platform/database/_category_.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Database",
|
||||
"position": 2
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 'Schema'
|
||||
title: 'Database'
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
Every Nhost app comes with a Postgres database. Postgres is the world's most advanced open-source relational database and the most popular SQL database among developers. The database is hosted with Amazon RDS.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user