Compare commits
491 Commits
@nhost/rea
...
@nhost/nex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6777738c53 | ||
|
|
0d60693c27 | ||
|
|
c159c9c98c | ||
|
|
58fa2a201c | ||
|
|
db4607ccac | ||
|
|
95b14557a0 | ||
|
|
8b527d0fcb | ||
|
|
fc50beec5e | ||
|
|
ed0de2d930 | ||
|
|
f26e8c3614 | ||
|
|
a05a484426 | ||
|
|
4f0d5aa9c0 | ||
|
|
254e362e95 | ||
|
|
a2a8839694 | ||
|
|
d12667ccc5 | ||
|
|
feb55fe0ad | ||
|
|
b97c0a9c9d | ||
|
|
2e98934f75 | ||
|
|
8cedafc807 | ||
|
|
322b433994 | ||
|
|
6082ba6943 | ||
|
|
1fb1d25a72 | ||
|
|
ae4e4e50f6 | ||
|
|
185f39e23f | ||
|
|
0dd7cab3bf | ||
|
|
47c7380d89 | ||
|
|
b56162a74b | ||
|
|
aeb8b8afda | ||
|
|
f9e107b008 | ||
|
|
d748d82483 | ||
|
|
2192fdc92e | ||
|
|
eec2601a3a | ||
|
|
93eaa85b47 | ||
|
|
4c8a168c02 | ||
|
|
eb166cf5ee | ||
|
|
54e1873461 | ||
|
|
e5bc3b356c | ||
|
|
42edb74057 | ||
|
|
3ebeae9294 | ||
|
|
24189bd155 | ||
|
|
5985b18764 | ||
|
|
95efcb4de6 | ||
|
|
609d9001f5 | ||
|
|
1e44a14b8a | ||
|
|
5917eff5a6 | ||
|
|
f8ee87ad01 | ||
|
|
a7990b363f | ||
|
|
ca8ecb4b5c | ||
|
|
dfe080b8f6 | ||
|
|
069a5d4d9a | ||
|
|
efccd54641 | ||
|
|
a3357265ec | ||
|
|
494d8127dc | ||
|
|
57b628a255 | ||
|
|
98b30a5c5e | ||
|
|
48afcf415d | ||
|
|
58b9488af8 | ||
|
|
e49528d43e | ||
|
|
ab0f5582e5 | ||
|
|
b499548503 | ||
|
|
fbc15cfee1 | ||
|
|
8788de83e8 | ||
|
|
1fb51a7bed | ||
|
|
365b806755 | ||
|
|
74025a2d90 | ||
|
|
ebd6f86ea3 | ||
|
|
7b23d33d9b | ||
|
|
de177710f7 | ||
|
|
eebfddb48e | ||
|
|
f5df7eaa2d | ||
|
|
0b4028b1d6 | ||
|
|
f2da21026b | ||
|
|
a8233ea621 | ||
|
|
f300d8b9f1 | ||
|
|
3b625acd96 | ||
|
|
116e23cb13 | ||
|
|
954aa43e49 | ||
|
|
de6f862c4c | ||
|
|
5025333fb4 | ||
|
|
b3991ea9d9 | ||
|
|
239db617f3 | ||
|
|
6d6c8b3e2e | ||
|
|
169f163c99 | ||
|
|
5ddd7eda22 | ||
|
|
00d50f404a | ||
|
|
b52b4fca2d | ||
|
|
56b1adfa81 | ||
|
|
9f55f4890e | ||
|
|
284890b437 | ||
|
|
1e3cb855f0 | ||
|
|
aaf575f0af | ||
|
|
ff3427ccdb | ||
|
|
3834d80a0a | ||
|
|
8fcc3fd560 | ||
|
|
616e320421 | ||
|
|
ff8fdc4db4 | ||
|
|
b732bc51e8 | ||
|
|
8b88d6c553 | ||
|
|
af192fbb66 | ||
|
|
b7c031d7e0 | ||
|
|
05e91d93a8 | ||
|
|
261f001a99 | ||
|
|
ddb965c7cf | ||
|
|
ff8af4912b | ||
|
|
948aebcf42 | ||
|
|
51a4d7a343 | ||
|
|
d521218146 | ||
|
|
9a1685c2eb | ||
|
|
fb8f58503f | ||
|
|
86409c2f18 | ||
|
|
f7d8c061a0 | ||
|
|
eb59a07c3f | ||
|
|
27add80f95 | ||
|
|
cc60a845f3 | ||
|
|
aeea44e4e8 | ||
|
|
d273825cee | ||
|
|
177733f4b1 | ||
|
|
efd33a7115 | ||
|
|
e4fe166992 | ||
|
|
d9f221b625 | ||
|
|
e0f5bf3396 | ||
|
|
17bd23460f | ||
|
|
52c30c25a9 | ||
|
|
989bc54544 | ||
|
|
5781cb0f9c | ||
|
|
24f9ed4c4d | ||
|
|
2b447af8a7 | ||
|
|
2485c83de3 | ||
|
|
707877477f | ||
|
|
756e1af52d | ||
|
|
7f771e2e8f | ||
|
|
a737863c2b | ||
|
|
ca2a61089a | ||
|
|
7432db0fe8 | ||
|
|
9c25e65df6 | ||
|
|
e345895fd8 | ||
|
|
cba7996dcf | ||
|
|
d7ceda6ae6 | ||
|
|
4e19b7309d | ||
|
|
de8a7d5512 | ||
|
|
7e7aa5adc4 | ||
|
|
1ed948952a | ||
|
|
55b0a8f4b9 | ||
|
|
1ce55c5568 | ||
|
|
fabd1da04c | ||
|
|
53f5226d0c | ||
|
|
e339cba384 | ||
|
|
c14d12e04d | ||
|
|
03370da2f4 | ||
|
|
87d32b2639 | ||
|
|
61b5fb549c | ||
|
|
28cd8dc5c5 | ||
|
|
fb93d8c1af | ||
|
|
f4e6aea9c1 | ||
|
|
65d61cf4b8 | ||
|
|
a8d5214b2f | ||
|
|
f9e5283fc3 | ||
|
|
d49b837abb | ||
|
|
49545c058b | ||
|
|
765340f7b2 | ||
|
|
5a212aaa12 | ||
|
|
2c3d9b11a6 | ||
|
|
79056d8b48 | ||
|
|
b1678eaad3 | ||
|
|
f1c16dba6e | ||
|
|
f86883df88 | ||
|
|
aa1fdf6c2c | ||
|
|
bebf9e1f2b | ||
|
|
2413c10283 | ||
|
|
0f7fbdab97 | ||
|
|
14e5fd63a6 | ||
|
|
2446913836 | ||
|
|
1f88a9f47a | ||
|
|
261e37cda4 | ||
|
|
5ee395ea8e | ||
|
|
e5f00394ae | ||
|
|
dd08aef4cc | ||
|
|
4ab85815a0 | ||
|
|
828633ffc9 | ||
|
|
1b0902079b | ||
|
|
7b7527a5e6 | ||
|
|
f719d47ed3 | ||
|
|
8cc88533b1 | ||
|
|
71de2bd0c5 | ||
|
|
66d204331b | ||
|
|
ee288fbc5f | ||
|
|
620566fa4d | ||
|
|
1a38b5dca3 | ||
|
|
e0bd8cf14b | ||
|
|
4ce8b88d27 | ||
|
|
28d25e46de | ||
|
|
e0cfcafead | ||
|
|
12bc30daa3 | ||
|
|
7b5f00d10e | ||
|
|
58e1485c13 | ||
|
|
1da0ff9109 | ||
|
|
a64f1c4396 | ||
|
|
75a1428114 | ||
|
|
d82d830849 | ||
|
|
2def59fc6c | ||
|
|
64ceb2c6bf | ||
|
|
3ee007620c | ||
|
|
c08230ae95 | ||
|
|
18df8921fd | ||
|
|
b9cf8172a0 | ||
|
|
32edfb4a9f | ||
|
|
848db9b672 | ||
|
|
3766921bcc | ||
|
|
5546052b2c | ||
|
|
c569b56d3d | ||
|
|
52ffa84adb | ||
|
|
b5ae438a8e | ||
|
|
fae05f7af2 | ||
|
|
380d7fc8ce | ||
|
|
94132bbc7f | ||
|
|
d87a9d7c79 | ||
|
|
be7756d4a2 | ||
|
|
ca5e335bff | ||
|
|
b9ed794f2b | ||
|
|
042dc7d27c | ||
|
|
db2df3d5b2 | ||
|
|
0b1cb628f2 | ||
|
|
912d95d153 | ||
|
|
76059f4738 | ||
|
|
011572f3ef | ||
|
|
b727b354dd | ||
|
|
a0682ed22e | ||
|
|
4d16306e56 | ||
|
|
b7861bbd36 | ||
|
|
e279805896 | ||
|
|
e3ebd9cb1b | ||
|
|
5bb928da2c | ||
|
|
ab06e96eac | ||
|
|
6e2aabbda0 | ||
|
|
e4ce235f38 | ||
|
|
e783b7478b | ||
|
|
06d2d2b0c7 | ||
|
|
656379e78b | ||
|
|
4156a9a61e | ||
|
|
0b72829274 | ||
|
|
6b0baab151 | ||
|
|
93f9d2d01d | ||
|
|
3fb3d4c282 | ||
|
|
ccba0b5015 | ||
|
|
62e331500d | ||
|
|
4104ddbcb6 | ||
|
|
43fc040a29 | ||
|
|
e472b2cb19 | ||
|
|
6570a940ee | ||
|
|
d3e97c87d6 | ||
|
|
36508c7930 | ||
|
|
709d364749 | ||
|
|
73eb2db159 | ||
|
|
86eb8903dc | ||
|
|
d665473074 | ||
|
|
58534c24f0 | ||
|
|
90a1c3b9e1 | ||
|
|
bdfa2b3053 | ||
|
|
2c5b31f27a | ||
|
|
d75fd747e0 | ||
|
|
a71b3aff59 | ||
|
|
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 | ||
|
|
63ee1d7659 | ||
|
|
eb33952760 | ||
|
|
b2c398df22 | ||
|
|
888192282f | ||
|
|
945b557dea | ||
|
|
4031d8a9e1 | ||
|
|
c77aa16181 | ||
|
|
ea2fb2e9a4 | ||
|
|
e147487e27 | ||
|
|
6f52652e10 | ||
|
|
1a8d9b5c28 | ||
|
|
9111299ddd | ||
|
|
1c7f520073 | ||
|
|
dff37a4cd0 | ||
|
|
96a572379e | ||
|
|
da3bbf2e10 | ||
|
|
a11fa372ff | ||
|
|
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 | ||
|
|
4235eb812e | ||
|
|
ed145234b2 | ||
|
|
7c2597ddc7 | ||
|
|
2b1f8182f2 | ||
|
|
ded9e7637a | ||
|
|
c42fb85bae | ||
|
|
33edc4291b | ||
|
|
df89d804c5 | ||
|
|
89da44d715 | ||
|
|
789faad645 | ||
|
|
8c7267cbee | ||
|
|
96c12ffff1 | ||
|
|
783729a6f6 | ||
|
|
60d4dbabdf | ||
|
|
a77ddcdbc2 | ||
|
|
f4c8a776a4 | ||
|
|
efbaf08483 |
@@ -18,5 +18,7 @@ module.exports = {
|
||||
useTabs: false
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
importOrderSeparation: true,
|
||||
importOrderSortSpecifiers: true
|
||||
}
|
||||
|
||||
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -2,6 +2,7 @@
|
||||
# https://help.github.com/en/articles/about-code-owners
|
||||
|
||||
/packages @plmercereau
|
||||
/packages/docgen @szilarddoro
|
||||
/.github/workflows @plmercereau
|
||||
/docs/ @guicurcio
|
||||
/examples/ @plmercereau @guicurcio @FuzzyReason
|
||||
|
||||
28
.github/workflows/changesets.yaml
vendored
28
.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.32.3
|
||||
run_install: true
|
||||
|
||||
# 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 }}
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -15,7 +15,6 @@ logs/
|
||||
/.vscode/
|
||||
.eslintcache
|
||||
.yarnclean
|
||||
.husky
|
||||
|
||||
# Directories
|
||||
coverage/
|
||||
@@ -50,4 +49,8 @@ todo.md
|
||||
.netlify
|
||||
.monorepo-example
|
||||
|
||||
.next
|
||||
.next
|
||||
|
||||
# TypeDoc output
|
||||
|
||||
.docgen
|
||||
1
.husky/.gitignore
vendored
Normal file
1
.husky/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
_
|
||||
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
6
.lintstagedrc.json
Normal file
6
.lintstagedrc.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"packages/(docgen|hasura-auth-js|hasura-storage-js|nextjs|nhost-js|react|core)/src/**/*.{js,ts,jsx,tsx}": [
|
||||
"pnpm docgen",
|
||||
"git add docs"
|
||||
]
|
||||
}
|
||||
@@ -24,6 +24,8 @@ If you find an Issue that addresses the problem you're having, please add your r
|
||||
|
||||
### Pull Requests
|
||||
|
||||
Please have a look at our [developers guide](https://github.com/nhost/nhost/blob/main/DEVELOPERS.md) to start coding!
|
||||
|
||||
PRs to our libraries are always welcome and can be a quick way to get your fix or improvement slated for the next release. In general, PRs should:
|
||||
|
||||
- Only fix/add the functionality in question **OR** address wide-spread whitespace/style issues, not both.
|
||||
|
||||
79
DEVELOPERS.md
Normal file
79
DEVELOPERS.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# Developer guide
|
||||
|
||||
## Requirements
|
||||
|
||||
- We use [pnpm](https://pnpm.io/) as a package manager to fasten development and builds, and as a backbone to our monorepo. You have to make sure it is installed in your machine. There are [multiple ways to install it](https://pnpm.io/installation), but the easiest way is with `npm`:
|
||||
|
||||
```sh
|
||||
npm install -g pnpm
|
||||
```
|
||||
|
||||
- Our tests and examples are using the Nhost CLI to run the backend services locally. You can follow the instructions to install it in [our documentation](https://docs.nhost.io/get-started/cli-workflow/install-cli)
|
||||
|
||||
## Getting things ready
|
||||
|
||||
First, clone this repository:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/nhost/nhost
|
||||
```
|
||||
|
||||
Then, install the dependencies with `pnpm`:
|
||||
|
||||
```sh
|
||||
cd nhost
|
||||
pnpm install
|
||||
```
|
||||
|
||||
## Starting development from an example
|
||||
|
||||
Let's follow the instructions to start [react-apollo example](https://github.com/nhost/nhost/blob/main/examples/react-apollo/README.md).
|
||||
|
||||
## Running the documentation website locally
|
||||
|
||||
The easier way to contribute to our documentation is to move to the `docs` folder and follow the [instructions to start local development](https://github.com/nhost/nhost/blob/main/docs/README.md):
|
||||
|
||||
```sh
|
||||
cd docs
|
||||
# not necessary step if you've done this step already anywhere in the repository
|
||||
pnpm install
|
||||
pnpm start
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
In order to run tests, the Nhost testing backend should run locally. You can start it in from a separate terminal:
|
||||
|
||||
```sh
|
||||
cd examples/testing-project
|
||||
nhost -d
|
||||
```
|
||||
|
||||
Once Nhost started locally, you can run the tests from the root folder of the repository with the following command:
|
||||
|
||||
```sh
|
||||
pnpm test
|
||||
```
|
||||
|
||||
## Changesets
|
||||
|
||||
If you made some changes in the packages, you will have to describe these changes so they will be taken into account in the next release.
|
||||
We are using [changesets](https://github.com/changesets/changesets) to support our version/publish workflows. When submitting a pull request, a bot will check if some changesets are present, and if not, will guide you to add them.
|
||||
|
||||
The most comprehensive way to add a changeset is to run the following command from the root directory of the repository:
|
||||
|
||||
```sh
|
||||
pnpm changeset
|
||||
```
|
||||
|
||||
This will generate a file in the `.changeset` directory. You can edit it to give further details about the change you just made.
|
||||
You can have a look at the changesets documentation on [how to add a changeset](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md)
|
||||
|
||||
## Commiting changes
|
||||
|
||||
You may notice `git commit` takes a few seconds to run. We set a commit hook that scan the changes in the code, automatically generates the documentation from the inline [TSDoc](https://tsdoc.org/) annotations, and add these generated documentation files to the commit. They will automatically update the [reference documentation](https://docs.nhost.io/reference).
|
||||
|
||||
<!-- ## Good practices
|
||||
- lint
|
||||
- prettier
|
||||
- documentation -->
|
||||
242
README.md
242
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** built with the following things in mind:
|
||||
**Nhost is an open-source GraphQL backend,** built with the following things in mind:
|
||||
|
||||
- Open Source
|
||||
- Open-Source
|
||||
- Developer Productivity
|
||||
- SQL
|
||||
- GraphQL
|
||||
@@ -36,6 +36,8 @@ 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"/>
|
||||
@@ -96,7 +98,7 @@ Nhost is frontend agnostic, which means Nhost works with all frontend frameworks
|
||||
|
||||
Nhost libraries and tools
|
||||
|
||||
- [JavaScript/TypeScript SDK](https://docs.nhost.io/reference/sdk)
|
||||
- [JavaScript/TypeScript SDK](https://docs.nhost.io/reference/javascript)
|
||||
- [Dart and Flutter SDK](https://github.com/nhost/nhost-dart)
|
||||
- [Nhost CLI](https://docs.nhost.io/reference/cli)
|
||||
- [Nhost React](https://docs.nhost.io/reference/react)
|
||||
@@ -108,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.
|
||||
|
||||
@@ -118,8 +120,226 @@ Here are some ways of contributing to making Nhost better:
|
||||
|
||||
- **[Try out Nhost](https://docs.nhost.io/get-started/quick-start)**, and think of ways to make the service better. Let us know here on GitHub.
|
||||
- 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!
|
||||
- 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) and our [developers guide](https://github.com/nhost/nhost/blob/main/DEVELOPERS.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/dbarrosop">
|
||||
<img src="https://avatars.githubusercontent.com/u/6246622?v=4" width="100;" alt="dbarrosop"/>
|
||||
<br />
|
||||
<sub><b>David Barroso</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<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/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/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/timpratim">
|
||||
<img src="https://avatars.githubusercontent.com/u/32492961?v=4" width="100;" alt="timpratim"/>
|
||||
<br />
|
||||
<sub><b>Pratim</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<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>
|
||||
<td align="center">
|
||||
<a href="https://github.com/heygambo">
|
||||
<img src="https://avatars.githubusercontent.com/u/449438?v=4" width="100;" alt="heygambo"/>
|
||||
<br />
|
||||
<sub><b>Christian Gambardella</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<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></tr>
|
||||
<tr>
|
||||
<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/jladuval">
|
||||
<img src="https://avatars.githubusercontent.com/u/1935359?v=4" width="100;" alt="jladuval"/>
|
||||
<br />
|
||||
<sub><b>Jacob Duval</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>
|
||||
<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 -->
|
||||
|
||||
@@ -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,46 +1,37 @@
|
||||
# Nhost Documentation
|
||||
# Nhost Docs
|
||||
|
||||
## Get started
|
||||
This documentation describes how to build, start and test the documentation locally.
|
||||
|
||||
From the **root** of the `nhost/nhost` repository:
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
pnpm run clean:all
|
||||
pnpm i
|
||||
cd docs
|
||||
pnpm run dev
|
||||
$ pnpm i
|
||||
```
|
||||
|
||||
### Local Development
|
||||
|
||||
## 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 start
|
||||
```
|
||||
|
||||
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.
|
||||
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
||||
|
||||
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"]`
|
||||
### Build
|
||||
|
||||
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.
|
||||
```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 my-10 ">
|
||||
<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,90 +0,0 @@
|
||||
---
|
||||
title: 'Authenticate users'
|
||||
---
|
||||
|
||||
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.
|
||||
|
||||
> Nhost's authentication service lets you deliver frictionless registration and login experiences to your users. We support most social providers and different methods such as email & password and passwordless (magic link).
|
||||
|
||||
---
|
||||
|
||||
## Insert a test user
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Authenticate and query data
|
||||
|
||||
Add the following code to sign in the new user and request the list of todos again:
|
||||
|
||||
```js
|
||||
import { NhostClient } from '@nhost/nhost-js'
|
||||
|
||||
const nhost = new NhostClient({
|
||||
backendUrl: 'https://[app-subdomain].nhost.run'
|
||||
})(async () => {
|
||||
// Sign in user
|
||||
const signInResponse = await nhost.auth.signIn({
|
||||
email: 'joe@example.com',
|
||||
password: 'securepassword'
|
||||
})
|
||||
|
||||
// Handle sign-in error
|
||||
if (signInResponse.error) {
|
||||
throw signInResponse.error
|
||||
}
|
||||
|
||||
// Get todos
|
||||
const todos = await nhost.graphql.request(`
|
||||
query {
|
||||
todos {
|
||||
id
|
||||
created_at
|
||||
name
|
||||
is_completed
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
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.
|
||||
|
||||
> For authenticated requests, there is always the option to override the default `user` role with any other valid role.
|
||||
|
||||
---
|
||||
|
||||
## Permissions for users
|
||||
|
||||
### Remove permissions for the public role
|
||||
|
||||
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.
|
||||
|
||||
> All logged-in users have the `user` role.
|
||||
|
||||
### Insert permission
|
||||
|
||||
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!
|
||||
@@ -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,9 +0,0 @@
|
||||
---
|
||||
title: 'Overview'
|
||||
---
|
||||
|
||||
Documentation for other platform features:
|
||||
|
||||
- [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,40 +0,0 @@
|
||||
---
|
||||
title: 'Protecting routes'
|
||||
---
|
||||
|
||||
Create a `auth-protected.js` file:
|
||||
|
||||
```jsx
|
||||
import { useRouter } from 'next/router'
|
||||
import { useAuthenticationStatus } from '@nhost/nextjs'
|
||||
|
||||
export function authProtected(Comp) {
|
||||
return function AuthProtected(props) {
|
||||
const router = useRouter()
|
||||
const { isLoading, isAuthenticated } = useAuthenticationStatus()
|
||||
|
||||
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,466 +0,0 @@
|
||||
---
|
||||
title: 'Hooks'
|
||||
---
|
||||
|
||||
## Authentication Hooks
|
||||
|
||||
### Email and Password Sign-Un
|
||||
|
||||
```js
|
||||
const { signUpEmailPassword, isLoading, isSuccess, needsEmailVerification, isError, error } =
|
||||
useSignUpEmailPassword(email?: string, password?: string, options?: Options )
|
||||
```
|
||||
|
||||
| Name | Type | Notes |
|
||||
| ------------------------ | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `signUpEmailPassword` | (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. |
|
||||
| `needsEmailVerification` | 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 { useSignUpEmailPassword } from '@nhost/react'
|
||||
|
||||
const Component = () => {
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const { signUpEmailPassword, isLoading, isSuccess, needsEmailVerification, isError, error } =
|
||||
useSignUpEmailPassword(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={signUpEmailPassword}>Register</button>
|
||||
{isSuccess && <div>Your account have beed created! You are now authenticated</div>}
|
||||
{needsEmailVerification && (
|
||||
<div>Please check your mailbox and follow the verification link to verify your email</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Email and Password Sign-In
|
||||
|
||||
```js
|
||||
const { signInEmailPassword, isLoading, needsEmailVerification, needsMfaOtp, sendMfaOtp, isSuccess, isError, error } =
|
||||
useSignInEmailPassword(email?: string, password?: string)
|
||||
```
|
||||
|
||||
| Name | Type | Notes |
|
||||
| ------------------------ | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `signInEmailPassword` | (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. |
|
||||
| `needsEmailVerification` | boolean | Returns `true` if the user email is still pending email verification. |
|
||||
| `needsMfaOtp` | boolean | Returns `true` if the server is awaiting an MFA one-time password to complete the authentication. |
|
||||
| `sendMfaOtp` | (otp: string) => void | Sends MFA One-time password. Will turn either `isSuccess` or `isError` to true, and store potential error in `error`. |
|
||||
| `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 { useSignInEmailPassword } from '@nhost/react'
|
||||
|
||||
const Component = () => {
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const { signInEmailPassword, isLoading, isSuccess, needsEmailVerification, isError, error } =
|
||||
useSignInEmailPassword(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={signInEmailPassword}>Register</button>
|
||||
{isSuccess && <div>Authentication suceeded</div>}
|
||||
{needsEmailVerification && (
|
||||
<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
|
||||
const providerLink = useProviderLink(options?: Options)
|
||||
```
|
||||
|
||||
| Name | Type | Notes |
|
||||
| ---------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `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
|
||||
|
||||
```js
|
||||
import { useProviderLink } from '@nhost/react'
|
||||
|
||||
const Component = () => {
|
||||
const { facebook, github } = useProviderLink()
|
||||
return
|
||||
;<div>
|
||||
<a href={facebook}>Authenticate with Facebook</a>
|
||||
<a href={github}>Authenticate with GitHub</a>
|
||||
</div>
|
||||
}
|
||||
```
|
||||
|
||||
### Passwordless email authentication
|
||||
|
||||
```js
|
||||
const { signInEmailPasswordless, isLoading, isSuccess, isError, error } =
|
||||
useSignInEmailPasswordless(email?: string, options?: Options)
|
||||
```
|
||||
|
||||
| Name | Type | Notes |
|
||||
| ------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `signInEmailPasswordless` | (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 { useSignInEmailPasswordless } from '@nhost/react'
|
||||
|
||||
const Component = () => {
|
||||
const [email, setEmail] = useState('')
|
||||
const { signInEmailPasswordless, isLoading, isSuccess, isError, error } =
|
||||
useSignInEmailPasswordless(email)
|
||||
return (
|
||||
<div>
|
||||
<input value={email} onChange={(event) => setEmail(event.target.value)} placeholder="Email" />
|
||||
<button onClick={signInEmailPasswordless}>Authenticate</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
|
||||
|
||||
### `useAuthenticationStatus`
|
||||
|
||||
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.
|
||||
|
||||
`isLoading` will return `true` until the authentication status is known.
|
||||
|
||||
#### Usage
|
||||
|
||||
```jsx
|
||||
import { useAuthenticationStatus } from '@nhost/react'
|
||||
|
||||
const Component = () => {
|
||||
const { isLoading, isAuthenticated } = useAuthenticationStatus()
|
||||
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 ellaborate -->
|
||||
|
||||
```js
|
||||
const accessToken = useAccessToken()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## User management
|
||||
|
||||
### Change email
|
||||
|
||||
```js
|
||||
const { changeEmail, isLoading, isSuccess, needsEmailVerification, isError, error } =
|
||||
useChangeEmail(email?: string, options?: { redirectTo?: string })
|
||||
```
|
||||
|
||||
| Name | Type | Notes |
|
||||
| ------------------------ | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `changeEmail` | (email?: string) => void | Requests 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. |
|
||||
| `needsEmailVerification` | 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, needsEmailVerification, isError, error } =
|
||||
useChangeEmail(password)
|
||||
return (
|
||||
<div>
|
||||
<input value={email} onChange={(event) => setEmail(event.target.value)} />
|
||||
<button onClick={changeEmail}>Change password</button>
|
||||
{needsEmailVerification && (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Send email verification
|
||||
|
||||
```js
|
||||
const { sendEmail, isLoading, isSent, isError, error } =
|
||||
useSendVerificationEmail(email?: string, options?: { redirectTo?: string })
|
||||
```
|
||||
|
||||
| Name | Type | Notes |
|
||||
| ------------ | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `sendEmail` | (email?: string) => void | Requests 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. |
|
||||
| `isSent` | boolean | Returns `true` if the verification email has been 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 { useChangeEmail } from '@nhost/react'
|
||||
|
||||
const Component = () => {
|
||||
const [email, setEmail] = useState('')
|
||||
const { sendEmail, isLoading, isSent, isError, error } = useChangeEmail(password)
|
||||
return (
|
||||
<div>
|
||||
<input value={email} onChange={(event) => setEmail(event.target.value)} />
|
||||
<button onClick={sendEmail}>Send email verification</button>
|
||||
{isSent && (
|
||||
<div>Please check your mailbox and follow the verification link to confirm your email</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## User data
|
||||
|
||||
<!-- TODO ellaborate -->
|
||||
|
||||
```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,68 +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 `NhostReactProvider` 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 { NhostClient, NhostReactProvider } from '@nhost/react'
|
||||
|
||||
import App from './App'
|
||||
|
||||
const nhost = new NhostClient({
|
||||
backendUrl: 'http://localhost:1337'
|
||||
})
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<NhostReactProvider nhost={nhost}>
|
||||
<App />
|
||||
</NhostReactProvider>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Options
|
||||
|
||||
```js
|
||||
const nhost = new NhostClient({
|
||||
backendUrl,
|
||||
autoLogin,
|
||||
autoRefreshToken,
|
||||
clientStorageGetter,
|
||||
clientStorageSetter
|
||||
})
|
||||
```
|
||||
|
||||
| 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` |
|
||||
| `autoLogin` | 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. |
|
||||
| `clientStorageGetter` | (key:string) => string \| null | use localStorage | Nhost stores a refresh token in `localStorage` so the session can be restored when starting the browser. |
|
||||
| `clientStorageGetter` | (key: string, value: string \| null | use localStorage | |
|
||||
| `refreshIntervalTime` | | |
|
||||
@@ -1,43 +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 { useAuthenticationStatus } from '@nhost/react'
|
||||
|
||||
export function AuthGate(children) {
|
||||
const { isLoading, isAuthenticated } = useAuthenticationStatus()
|
||||
|
||||
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,134 +0,0 @@
|
||||
---
|
||||
title: 'Authentication'
|
||||
---
|
||||
|
||||
## nhost.auth.signIn()
|
||||
|
||||
Sign in a user with one of the following methods:
|
||||
|
||||
- Email and Password
|
||||
- Passwordless Email (Magic Link)
|
||||
- Passwordless SMS
|
||||
- OAuth Provider
|
||||
|
||||
If the user does not exist, the user is created. Only exception is when using Email and Password. To sign in a user with Email and password a user first must sign up.
|
||||
|
||||
### Examples
|
||||
|
||||
#### Sign in with Email and Password
|
||||
|
||||
```js
|
||||
const { session, mfa, error } = await nhost.auth.signIn({
|
||||
email: 'username@gmail.com',
|
||||
password: 'my-secret-password'
|
||||
})
|
||||
```
|
||||
|
||||
### Sign in with Passwordless Email (Magic link)
|
||||
|
||||
An email will be sent to the user's email with a link. When the user clicks on the link the user will be automatically redirected and signed-in to your app.
|
||||
|
||||
```js
|
||||
const { error } = await nhost.auth.signIn({
|
||||
email: 'username@gmail.com'
|
||||
})
|
||||
```
|
||||
|
||||
### Sign in with Passwordless SMS
|
||||
|
||||
Start the sign-in process for a user using SMS. This will send a One-time Password (OTP) to the user:
|
||||
|
||||
```js
|
||||
const { session, error } = await nhost.auth.signIn({
|
||||
phoneNumber: '+46123456789'
|
||||
})
|
||||
```
|
||||
|
||||
To verify the phone number and to sign in the user, combine the phone number and the OTP for the user:
|
||||
|
||||
```js
|
||||
const { session, error } = await nhost.auth.signIn({
|
||||
phoneNumber: '+46123456789',
|
||||
otp: '<otp-from-sms>'
|
||||
})
|
||||
```
|
||||
|
||||
### Sign in with OAuth Provider
|
||||
|
||||
The following providers are available:
|
||||
|
||||
- `google`
|
||||
- `github`
|
||||
- `facebook`
|
||||
- `spotify`
|
||||
|
||||
```js
|
||||
const { session, providerUrl, provider, error } = await nhost.auth.signIn({
|
||||
provider: 'google'
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `nhost.auth.signUp()`
|
||||
|
||||
Sign up a new user using email and password.
|
||||
|
||||
```js
|
||||
const { session, error } = await nhost.auth.signUp({
|
||||
email: 'username@gmail.com',
|
||||
password: 'my-secret-password',
|
||||
options: {
|
||||
displayName: 'John Doe'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## `nhost.auth.signOut()`
|
||||
|
||||
Sign Out the current user.
|
||||
|
||||
## `nhost.auth.getUser()`
|
||||
|
||||
Get information about the current user.
|
||||
|
||||
```js
|
||||
const user = nhost.auth.getUser()
|
||||
```
|
||||
|
||||
## `nhost.auth.changeEmail()`
|
||||
|
||||
Change the email of the current user. For the new email to be in effect, the user must verify the new email address by clicking on the link in the email that is sent out after executing this method.
|
||||
|
||||
```js
|
||||
const { error } = await nhost.auth.changeEmail({ newEmail })
|
||||
```
|
||||
|
||||
## `nhost.auth.changePassword()`
|
||||
|
||||
Change the password of the current user. The old password is not needed when changing the password.
|
||||
|
||||
```js
|
||||
const { error } = await nhost.auth.changePassword({ newPassword })
|
||||
```
|
||||
|
||||
## `nhost.auth.resetPassword()`
|
||||
|
||||
Reset password for a user. Calling this function sends an email to the user with a link to reset their password.
|
||||
|
||||
```js
|
||||
const { error } = await nhost.auth.resetPassword({ email })
|
||||
```
|
||||
|
||||
## `nhost.auth.onAuthStateChanged(event, session)`
|
||||
|
||||
Use `onAuthStateChanged` to add a custom function that is triggered
|
||||
whenever the user's state changes. For example, from signed in to signed out or
|
||||
vice versa.
|
||||
|
||||
```js
|
||||
nhost.auth.onAuthStateChanged(({ event, session }) => {
|
||||
// event = 'SIGNED_IN' | 'SIGNED_OUT'
|
||||
console.log(`Auth state changed. State is now ${event} with the session: ${session}`)
|
||||
})
|
||||
```
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: 'Functions'
|
||||
---
|
||||
|
||||
## `nhost.functions.call()`
|
||||
|
||||
Send a request to a serverless function:
|
||||
|
||||
```js
|
||||
const { res, error } = await nhost.functions.call('/test', { name: 'Johan' })
|
||||
```
|
||||
|
||||
If the user is signed in, the user's access token is attached in the `Authorization` header.
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
title: 'GraphQL'
|
||||
---
|
||||
|
||||
## nhost.graphql.request()
|
||||
|
||||
Make GraphQL query or mutation request.
|
||||
|
||||
| Variable | Type | Notes |
|
||||
| ---------------- | ------ | --------------------------------------------- |
|
||||
| query (required) | String | A valid GraphQL query or mutation. |
|
||||
| variables | Object | Key-value pairs to use in your GraphQL query. |
|
||||
| httpOptions | Object | |
|
||||
|
||||
```js
|
||||
const { data, error } = await nhost.graphql.request(`
|
||||
query {
|
||||
customers {
|
||||
id
|
||||
name
|
||||
public
|
||||
}
|
||||
}
|
||||
`)
|
||||
```
|
||||
|
||||
## `nhost.graphql.getUrl()`
|
||||
|
||||
Returns the GraphQL API endpoint of the Nhost app.
|
||||
|
||||
```js
|
||||
const graphqlEndpoint = nhost.graphql.getUrl()
|
||||
```
|
||||
@@ -1,48 +0,0 @@
|
||||
---
|
||||
title: 'Storage'
|
||||
---
|
||||
|
||||
## `nhost.storage.upload()`
|
||||
|
||||
Upload a file. Stores a new file or replaces an existing file.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
| ----------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
||||
| `file` (required) | [File](https://developer.mozilla.org/en-US/docs/Web/API/File) | The file to upload. |
|
||||
| `bucketId` | String | The bucket to be used. If no `bucketId` is specified the default bucket is used. |
|
||||
| `id` | String | Database ID of the file to override. If no `id` is specified, the server generates a unique ID for the file. |
|
||||
| `name` | String | |
|
||||
|
||||
```js
|
||||
const { fileMetadata, error } = await nhost.storage.upload({ file })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `nhost.storage.getPublicUrl()`
|
||||
|
||||
Get the public URL of a file. To access the file via public URL, the file must have the `select` permission for the `public` role.
|
||||
|
||||
```js
|
||||
const url = nhost.storage.getPublicUrl({ fileId })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `nhost.storage.getPresignedUrl()`
|
||||
|
||||
Get a presigned URL for a file. To generate a presigned URL for the file, a user has to be logged in and have `select` permission to the file.
|
||||
|
||||
```js
|
||||
const { presignedUrl, error } = await nhost.storage.getPresignedUrl({ fileId })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `nhost.storage.delete()`
|
||||
|
||||
Delete a file.
|
||||
|
||||
```js
|
||||
const { error } = await nhost.storage.delete({ fileId })
|
||||
```
|
||||
@@ -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[]
|
||||
}
|
||||
125
docs/docs/get-started/authentication/index.md
Normal file
125
docs/docs/get-started/authentication/index.md
Normal file
@@ -0,0 +1,125 @@
|
||||
---
|
||||
title: 'Authenticate users'
|
||||
slug: /get-started/authentication
|
||||
---
|
||||
|
||||
In the previous section, you defined `select` permissions for the `public` role. You will now add `insert` and `select` permissions for authenticated users to secure your app's GraphQL API with authentication.
|
||||
|
||||
> Nhost's authentication service lets you deliver frictionless registration and login experiences to your users. We support most social providers and different methods such as email & password and passwordless (magic link).
|
||||
|
||||
---
|
||||
|
||||
## Insert a test user
|
||||
|
||||
Manually create a user by going to your app's **Users** tab (top menu) and clicking on **Add User**.
|
||||
|
||||
<video width="99%" loop="" muted="" playsInline="" controls="true">
|
||||
<source src="/videos/add-user.mp4" type="video/mp4" />
|
||||
</video>
|
||||
|
||||
You will now use that newly created user. We'll use this newly created user to make authenticated requests to the GraphQL API.
|
||||
|
||||
---
|
||||
|
||||
## Sign in and query data
|
||||
|
||||
Add the following code to sign in the new user and request the list of todos again:
|
||||
|
||||
```js
|
||||
import { NhostClient } from '@nhost/nhost-js';
|
||||
|
||||
const nhost = new NhostClient({
|
||||
backendUrl: 'https://[app-subdomain].nhost.run',
|
||||
})(async () => {
|
||||
// Sign in user
|
||||
const signInResponse = await nhost.auth.signIn({
|
||||
email: 'joe@example.com',
|
||||
password: 'securepassword',
|
||||
});
|
||||
|
||||
// Handle sign-in error
|
||||
if (signInResponse.error) {
|
||||
throw signInResponse.error;
|
||||
}
|
||||
|
||||
// Get todos
|
||||
const todos = await nhost.graphql.request(`
|
||||
query {
|
||||
todos {
|
||||
id
|
||||
created_at
|
||||
name
|
||||
is_completed
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
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.
|
||||
|
||||
> For authenticated requests, there is always the option to override the default `user` role with any other valid role.
|
||||
|
||||
To prepare our database and GraphQL API to work for signed-in users we need to do two things:
|
||||
|
||||
1. Add a `user_id` column to the `todos` table, so we know what todo belongs to which user.
|
||||
2. Use the `user` role instead of the `public` role for permissions.
|
||||
|
||||
## Add `user_id` column
|
||||
|
||||
Before adding the `user_id` column, let's delete all existing todos.
|
||||
|
||||
Then add the `user_id` column as a `UUID` type. Make sure that `nullable` is **not** checked. This will ensure that all todos must have a `user_id` value.
|
||||
|
||||
At last, we'll create a connection between the `todos` table and the `users` table. For that, we need to do yet another two things:
|
||||
|
||||
1. Create a Foreign Key (FK) between `todos` and `auth.users.id`.
|
||||
2. Let Hasura track the relationship between the two tables.
|
||||
|
||||
<video width="99%" loop="" muted="" playsInline="" controls="true">
|
||||
<source src="/videos/user-id-column.mp4" type="video/mp4" />
|
||||
</video>
|
||||
|
||||
### Create FK
|
||||
|
||||
Create a FK between the `auth.users.id` column and the `public.todos.user_id` column. See video above.
|
||||
|
||||
### Track relationship
|
||||
|
||||
Click on the `public` schema and track the untracked foreign key relationship. Then click on the `auth` schema and track the relationship again. See video above.
|
||||
|
||||
We track these relationships to create the GrpahQL relationships between the `todos` table to the `users` table and the `users` table to the `todos` table.
|
||||
|
||||
Ok, our `user_id` column is added and connected correctly. Let's continue with setting permissions for signed-in users.
|
||||
|
||||
## Permissions for signed-in users
|
||||
|
||||
Let us organize the permissions so it works for signed in users too.
|
||||
|
||||
### Remove permissions for the public role
|
||||
|
||||
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.
|
||||
|
||||
> Signed-in users use the `user` role by default
|
||||
|
||||
### Insert permission
|
||||
|
||||
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.
|
||||
@@ -2,6 +2,9 @@
|
||||
title: 'Metadata and Serverless Functions'
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
In the previous section, we only created a new table; `customers`. Using the CLI you can also do changes to other parts of your backend.
|
||||
|
||||
There are three things the CLI and the GitHub integration track and applies to production:
|
||||
@@ -10,18 +13,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 +39,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 +56,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,25 +74,30 @@ 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.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="npm" default>
|
||||
|
||||
```bash
|
||||
npm install express
|
||||
# or with yarn
|
||||
yarn add express
|
||||
npm install -d @types/node @types/express
|
||||
```
|
||||
|
||||
We’ll use TypeScript so we’ll install two type definitions too:
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
npm install -d @types/node @types/express
|
||||
# or with yarn
|
||||
yarn add express
|
||||
yarn add -D @types/node @types/express
|
||||
```
|
||||
|
||||
Then we’ll create a file `functions/time.ts`
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
In the file `time.ts` we’ll add the following code to create our serverless function:
|
||||
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:
|
||||
|
||||
```bash
|
||||
import { Request, Response } from 'express';
|
||||
@@ -98,11 +109,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 +127,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 +139,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.
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
title: 'JavaScript client'
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
In the previous section, you used the Hasura Console to fetch a list of todos. Now, you will write a small JavaScript client to interact and retrieve todos from your Nhost app.
|
||||
|
||||
### Frontend frameworks
|
||||
@@ -14,25 +17,36 @@ In this guide, we'll keep the example simple. We're not using a frontend framewo
|
||||
|
||||
## Setup
|
||||
|
||||
> Make sure you have [Node.js](https://nodejs.org) and [npm](https://docs.npmjs.com/getting-started) installed.
|
||||
:::info
|
||||
|
||||
Create a new folder called `nhost-todos`, and initialize a new JavaScript app there:
|
||||
Make sure you have [Node.js](https://nodejs.org) and [npm](https://docs.npmjs.com/getting-started) or [Yarn](https://classic.yarnpkg.com/lang/en/docs/install) installed.
|
||||
|
||||
```sh
|
||||
:::
|
||||
|
||||
Create a new folder called `nhost-todos`, initialize a new JavaScript app there, and install the Nhost JavaScript SDK:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="npm" default>
|
||||
|
||||
```bash
|
||||
npm init -y
|
||||
# or
|
||||
npm install @nhost/nhost-js graphql
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn init -y
|
||||
yarn add @nhost/nhost-js graphql
|
||||
```
|
||||
|
||||
> You might have to edit the `package.json` file and add/change the `type` object to `module` (`"type": "module"`).
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Install Nhost JavaScript SDK:
|
||||
|
||||
```sh
|
||||
npm install @nhost/nhost-js
|
||||
# or
|
||||
yarn add @nhost/nhost-js
|
||||
```
|
||||
:::caution attention
|
||||
You might have to edit the `package.json` file and add/change the `type` object to `module` (`"type": "module"`).
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
@@ -43,18 +57,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 +79,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 +94,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
|
||||
@@ -2,7 +2,7 @@
|
||||
title: 'Set permissions'
|
||||
---
|
||||
|
||||
In the previous section, you could fetch the todos because the **admin** role is enabled by default when using Hasura Console. When building your applications, you want to define permissions using **roles** that your users can assume when making requests.
|
||||
While using the Hasura Console, you could fetch the todos because the **admin** role is enabled by default but when building your applications with a client, you want to define permissions using **roles** that your users can assume when making requests.
|
||||
|
||||
Hasura supports role-based access control. You create rules for each role, table, and operation (select, insert, update and delete) that can check dynamic session variables, like user ID.
|
||||
|
||||
@@ -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,9 @@ 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.
|
||||
|
||||

|
||||
<video width="99%" loop="" muted="" playsInline="" controls="true">
|
||||
<source src="/videos/insert-todos.mp4" type="video/mp4" />
|
||||
</video>
|
||||
|
||||
---
|
||||
|
||||
@@ -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!
|
||||
4
docs/docs/platform/authentication/_category_.json
Normal file
4
docs/docs/platform/authentication/_category_.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Authentication",
|
||||
"position": 6
|
||||
}
|
||||
@@ -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/javascript):
|
||||
|
||||
```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/javascript) automatically handles access and refresh tokens.
|
||||
|
||||
### Access tokens
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "OAuth providers",
|
||||
"position": 3
|
||||
}
|
||||
@@ -1,14 +1,23 @@
|
||||
---
|
||||
title: Sign In with Facebook
|
||||
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 Facebook account
|
||||
|
||||
- Create a new [Facebook account](https://www.facebook.com/) if you don’t have one already.
|
||||
- Create a new [Facebook account](https://www.facebook.com/) if you don't have one already.
|
||||
|
||||
## Create Facebook App
|
||||
|
||||
@@ -24,7 +33,7 @@ Follow this guide to sign in users with Facebook with your Nhost App.
|
||||
|
||||
- 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.
|
||||
- **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.
|
||||
@@ -43,16 +52,16 @@ To make sure we can fetch all user data (email, profile picture and name). For t
|
||||
## 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).
|
||||
- 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:
|
||||
Use the [Nhost JavaScript client](/reference/javascript) to sign in users in your app:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'facebook'
|
||||
})
|
||||
provider: 'facebook',
|
||||
});
|
||||
```
|
||||
@@ -1,14 +1,23 @@
|
||||
---
|
||||
title: Sign In with GitHub
|
||||
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 GitHub account
|
||||
|
||||
- Create a new [GitHub account](https://github.com/signup) if you don’t have one already.
|
||||
- Create a new [GitHub account](https://github.com/signup) if you don't have one already.
|
||||
|
||||
## Create GitHub OAuth App
|
||||
|
||||
@@ -28,16 +37,16 @@ Follow this guide to sign in users with GitHub with your Nhost App.
|
||||
## 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).
|
||||
- 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:
|
||||
Use the [Nhost JavaScript client](/reference/javascript) to sign in users in your app:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'github'
|
||||
})
|
||||
provider: 'github',
|
||||
});
|
||||
```
|
||||
@@ -1,10 +1,19 @@
|
||||
---
|
||||
title: Sign In with Google
|
||||
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
|
||||
|
||||
@@ -52,16 +61,16 @@ Follow this guide to sign in users with Google with your Nhost App.
|
||||
## 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](/platform/authentication/social-sign-in#enabling-social-sign-in).
|
||||
- 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](/reference/sdk) to sign in users in your app:
|
||||
Use the Nhost JavaScript client to sign in users in your app:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'google'
|
||||
})
|
||||
provider: 'google',
|
||||
});
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user