Compare commits
512 Commits
@nhost/rea
...
@nhost/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f58c2bb9ce | ||
|
|
41db6f613a | ||
|
|
ee84bfa098 | ||
|
|
ad1b7b80e2 | ||
|
|
3fcd345cff | ||
|
|
43a3f1dd46 | ||
|
|
3ec745c91e | ||
|
|
92deec4531 | ||
|
|
c7fcc9fe82 | ||
|
|
081377af6c | ||
|
|
23cb207afc | ||
|
|
0f55f6db9b | ||
|
|
7b16a8d790 | ||
|
|
dca8233601 | ||
|
|
a7535b260b | ||
|
|
82520963f1 | ||
|
|
722abd9a19 | ||
|
|
2aff6c0b4e | ||
|
|
e101915f60 | ||
|
|
0195143fe1 | ||
|
|
e362925041 | ||
|
|
b4f8c7457d | ||
|
|
84f1ab2f61 | ||
|
|
dee93bb873 | ||
|
|
173b587802 | ||
|
|
30ef1660b4 | ||
|
|
a613aa9f0c | ||
|
|
3c03b9b46f | ||
|
|
65a3061146 | ||
|
|
55864eac30 | ||
|
|
28494d6c1f | ||
|
|
6777738c53 | ||
|
|
34532b1a2f | ||
|
|
0d60693c27 | ||
|
|
c159c9c98c | ||
|
|
58fa2a201c | ||
|
|
db4607ccac | ||
|
|
95b14557a0 | ||
|
|
8b527d0fcb | ||
|
|
67f0450dac | ||
|
|
fc50beec5e | ||
|
|
584976d1ad | ||
|
|
509ed7d864 | ||
|
|
71b92363b4 | ||
|
|
ed0de2d930 | ||
|
|
f26e8c3614 | ||
|
|
a05a484426 | ||
|
|
4f0d5aa9c0 | ||
|
|
254e362e95 | ||
|
|
a2a8839694 | ||
|
|
d12667ccc5 | ||
|
|
feb55fe0ad | ||
|
|
b97c0a9c9d | ||
|
|
bb548cd108 | ||
|
|
adb30c537f | ||
|
|
d7d3e8f903 | ||
|
|
2e98934f75 | ||
|
|
8a4064e99f | ||
|
|
937e28116b | ||
|
|
8cedafc807 | ||
|
|
d885fe7b02 | ||
|
|
b0d7217276 | ||
|
|
bfba4ae7ec | ||
|
|
322b433994 | ||
|
|
6082ba6943 | ||
|
|
44b12dc0a0 | ||
|
|
1fb1d25a72 | ||
|
|
ae4e4e50f6 | ||
|
|
185f39e23f | ||
|
|
bf6ee5d360 | ||
|
|
0dd7cab3bf | ||
|
|
47c7380d89 | ||
|
|
b56162a74b | ||
|
|
9f8bdb504d | ||
|
|
aeb8b8afda | ||
|
|
f9e107b008 | ||
|
|
cd2594f66a | ||
|
|
c6a3e9f516 | ||
|
|
d748d82483 | ||
|
|
1bfb1e6d10 | ||
|
|
35fd7b1b7c | ||
|
|
04c1ed6955 | ||
|
|
0c591daef4 | ||
|
|
2192fdc92e | ||
|
|
eec2601a3a | ||
|
|
93eaa85b47 | ||
|
|
4c8a168c02 | ||
|
|
eb166cf5ee | ||
|
|
54e1873461 | ||
|
|
e5bc3b356c | ||
|
|
42edb74057 | ||
|
|
3ebeae9294 | ||
|
|
24189bd155 | ||
|
|
5985b18764 | ||
|
|
95efcb4de6 | ||
|
|
609d9001f5 | ||
|
|
6f5729eb45 | ||
|
|
9bc447dbff | ||
|
|
d2d0e7fced | ||
|
|
0ad654226b | ||
|
|
9d8f2dea22 | ||
|
|
f3a44931a5 | ||
|
|
1e44a14b8a | ||
|
|
1c1656441b | ||
|
|
5f68f8fe31 | ||
|
|
961103d7a5 | ||
|
|
2bebab3f8e | ||
|
|
0363abbbb1 | ||
|
|
2f3715d02a | ||
|
|
7c101e5226 | ||
|
|
ef943995e2 | ||
|
|
b6032508bc | ||
|
|
a6b00294e7 | ||
|
|
dbfc5ec220 | ||
|
|
5917eff5a6 | ||
|
|
f8ee87ad01 | ||
|
|
a7990b363f | ||
|
|
ca8ecb4b5c | ||
|
|
dfe080b8f6 | ||
|
|
069a5d4d9a | ||
|
|
6ffaf31af5 | ||
|
|
8ec18157bb | ||
|
|
efccd54641 | ||
|
|
175ab26e04 | ||
|
|
eb2d064cbe | ||
|
|
17d2c8c3d9 | ||
|
|
4e0aab1bb2 | ||
|
|
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 | ||
|
|
1bde3e6516 | ||
|
|
954aa43e49 | ||
|
|
de6f862c4c | ||
|
|
5025333fb4 | ||
|
|
b3991ea9d9 | ||
|
|
3674f11183 | ||
|
|
239db617f3 | ||
|
|
76b3cb4643 | ||
|
|
6d6c8b3e2e | ||
|
|
169f163c99 | ||
|
|
5ddd7eda22 | ||
|
|
556190dfc5 | ||
|
|
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 | ||
|
|
69f3a84bf5 | ||
|
|
16dcd314bb | ||
|
|
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 | ||
|
|
9b840f7c4a | ||
|
|
83d3c90f43 | ||
|
|
058956bdcb | ||
|
|
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 |
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"ignorePatterns": ["**/dist", "**/build", "**/.next"],
|
||||
"extends": ["react-app", "plugin:react/recommended", "plugin:react-hooks/recommended"],
|
||||
"parserOptions": {
|
||||
// "project": "./tsconfig.json"
|
||||
"project": ["packages/*/tsconfig.json", "examples/*/tsconfig.json"]
|
||||
},
|
||||
"plugins": ["react", "@typescript-eslint", "react-hooks", "simple-import-sort"],
|
||||
"rules": {
|
||||
"no-use-before-define": "off",
|
||||
"simple-import-sort/exports": "error",
|
||||
|
||||
"simple-import-sort/imports": [
|
||||
"error",
|
||||
{
|
||||
"groups": [
|
||||
// Node.js builtins. You could also generate this regex if you use a `.js` config.
|
||||
// For example: `^(${require("module").builtinModules.join("|")})(/|$)`
|
||||
[
|
||||
"^(assert|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|https|module|net|os|path|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|tty|url|util|vm|zlib|freelist|v8|process|async_hooks|http2|perf_hooks)(/.*|$)"
|
||||
],
|
||||
// Packages
|
||||
["^\\w"],
|
||||
// Internal packages.
|
||||
["^(@|config/)(/*|$)"],
|
||||
// Side effect imports.
|
||||
["^\\u0000"],
|
||||
// Parent imports. Put `..` last.
|
||||
["^\\.\\.(?!/?$)", "^\\.\\./?$"],
|
||||
// Other relative imports. Put same-folder imports and `.` last.
|
||||
["^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"],
|
||||
// Style imports.
|
||||
["^.+\\.s?css$"]
|
||||
]
|
||||
}
|
||||
],
|
||||
"import/no-anonymous-default-export": [
|
||||
"error",
|
||||
{
|
||||
"allowArrowFunction": true,
|
||||
"allowAnonymousFunction": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
const esbuild = require('esbuild')
|
||||
|
||||
// Automatically exclude all node_modules from the bundled version
|
||||
const { nodeExternalsPlugin } = require('esbuild-node-externals')
|
||||
esbuild
|
||||
.build({
|
||||
entryPoints: ['./src/index.ts'],
|
||||
outfile: 'dist/index.cjs.js',
|
||||
bundle: true,
|
||||
minify: true,
|
||||
platform: 'node',
|
||||
format: 'cjs',
|
||||
sourcemap: true,
|
||||
target: 'node14',
|
||||
plugins: [nodeExternalsPlugin()]
|
||||
})
|
||||
.catch(() => process.exit(1))
|
||||
|
||||
esbuild
|
||||
.build({
|
||||
entryPoints: ['./src/index.ts'],
|
||||
outfile: 'dist/index.es.js',
|
||||
bundle: true,
|
||||
minify: true,
|
||||
platform: 'browser',
|
||||
format: 'esm',
|
||||
sourcemap: true,
|
||||
target: 'es2019'
|
||||
})
|
||||
.catch(() => process.exit(1))
|
||||
@@ -1,7 +0,0 @@
|
||||
const base = require('./jest.config.base.js')
|
||||
|
||||
module.exports = {
|
||||
...base,
|
||||
projects: ['<rootDir>/packages/*/jest.config.js'],
|
||||
coverageDirectory: '<rootDir>/coverage/'
|
||||
}
|
||||
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
|
||||
|
||||
20
.github/workflows/changesets.yaml
vendored
20
.github/workflows/changesets.yaml
vendored
@@ -16,15 +16,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
# - name: Cache pnpm modules
|
||||
# uses: actions/cache@v2
|
||||
# with:
|
||||
# path: ~/.pnpm-store
|
||||
# key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: pnpm/action-setup@v2.1.0
|
||||
with:
|
||||
@@ -35,16 +30,19 @@ jobs:
|
||||
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 }}
|
||||
17
.gitignore
vendored
17
.gitignore
vendored
@@ -15,11 +15,11 @@ logs/
|
||||
/.vscode/
|
||||
.eslintcache
|
||||
.yarnclean
|
||||
.husky
|
||||
|
||||
# Directories
|
||||
coverage/
|
||||
dist/
|
||||
umd/
|
||||
lib/
|
||||
node_modules/
|
||||
tmp/
|
||||
@@ -33,16 +33,15 @@ tmp/
|
||||
*.map
|
||||
todo.md
|
||||
|
||||
# Generated configs
|
||||
# Config files that are not part of the repository root anymore. Should be removed in the future.
|
||||
/.eslintignore
|
||||
/.eslintrc
|
||||
/.prettierignore
|
||||
/prettier.config.js
|
||||
/.eslintrc*
|
||||
/vite.*.js
|
||||
/jest.*.js
|
||||
/*tsconfig*.json
|
||||
/esbuild.*.js
|
||||
|
||||
!.config/**
|
||||
!config/**
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -11,6 +11,11 @@ module.exports = {
|
||||
tabWidth: 2,
|
||||
trailingComma: 'none',
|
||||
useTabs: false,
|
||||
// TODO: add import sort configuration to match ESLint rules
|
||||
// plugins: ['./node_modules/@trivago/prettier-plugin-sort-imports'],
|
||||
// importOrderSeparation: true,
|
||||
// importOrderSortSpecifiers: true
|
||||
plugins: [],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.json', '*.yaml'],
|
||||
@@ -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.
|
||||
|
||||
98
DEVELOPERS.md
Normal file
98
DEVELOPERS.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Developer guide
|
||||
|
||||
## Requirements
|
||||
|
||||
- We use [pnpm](https://pnpm.io/) as a package manager to speed up development and builds, and as a basis for our monorepo. You need to make sure it's installed on your machine. There are [several ways to install it](https://pnpm.io/installation), but the easiest way is with `npm`:
|
||||
|
||||
```sh
|
||||
$ npm install -g pnpm
|
||||
```
|
||||
|
||||
- Our tests and examples use the Nhost CLI, to run the backend services locally. You can follow the installation instructions in [our documentation](https://docs.nhost.io/get-started/cli-workflow/install-cli).
|
||||
|
||||
## Get started
|
||||
|
||||
### Installation
|
||||
|
||||
First, clone this repository:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/nhost/nhost
|
||||
```
|
||||
|
||||
Then, install the dependencies with `pnpm`:
|
||||
|
||||
```sh
|
||||
$ cd nhost
|
||||
$ pnpm install
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
Although package references are correctly updated on the fly for TypeScript, example projects won't
|
||||
see the changes because they are depending on the build output. To fix this, you can run packages
|
||||
in development mode.
|
||||
|
||||
Running packages in development mode is as simple as:
|
||||
|
||||
```sh
|
||||
$ pnpm dev
|
||||
```
|
||||
|
||||
Our packages are linked together using [PNPM's workspace](https://pnpm.io/workspaces) feature. Vite automatically detects changes in the dependencies and rebuilds everything, so that the changes are immediately reflected in the other packages.
|
||||
|
||||
### Use examples
|
||||
|
||||
Examples are a great way to test your changes in practice. Make sure you've `pnpm dev` running in your terminal and then run an example.
|
||||
|
||||
Let's follow the instructions to run [react-apollo example](https://github.com/nhost/nhost/blob/main/examples/react-apollo/README.md).
|
||||
|
||||
## Run the documentation website locally
|
||||
|
||||
The easier way to contribute to our documentation is to go 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 if you've already done this step somewhere in the repository
|
||||
$ pnpm install
|
||||
$ pnpm start
|
||||
```
|
||||
|
||||
## Run test suites
|
||||
|
||||
In order to run tests, the Nhost testing backend should be running locally. You can run it from a separate terminal:
|
||||
|
||||
```sh
|
||||
$ cd examples/testing-project
|
||||
$ nhost -d
|
||||
```
|
||||
|
||||
Once Nhost is started locally, you can run the tests with the following command from the repository root:
|
||||
|
||||
```sh
|
||||
$ pnpm test
|
||||
```
|
||||
|
||||
## Changesets
|
||||
|
||||
If you've made changes to the packages, you must describe those changes so that they can be reflected in the next release.
|
||||
We use [changesets](https://github.com/changesets/changesets) to support our versioning and release workflows. When you submit a pull request, a bot checks if some changesets are present, and if not, it directs you to add them.
|
||||
|
||||
The most comprehensive way to add a changeset is to run the following command in the repository root:
|
||||
|
||||
```sh
|
||||
$ pnpm changeset
|
||||
```
|
||||
|
||||
This will create a file in the `.changeset` directory. You can edit it to give more details about the change you just made.
|
||||
|
||||
You can take a look at the changeset documentation: [How to add a changeset](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).
|
||||
|
||||
## Committing changes
|
||||
|
||||
You'll notice that `git commit` takes a few seconds to run. We set a commit hook that scans the changes in the code, automatically generates documentation from the inline [TSDoc](https://tsdoc.org/) annotations, and adds these generated documentation files to the commit. They automatically update the [reference documentation](https://docs.nhost.io/reference).
|
||||
|
||||
<!-- ## Good practices
|
||||
- lint
|
||||
- prettier
|
||||
- documentation -->
|
||||
248
README.md
248
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
|
||||
@@ -98,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)
|
||||
@@ -110,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.
|
||||
|
||||
@@ -120,8 +120,234 @@ 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/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/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/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/MarcelloTheArcane">
|
||||
<img src="https://avatars.githubusercontent.com/u/21159570?v=4" width="100;" alt="MarcelloTheArcane"/>
|
||||
<br />
|
||||
<sub><b>Max Reynolds</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></tr>
|
||||
<tr>
|
||||
<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 -->
|
||||
|
||||
65
config/.eslintrc.js
Normal file
65
config/.eslintrc.js
Normal file
@@ -0,0 +1,65 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
node: true
|
||||
},
|
||||
ignorePatterns: [
|
||||
'dist',
|
||||
'umd',
|
||||
'build',
|
||||
'.next',
|
||||
'node_modules',
|
||||
'tsup.config.ts',
|
||||
'__tests__',
|
||||
'__mocks__',
|
||||
'*.test.ts',
|
||||
'*.test.tsx',
|
||||
'*.spec.ts',
|
||||
'*.spec.tsx',
|
||||
'tests/**/*.ts',
|
||||
'tests/**/*.d.ts'
|
||||
],
|
||||
extends: ['react-app', 'plugin:react/recommended', 'plugin:react-hooks/recommended'],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module'
|
||||
},
|
||||
plugins: ['react', '@typescript-eslint', 'react-hooks', 'simple-import-sort'],
|
||||
rules: {
|
||||
'react/prop-types': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
'simple-import-sort/exports': 'error',
|
||||
'simple-import-sort/imports': [
|
||||
'error',
|
||||
{
|
||||
groups: [
|
||||
// Node.js builtins. You could also generate this regex if you use a `.js` config.
|
||||
// For example: `^(${require("module").builtinModules.join("|")})(/|$)`
|
||||
[
|
||||
'^(assert|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|https|module|net|os|path|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|tty|url|util|vm|zlib|freelist|v8|process|async_hooks|http2|perf_hooks)(/.*|$)'
|
||||
],
|
||||
// Packages
|
||||
['^\\w'],
|
||||
// Internal packages.
|
||||
['^(@|config/)(/*|$)'],
|
||||
// Side effect imports.
|
||||
['^\\u0000'],
|
||||
// Parent imports. Put `..` last.
|
||||
['^\\.\\.(?!/?$)', '^\\.\\./?$'],
|
||||
// Other relative imports. Put same-folder imports and `.` last.
|
||||
['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
|
||||
// Style imports.
|
||||
['^.+\\.s?css$']
|
||||
]
|
||||
}
|
||||
],
|
||||
'import/no-anonymous-default-export': [
|
||||
'error',
|
||||
{
|
||||
allowArrowFunction: true,
|
||||
allowAnonymousFunction: true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ module.exports = {
|
||||
testRegex: '(/tests/.*.(test|spec)).(jsx?|tsx?)$',
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
||||
collectCoverage: true,
|
||||
// coveragePathIgnorePatterns: ['(tests/.*.mock).(jsx?|tsx?)$'],
|
||||
verbose: true,
|
||||
testTimeout: 30000,
|
||||
globals: {
|
||||
15
config/jest.config.js
Normal file
15
config/jest.config.js
Normal file
@@ -0,0 +1,15 @@
|
||||
module.exports = {
|
||||
rootDir: process.cwd(),
|
||||
preset: 'ts-jest',
|
||||
collectCoverage: true,
|
||||
coverageProvider: 'v8',
|
||||
coverageDirectory: '<rootDir>/coverage',
|
||||
clearMocks: true,
|
||||
verbose: true,
|
||||
testTimeout: 30000,
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
isolatedModules: true
|
||||
}
|
||||
}
|
||||
}
|
||||
8
config/react-library.tsconfig.json
Normal file
8
config/react-library.tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"display": "React Library",
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve"
|
||||
}
|
||||
}
|
||||
56
config/tsconfig.base.json
Normal file
56
config/tsconfig.base.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"display": "Default",
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"strict": true,
|
||||
"isolatedModules": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "node",
|
||||
"target": "ES6",
|
||||
"module": "CommonJS",
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.promise",
|
||||
"es2015.symbol",
|
||||
"es2015.iterable",
|
||||
"es2015.collection",
|
||||
"es2015.symbol.wellknown",
|
||||
"es2015.core",
|
||||
"es2017.object",
|
||||
"es2017.string"
|
||||
],
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true,
|
||||
"types": ["node"],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types", "**/*/dist", "**/*/build", "**/*/.next", "**/*/umd"
|
||||
],
|
||||
"paths": {
|
||||
"@nhost/apollo": ["../packages/apollo/src/index.ts"],
|
||||
"@nhost/core": ["../packages/core/src/index.ts"],
|
||||
"@nhost/docgen": ["../packages/docgen/src/index.ts"],
|
||||
"@nhost/hasura-auth-js": ["../packages/hasura-auth-js/src/index.ts"],
|
||||
"@nhost/hasura-storage-js": ["../packages/hasura-storage-js/src/index.ts"],
|
||||
"@nhost/nextjs": ["../packages/nextjs/src/index.ts"],
|
||||
"@nhost/nhost-js": ["../packages/nhost-js/src/index.ts"],
|
||||
"@nhost/react": ["../packages/react/src/index.ts"],
|
||||
"@nhost/react-apollo": ["../packages/react-apollo/src/index.ts"],
|
||||
"@nhost/react-auth": ["../packages/react-auth/src/index.ts"]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/*/dist",
|
||||
"**/*/build",
|
||||
"**/*/.next",
|
||||
"**/*/__tests__",
|
||||
"**/*/__mocks__"
|
||||
]
|
||||
}
|
||||
@@ -5,20 +5,19 @@ import { defineConfig } from 'vite'
|
||||
import dts from 'vite-plugin-dts'
|
||||
import tsconfigPaths from 'vite-tsconfig-paths'
|
||||
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
const PWD = process.env.PWD
|
||||
const pkg = require(path.join(PWD, 'package.json'))
|
||||
|
||||
const tsEntry = path.resolve(PWD, 'src/index.ts')
|
||||
const entry = fs.existsSync(tsEntry) ? tsEntry : tsEntry.replace('.ts', '.tsx')
|
||||
|
||||
/**
|
||||
* @type {import('vite').UserConfig}
|
||||
*/
|
||||
const deps = [...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies))]
|
||||
|
||||
export default defineConfig({
|
||||
optimizeDeps: {
|
||||
include: ['react/jsx-runtime']
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
tsconfigPaths(),
|
||||
dts({
|
||||
exclude: ['**/*.spec.ts', '**/*.test.ts', '**/tests/**'],
|
||||
@@ -32,16 +31,29 @@ export default defineConfig({
|
||||
})
|
||||
],
|
||||
build: {
|
||||
sourcemap: true,
|
||||
lib: {
|
||||
entry,
|
||||
name: pkg.name,
|
||||
fileName: 'index'
|
||||
fileName: 'index',
|
||||
formats: ['cjs', 'es']
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['react', '@nhost/react'],
|
||||
external: (id) => deps.some((dep) => id.startsWith(dep)),
|
||||
output: {
|
||||
globals: {
|
||||
react: 'react',
|
||||
'graphql/language/printer': 'graphql/language/printer',
|
||||
'@apollo/client': '@apollo/client',
|
||||
'@apollo/client/link/context': '@apollo/client/link/context',
|
||||
'@apollo/client/link/subscriptions': '@apollo/client/link/subscriptions',
|
||||
'@apollo/client/utilities': '@apollo/client/utilities',
|
||||
'graphql-ws': 'graphql-ws',
|
||||
xstate: 'xstate',
|
||||
axios: 'axios',
|
||||
'js-cookie': 'Cookies',
|
||||
react: 'React',
|
||||
'react-dom': 'ReactDOM',
|
||||
'react/jsx-runtime': '_jsx',
|
||||
'@nhost/react': '@nhost/react'
|
||||
}
|
||||
}
|
||||
13
config/vite.lib.dev.config.js
Normal file
13
config/vite.lib.dev.config.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
import viteLibConfig from './vite.lib.config'
|
||||
|
||||
export default defineConfig({
|
||||
...viteLibConfig,
|
||||
build: {
|
||||
...viteLibConfig.build,
|
||||
watch: {
|
||||
buildDelay: 500
|
||||
}
|
||||
}
|
||||
})
|
||||
42
config/vite.lib.umd.config.js
Normal file
42
config/vite.lib.umd.config.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import dts from 'vite-plugin-dts'
|
||||
import tsconfigPaths from 'vite-tsconfig-paths'
|
||||
|
||||
import baseLibConfig from './vite.lib.config'
|
||||
|
||||
const PWD = process.env.PWD
|
||||
const pkg = require(path.join(PWD, 'package.json'))
|
||||
|
||||
const deps = [...Object.keys(Object.assign({}, pkg.peerDependencies))]
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
tsconfigPaths(),
|
||||
dts({
|
||||
exclude: ['**/*.spec.ts', '**/*.test.ts', '**/tests/**'],
|
||||
afterBuild: () => {
|
||||
const types = fs.readdirSync(path.join(PWD, 'umd/src'))
|
||||
types.forEach((file) => {
|
||||
fs.renameSync(path.join(PWD, 'umd/src', file), path.join(PWD, 'umd', file))
|
||||
})
|
||||
fs.rmdirSync(path.join(PWD, 'umd/src'))
|
||||
}
|
||||
})
|
||||
],
|
||||
build: {
|
||||
...(baseLibConfig.build || {}),
|
||||
outDir: 'umd',
|
||||
lib: {
|
||||
...(baseLibConfig.build?.lib || {}),
|
||||
fileName: pkg.name.replace(/@nhost\//g, ''),
|
||||
formats: ['umd']
|
||||
},
|
||||
rollupOptions: {
|
||||
...(baseLibConfig.build?.rollupOptions || {}),
|
||||
external: (id) => deps.some((dep) => id.startsWith(dep))
|
||||
}
|
||||
}
|
||||
})
|
||||
8
config/vite.react.config.js
Normal file
8
config/vite.react.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from 'vite'
|
||||
import baseLibConfig from './vite.lib.config'
|
||||
|
||||
export default defineConfig({
|
||||
...baseLibConfig,
|
||||
plugins: [react(), ...baseLibConfig.plugins]
|
||||
})
|
||||
12
config/vite.react.dev.config.js
Normal file
12
config/vite.react.dev.config.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import viteReactConfig from './vite.react.config'
|
||||
|
||||
export default defineConfig({
|
||||
...viteReactConfig,
|
||||
build: {
|
||||
...viteReactConfig.build,
|
||||
watch: {
|
||||
buildDelay: 500
|
||||
}
|
||||
}
|
||||
})
|
||||
45
config/vite.react.umd.config.js
Normal file
45
config/vite.react.umd.config.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import dts from 'vite-plugin-dts'
|
||||
import tsconfigPaths from 'vite-tsconfig-paths'
|
||||
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
import baseLibConfig from './vite.lib.config'
|
||||
|
||||
const PWD = process.env.PWD
|
||||
const pkg = require(path.join(PWD, 'package.json'))
|
||||
|
||||
const deps = [...Object.keys(Object.assign({}, pkg.peerDependencies))]
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
tsconfigPaths(),
|
||||
dts({
|
||||
exclude: ['**/*.spec.ts', '**/*.test.ts', '**/tests/**'],
|
||||
afterBuild: () => {
|
||||
const types = fs.readdirSync(path.join(PWD, 'umd/src'))
|
||||
types.forEach((file) => {
|
||||
fs.renameSync(path.join(PWD, 'umd/src', file), path.join(PWD, 'umd', file))
|
||||
})
|
||||
fs.rmdirSync(path.join(PWD, 'umd/src'))
|
||||
}
|
||||
})
|
||||
],
|
||||
build: {
|
||||
...(baseLibConfig.build || {}),
|
||||
outDir: 'umd',
|
||||
lib: {
|
||||
...(baseLibConfig.build?.lib || {}),
|
||||
fileName: pkg.name.replace(/@nhost\//g, ''),
|
||||
formats: ['umd']
|
||||
},
|
||||
rollupOptions: {
|
||||
...(baseLibConfig.build?.rollupOptions || {}),
|
||||
external: (id) => deps.some((dep) => id.startsWith(dep))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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/
|
||||
@@ -1,6 +1,9 @@
|
||||
# nhost-documentation
|
||||
# @nhost/docs
|
||||
|
||||
## 0.0.1
|
||||
|
||||
## 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
|
||||
- 584976d: - publishable directory structure changes (ESM, CJS and UMD included in the output)
|
||||
- build system improvements
|
||||
- fixed some bundling concerns (https://github.com/nhost/nhost/issues/428)
|
||||
|
||||
@@ -1,48 +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'],
|
||||
react: ['index', 'hooks', 'protecting-routes', 'apollo'],
|
||||
nextjs: ['index', 'configuration', 'protecting-routes', ],
|
||||
cli: ['index'],
|
||||
'hasura-auth': ['index', 'installation', 'configuration', 'environment-variables', 'schema', 'api-reference']
|
||||
}
|
||||
};
|
||||
```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,156 +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'
|
||||
import { Swagger } from '../Swagger'
|
||||
|
||||
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} />
|
||||
},
|
||||
Swagger,
|
||||
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,29 +0,0 @@
|
||||
import SwaggerUI from 'swagger-ui-react'
|
||||
import 'swagger-ui-react/swagger-ui.css'
|
||||
|
||||
const OperationsLayout = (props) => {
|
||||
const { getComponent } = props
|
||||
const Operations = getComponent('operations', true)
|
||||
let SvgAssets = getComponent('SvgAssets')
|
||||
return (
|
||||
<div className="swagger-ui">
|
||||
<SvgAssets />
|
||||
<Operations />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const OperationsLayoutPlugin = () => ({
|
||||
components: {
|
||||
OperationsLayout
|
||||
}
|
||||
})
|
||||
|
||||
export const Swagger: React.FC<{ spec: string }> = ({ spec }) => (
|
||||
<SwaggerUI
|
||||
url={`/openapi/${spec}`}
|
||||
plugins={[OperationsLayoutPlugin]}
|
||||
layout="OperationsLayout"
|
||||
supportedSubmitMethods={[]}
|
||||
/>
|
||||
)
|
||||
@@ -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,6 +0,0 @@
|
||||
---
|
||||
title: 'API Reference'
|
||||
subtitle: 'Hasura Auth'
|
||||
---
|
||||
|
||||
<Swagger spec="hasura-auth.json" />
|
||||
@@ -1,38 +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)
|
||||
### Hasura Auth
|
||||
|
||||
- [Overview](./reference/hasura-auth)
|
||||
- [Installation](./reference/hasura-auth/installation)
|
||||
- [Configuration](./reference/hasura-auth/configuration)
|
||||
- [Environment variables](./reference/hasura-auth/environment-variables)
|
||||
- [API](./reference/hasura-auth/api-reference)
|
||||
- [Schema](./reference/hasura-auth/api-reference)
|
||||
@@ -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 | Resend the verification email. |
|
||||
| `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 { useSendVerificationEmail } from '@nhost/react'
|
||||
|
||||
const Component = () => {
|
||||
const [email, setEmail] = useState('')
|
||||
const { sendEmail, isLoading, isSent, isError, error } = useSendVerificationEmail(email)
|
||||
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!
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user