Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8700453a4 |
@@ -353,8 +353,7 @@
|
||||
"profile": "https://github.com/ntgussoni",
|
||||
"contributions": [
|
||||
"test",
|
||||
"code",
|
||||
"review"
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -609,8 +608,7 @@
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/1329874?v=4",
|
||||
"profile": "https://jamiedavenport.dev",
|
||||
"contributions": [
|
||||
"code",
|
||||
"maintenance"
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -973,8 +971,7 @@
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/37571416?v=4",
|
||||
"profile": "https://github.com/clgeoio",
|
||||
"contributions": [
|
||||
"code",
|
||||
"test"
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -994,8 +991,7 @@
|
||||
"contributions": [
|
||||
"code",
|
||||
"maintenance",
|
||||
"question",
|
||||
"doc"
|
||||
"question"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1004,8 +1000,7 @@
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/1430136?v=4",
|
||||
"profile": "https://github.com/sirmyron",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code"
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1014,9 +1009,8 @@
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/36962022?v=4",
|
||||
"profile": "https://github.com/engelkes-finstreet",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code",
|
||||
"maintenance"
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1174,575 +1168,6 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jorisre",
|
||||
"name": "Joris",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/7545547?v=4",
|
||||
"profile": "https://github.com/jorisre",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Kamshak",
|
||||
"name": "Valentin Funk",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/337968?v=4",
|
||||
"profile": "https://github.com/Kamshak",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "lukebennett",
|
||||
"name": "Luke Bennett",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/135390?v=4",
|
||||
"profile": "https://lukebennett.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "hmajid2301",
|
||||
"name": "Haseeb Majid",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/998807?v=4",
|
||||
"profile": "https://haseebmajid.dev",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "phillippschmedt",
|
||||
"name": "Phillipp Schmedt",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/16028406?v=4",
|
||||
"profile": "https://github.com/phillippschmedt",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "hasparus",
|
||||
"name": "Piotr Monwid-Olechnowicz",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/15332326?v=4",
|
||||
"profile": "https://haspar.us",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "mizchi",
|
||||
"name": "Kotaro Chikuba",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/73962?v=4",
|
||||
"profile": "https://mizchi.dev",
|
||||
"contributions": [
|
||||
"code",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "konradkalemba",
|
||||
"name": "Konrad Kalemba",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/8682104?v=4",
|
||||
"profile": "https://github.com/konradkalemba",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Alucard17",
|
||||
"name": "Alucard17",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/26205172?v=4",
|
||||
"profile": "https://github.com/Alucard17",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Dohxis",
|
||||
"name": "Domantas Mauruča",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/8768909?v=4",
|
||||
"profile": "https://github.com/Dohxis",
|
||||
"contributions": [
|
||||
"test",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sandulat",
|
||||
"name": "Stratulat Alexandru",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/7345874?v=4",
|
||||
"profile": "https://sandulat.com/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"maintenance"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "aericson",
|
||||
"name": "André Ericson",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/692542?v=4",
|
||||
"profile": "https://github.com/aericson",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "cajotafer",
|
||||
"name": "Carlos Fernández",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/41461969?v=4",
|
||||
"profile": "http://Cajotafer.com",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Kosai106",
|
||||
"name": "Kevin Østerkilde",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/6379824?v=4",
|
||||
"profile": "https://oesterkilde.dk/",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "aaronfulkerson",
|
||||
"name": "aaronfulkerson",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/31112737?v=4",
|
||||
"profile": "https://github.com/aaronfulkerson",
|
||||
"contributions": [
|
||||
"code",
|
||||
"question"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "alexnaiman",
|
||||
"name": "Alexandru Naiman",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/25799714?v=4",
|
||||
"profile": "https://github.com/alexnaiman",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "davidlutta",
|
||||
"name": "David Ezekiel Lutta",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/14890315?v=4",
|
||||
"profile": "https://davidlutta.github.io/portfolio/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "wanjuntham",
|
||||
"name": "wanjuntham",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/49380551?v=4",
|
||||
"profile": "https://github.com/wanjuntham",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "nahue",
|
||||
"name": "Victor Nahuel Chaves",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/96837?v=4",
|
||||
"profile": "http://www.nahuelchaves.xyz",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "peter50216",
|
||||
"name": "Peter Shih",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/891109?v=4",
|
||||
"profile": "https://github.com/peter50216",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sewerynkalemba",
|
||||
"name": "Seweryn Kalemba",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/37031328?v=4",
|
||||
"profile": "http://seweryn.kale.mba",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "nksaraf",
|
||||
"name": "Nikhil Saraf",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/11255148?v=4",
|
||||
"profile": "https://nksaraf.github.io",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "zanedb",
|
||||
"name": "Zane",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/16865690?v=4",
|
||||
"profile": "https://zane.sh",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dulcehc",
|
||||
"name": "Dulce Hernández",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/19391835?v=4",
|
||||
"profile": "https://github.com/dulcehc",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "markhaehnel",
|
||||
"name": "Mark Hähnel",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/1516205?v=4",
|
||||
"profile": "https://markhaehnel.de",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "nemesv",
|
||||
"name": "Viktor Nemes",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/251330?v=4",
|
||||
"profile": "http://stackoverflow.com/users/872395/nemesv",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "goleary",
|
||||
"name": "Gabe O'Leary",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/16123225?v=4",
|
||||
"profile": "http://gabeoleary.com",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "machadolucasvp",
|
||||
"name": "Lucas Machado",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/44952113?v=4",
|
||||
"profile": "https://github.com/machadolucasvp",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "maciekgrzybek",
|
||||
"name": "maciek_grzybek",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/16546428?v=4",
|
||||
"profile": "https://github.com/maciekgrzybek",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "mweibel",
|
||||
"name": "Michael Weibel",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/307427?v=4",
|
||||
"profile": "https://github.com/mweibel",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "isoppp",
|
||||
"name": "Hiroki Isogai",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/16318727?v=4",
|
||||
"profile": "https://isoppp.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "matamatanot",
|
||||
"name": "matamatanot",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/39780486?v=4",
|
||||
"profile": "https://github.com/matamatanot",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ericsakmar",
|
||||
"name": "Eric Sakmar",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/5620709?v=4",
|
||||
"profile": "https://github.com/ericsakmar",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "leggsimon",
|
||||
"name": "Simon Legg",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/11544418?v=4",
|
||||
"profile": "https://github.com/leggsimon",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "wobsoriano",
|
||||
"name": "Robert Soriano",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/13049130?v=4",
|
||||
"profile": "https://robsoriano.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "benediktms",
|
||||
"name": "Benedikt Schnatterbeck",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/48836135?v=4",
|
||||
"profile": "https://github.com/benediktms",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Talor-A",
|
||||
"name": "Talor Anderson",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/11509865?v=4",
|
||||
"profile": "http://taloranderson.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "akirabaruah",
|
||||
"name": "Akira Baruah",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/6751517?v=4",
|
||||
"profile": "https://github.com/akirabaruah",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "cwray-tech",
|
||||
"name": "Christopher Wray",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/53663762?v=4",
|
||||
"profile": "https://chriswray.dev/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "piotrski",
|
||||
"name": "Piotrek Tomczewski",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/244174?v=4",
|
||||
"profile": "https://github.com/piotrski",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "rap2hpoutre",
|
||||
"name": "Raphaël Huchet",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/1575946?v=4",
|
||||
"profile": "http://raph.site",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"test",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "KATT",
|
||||
"name": "Alex Johansson",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/459267?v=4",
|
||||
"profile": "http://kattcorp.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dmzza",
|
||||
"name": "David Mazza",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/120893?v=4",
|
||||
"profile": "http://davidmazza.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "rayandrews",
|
||||
"name": "Ray Andrew",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/4437323?v=4",
|
||||
"profile": "https://github.com/rayandrews",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Mzaien",
|
||||
"name": "Abdullah Mzaien",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/43112535?v=4",
|
||||
"profile": "http://Dal.Design",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "williamkwao",
|
||||
"name": "William Kwao",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/8839514?v=4",
|
||||
"profile": "http://kwao.io",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sakulstra",
|
||||
"name": "Lukas Strassel",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/4396533?v=4",
|
||||
"profile": "https://github.com/sakulstra",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "tpatel",
|
||||
"name": "Thibaut Patel",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/494686?v=4",
|
||||
"profile": "https://thibpat.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jonstuebe",
|
||||
"name": "Jon Stuebe",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/156722?v=4",
|
||||
"profile": "http://jonstuebe.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ugogo",
|
||||
"name": "Ugo Onali",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/5040476?v=4",
|
||||
"profile": "https://ugogo.dev",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "saintmalik",
|
||||
"name": "SaintMalik",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/37118134?v=4",
|
||||
"profile": "https://saintmalik.me",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Khaledgarbaya",
|
||||
"name": "Khaled Garbaya",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/1156093?v=4",
|
||||
"profile": "https://khaledgarbaya.net",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "tundera",
|
||||
"name": "tundera",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/61833561?v=4",
|
||||
"profile": "https://tundera.dev",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "markylaing",
|
||||
"name": "markylaing",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/41469221?v=4",
|
||||
"profile": "https://github.com/markylaing",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "AkifumiSato",
|
||||
"name": "Akifumi Sato",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/25711332?v=4",
|
||||
"profile": "https://akfm.dev/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "beeplin",
|
||||
"name": "Beep LIN",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/13058150?v=4",
|
||||
"profile": "https://github.com/beeplin",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "mattfwood",
|
||||
"name": "Matt Wood",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/22530815?v=4",
|
||||
"profile": "https://mattwood.tech/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jackbravo",
|
||||
"name": "Joaquin Bravo Contreras",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/15214?v=4",
|
||||
"profile": "http://joaquin.axai.mx",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "arjundubey-cr",
|
||||
"name": "Arjun Dubey",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/40758425?v=4",
|
||||
"profile": "https://github.com/arjundubey-cr",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "chanand",
|
||||
"name": "chanand",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/1317789?v=4",
|
||||
"profile": "https://github.com/chanand",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "phillipkregg",
|
||||
"name": "phillipkregg",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/1066044?v=4",
|
||||
"profile": "https://github.com/phillipkregg",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "timReynolds",
|
||||
"name": "Tim Reynolds",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/168870?v=4",
|
||||
"profile": "http://timothyreynolds.co.uk",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
29
.eslintrc.js
29
.eslintrc.js
@@ -8,7 +8,7 @@ module.exports = {
|
||||
},
|
||||
project: `./tsconfig.json`,
|
||||
},
|
||||
plugins: ["@typescript-eslint", "import", "unicorn", "simple-import-sort"],
|
||||
plugins: ["@typescript-eslint", "import", "unicorn"],
|
||||
extends: ["react-app"],
|
||||
rules: {
|
||||
"react/react-in-jsx-scope": "off", // React is always in scope with Blitz
|
||||
@@ -24,32 +24,7 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
// note you must disable the base rule as it can report incorrect errors
|
||||
"no-use-before-define": "off",
|
||||
"@typescript-eslint/no-use-before-define": ["error"],
|
||||
// note you must disable the base rule as it can report incorrect errors
|
||||
"no-redeclare": "off",
|
||||
"@typescript-eslint/no-redeclare": ["error"],
|
||||
"simple-import-sort/imports": [
|
||||
"warn",
|
||||
{
|
||||
groups: [
|
||||
[
|
||||
// Side effect imports.
|
||||
"^\\u0000",
|
||||
// Packages.
|
||||
// Things that start with a letter (or digit or underscore), or `@` followed by a letter.
|
||||
"^@?\\w",
|
||||
// Absolute imports and other imports such as Vue-style `@/foo`.
|
||||
// Anything that does not start with a dot.
|
||||
"^[^.]",
|
||||
// Relative imports.
|
||||
// Anything that starts with a dot.
|
||||
"^\\.",
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
"no-use-before-define": ["error", {functions: false, classes: false}],
|
||||
},
|
||||
ignorePatterns: ["packages/cli/", "packages/generator/templates", ".eslintrc.js"],
|
||||
overrides: [
|
||||
|
||||
9
.github/CODEOWNERS
vendored
9
.github/CODEOWNERS
vendored
@@ -1,8 +1,11 @@
|
||||
# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners
|
||||
|
||||
* @flybayer
|
||||
*.md @merelinguist
|
||||
|
||||
packages/cli/**/* @aem, @flybayer
|
||||
packages/generator/**/* @aem @flybayer
|
||||
packages/server/**/* @ryardley
|
||||
packages/file-pipeline/**/* @ryardley
|
||||
packages/cli/**/* @aem
|
||||
packages/generator/**/* @aem
|
||||
packages/generator/templates**/* @flybayer
|
||||
packages/installer/**/* @aem @flybayer
|
||||
packages/installer/**/* @aem
|
||||
|
||||
7
.github/workflows/compressed.yml
vendored
7
.github/workflows/compressed.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: CI
|
||||
name: Compressed Size
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -6,15 +6,14 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Compressed Size
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node and Yarn
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "14"
|
||||
node-version: "12.x"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn && yarn build
|
||||
|
||||
33
.github/workflows/main.yml
vendored
33
.github/workflows/main.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: CI
|
||||
name: Continuous Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -17,9 +17,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "14"
|
||||
node-version: "12.16.1"
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
@@ -32,9 +32,9 @@ jobs:
|
||||
**/node_modules
|
||||
/home/runner/.cache/Cypress
|
||||
C:\Users\runneradmin\AppData\Local\Cypress\Cache
|
||||
key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v4-${{ hashFiles('yarn.lock') }}
|
||||
key: ${{ runner.os }}-yarn-v3-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.node_version}}-yarn-v4-
|
||||
${{ runner.os }}-yarn-v3-
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile --silent
|
||||
env:
|
||||
@@ -44,18 +44,17 @@ jobs:
|
||||
env:
|
||||
CI: true
|
||||
build_and_test_pkgs:
|
||||
name: Packages Tests
|
||||
name: Build & Test Packages
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
node_version: [12, 14]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
node-version: "12.16.1"
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
@@ -67,9 +66,9 @@ jobs:
|
||||
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
/home/runner/.cache/Cypress
|
||||
C:\Users\runneradmin\AppData\Local\Cypress\Cache
|
||||
key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v4-${{ hashFiles('yarn.lock') }}
|
||||
key: ${{ runner.os }}-yarn-v2-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.node_version}}-yarn-v4-
|
||||
${{ runner.os }}-yarn-v2-
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile --silent
|
||||
env:
|
||||
@@ -86,19 +85,17 @@ jobs:
|
||||
env:
|
||||
CI: true
|
||||
build_and_test_examples:
|
||||
timeout-minutes: 30
|
||||
name: Example Apps Tests
|
||||
name: Build & Test Examples
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
node_version: [12, 14]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
node-version: "12.16.1"
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
@@ -110,9 +107,9 @@ jobs:
|
||||
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
/home/runner/.cache/Cypress
|
||||
C:\Users\runneradmin\AppData\Local\Cypress\Cache
|
||||
key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v4-${{ hashFiles('yarn.lock') }}
|
||||
key: ${{ runner.os }}-yarn-v2-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.node_version}}-yarn-v4-
|
||||
${{ runner.os }}-yarn-v2-
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile --silent
|
||||
env:
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -20,7 +20,3 @@ dist
|
||||
**/.envrc
|
||||
.blitz-*
|
||||
.blitz-cli-cache
|
||||
.vscode
|
||||
.tsbuildinfo
|
||||
.nvmrc
|
||||
**/.test*
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# .kodiak.toml
|
||||
# Minimal config. version is the only required field.
|
||||
version = 1
|
||||
merge.automerge_label = "0 - <(^_^)> - merge it! ✌️"
|
||||
approve.auto_approve_usernames = ["flybayer", "depfu"]
|
||||
@@ -1 +0,0 @@
|
||||
12.20.0
|
||||
137
README.md
137
README.md
@@ -6,7 +6,7 @@
|
||||
<img alt="" src="https://img.shields.io/badge/Join%20our%20community-6700EB.svg?style=for-the-badge&labelColor=000000&logoWidth=20&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAQ9SURBVHgB7d3dVdtAEIbhcSpICUoH0IEogQqSVBBSAU4FSSpIOoAORAfQgSghHXzZ1U/YcMD4R9rZmf2ec3y448LyiNf27iLiGIAmPLrweC9Un3DhrzG6EarLNP09nlwJ1SOZ/lQr5N80/S/p2QMVCBf5N17XCfm1Y/rBHqjAG9PPHvBsz+mf9WAP+HLA9M/YA14cOP2payH7jpj+VCtk1wnTP+vj7xCy6cTpn7EHLMLp059iD1iD8eveJbVCNsSLheX1YA/YgOWnf8YeKB3Wmf7Ud6Fy4f/FHmtpxbl3YlC4MJ/Cj0bWdwPnPbARg+L0S54XQHS32WwuxClzd4CM0z9rPfeAuTtA5ulPXYQ7wZ04Y+oOoDD9KZc9YOoOoDj9s4dwFzgXR6w1wIPoOvPWA9buAHEJ173o3gWiy3AnuBUHLEbgmYwvAk1/wuM8vAgexThzbwPDkx7/DHwVXfFOxP2GmsKd4Ab6zPeAyU8CI7AHFmH2BRCBPXAyk18GzUrqAXCTiR4ssyj0VFw/oCU8+e+RZ33AWz6KMaYbIIWxB+JSLs1bsbkeMN0AqakHvoku9oA2sAfqBvbAQdw0QArsgb25aYBUQT3QgT2gB+yBuqGcHij2UCqXDZACe2Anlw2QYg/QAOyBuoE98CL3DZDCuK4/rh/Q7oGL6U+TOvcNkJoijN8X1C48+T+g75eQDrAH/qmqAVJgDwyqaoAUe4AGYA/UDZX3QLUNkEIZPRCd5+6BahsgVUgPROwBTSijB7jpVAvGHriHvmw9wAZ4BpX1ABvgmakHtPcbRuwBTWAPULgAV9D/jKDY9YRvwvgEaurD44uQHvAol7qBW7WKluVtIHiUS7GyvA0s6CiXDnxrpQfsgbqBS7GKk/2jYHCrVlGyfxTMrVo0ALdq1Q3sgSKofh0M9oA61a+D2QM0AHugbmAPqClmSRjK2apVVQ8UsySsoK1aHdgDesCtWnUDeyCrIpeFg1u3sylyWTi3btMA7IG6gT2wuuK3hoE9sKrit4YVslWLPaAN7IG6ocKt2zmY2h4O9sDiTG0PZw/QANy6XTewBxZj9ogYVHy025LMHhEz9cBn0We6B0yfERReBLfhx0/R1YQHPx/QBPbA0VwcEwf2wNFcHBPHHjiem3MC2QPHcXdSaJjA+KfgTPQ8hhfjBzHC40mhlzJ+Xq9lK4a4PCs43AVaGTed5mZq+iOXZwWHi3AnOj2wFWNcnxYe7gTxLtBKHuamP/J+Wnh8a5irB7ZC5Yk9gPX1QuXC+usHWqGyhYvUYR0a7zboUOFCNVhnk0krZAOW7wFOvzXhom2xnEbIHizTA1wEYhWW6YFGyC6c1gOcfg9wfA80Qj7g8B7g9HuCww+haIR8wf49wOn3Cvv9k8tGyC/s7gFOv3fY3QONkH+v9MBWqB7PeqDn9FcIT//kcitUn6kHOu/T/xfWzlQy3dEHhwAAAABJRU5ErkJggg==">
|
||||
</a>
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
<a aria-label="All Contributors" href="#contributors-"><img alt="" src="https://img.shields.io/badge/all_contributors-184-17BB8A.svg?style=for-the-badge&labelColor=000000"></a>
|
||||
<a aria-label="All Contributors" href="#contributors-"><img alt="" src="https://img.shields.io/badge/all_contributors-122-17BB8A.svg?style=for-the-badge&labelColor=000000"></a>
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
<a aria-label="License" href="https://github.com/blitz-js/blitz/blob/canary/LICENSE">
|
||||
<img alt="" src="https://img.shields.io/npm/l/blitz.svg?style=for-the-badge&labelColor=000000&color=blue">
|
||||
@@ -40,9 +40,7 @@ You need Node.js 12 or newer
|
||||
|
||||
#### Install Blitz
|
||||
|
||||
Run `npm install -g blitz` or `yarn global add blitz`
|
||||
|
||||
_You can alternatively use [`npx`](https://www.npmjs.com/package/npx)_
|
||||
Run `npm install -g blitz`
|
||||
|
||||
#### Create a New App
|
||||
|
||||
@@ -53,12 +51,6 @@ _You can alternatively use [`npx`](https://www.npmjs.com/package/npx)_
|
||||
|
||||
<br><br>
|
||||
|
||||
<a aria-label="Bytes Newsletter" href="https://ui.dev/bytes/?r=blitzjs">
|
||||
<img alt="Bytes Newsletter" src="https://files-8wtskjofb.vercel.app/smarter-16x1.jpg">
|
||||
</a>
|
||||
|
||||
<br><br>
|
||||
|
||||

|
||||
|
||||
<br><br>
|
||||
@@ -124,21 +116,16 @@ Your financial contributions help ensure Blitz continues to be developed and mai
|
||||
|
||||
👉 View options and contribute at [GitHub Sponsors](https://github.com/sponsors/blitz-js), [PayPal](https://paypal.me/thebayers), or [Open Collective](https://opencollective.com/blitzjs)
|
||||
|
||||
|
||||
### 🌱 Seedling Sponsors
|
||||
|
||||
<a aria-label="React Bricks" href="https://reactbricks.com/?utm_source=blitzjs&utm_medium=sponsorship&utm_campaign=blitzjs_sponsorship">
|
||||
<img alt="" src="https://reactbricks.com/reactbricks_icon.svg" width="30px"/>
|
||||
</a>
|
||||
<a aria-label="Andreas Asprou" href="https://andreas.fyi">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/andreas.jpg" width="30px"/>
|
||||
<img alt="" src="https://reactbricks.com/reactbricks_icon.svg" width="30px">
|
||||
</a>
|
||||
|
||||
|
||||
### 🥉 Bronze Sponsors
|
||||
|
||||
<a aria-label="Render.com" href="https://render.com?utm_source=BlitzJS&utm_medium=sponsorship&utm_campaign=BlitzJS_Sponsorship_2020">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/render-logo-color2.png" width="110px">
|
||||
<a aria-label="Your Company" href="#">
|
||||
<img alt="" src="https://dummyimage.com/1000x330/efe8ff/000000.png&text=Your+Logo+Here" width="100px">
|
||||
</a>
|
||||
|
||||
### 🥈 Silver Sponsors
|
||||
@@ -149,11 +136,9 @@ Your financial contributions help ensure Blitz continues to be developed and mai
|
||||
|
||||
### 🏆 Gold Sponsors
|
||||
|
||||
<div>
|
||||
<a aria-label="G2i" href="http://g2i.co/sign-up?utm_source=blitz&utm_medium=referral&utm_campaign=blitz2020">
|
||||
<img alt="" src="https://files-5oz00y7xp.vercel.app/G2i_Logo_wwords.png" width="160px">
|
||||
<a aria-label="Your Company" href="#">
|
||||
<img alt="" src="https://dummyimage.com/1000x330/efe8ff/000000.png&text=Your+Logo+Here" width="300px">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
### 💎 Diamond Sponsors
|
||||
|
||||
@@ -169,6 +154,8 @@ Your financial contributions help ensure Blitz continues to be developed and mai
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="https://twitter.com/flybayer"><img src="https://avatars3.githubusercontent.com/u/8813276?v=4" width="100px;" alt=""/><br /><sub><b>Brandon Bayer</b></sub></a><br />Creator</td>
|
||||
<td align="center"><a href="https://medium.com/@ryardley"><img src="https://avatars0.githubusercontent.com/u/1256409?v=4" width="100px;" alt=""/><br /><sub><b>Rudi Yardley</b></sub></a><br />Node.js Wizard</td>
|
||||
<td align="center"><a href="https://merelinguist.now.sh"><img src="https://avatars3.githubusercontent.com/u/24858006?v=4" width="100px;" alt=""/><br /><sub><b>Dylan Brookes</b></sub></a><br />Friendly Generalist</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- markdownlint-enable -->
|
||||
@@ -187,7 +174,6 @@ _Code ownership, pull request approvals and merging, etc_ (see [MAINTAINERS.md](
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/aem"><img src="https://avatars0.githubusercontent.com/u/1909883?v=4" width="100px;" alt=""/><br /><sub><b>Adam Markon</b></sub></a><br />CLI</td>
|
||||
<td align="center"><a href="http://robdrosenberg.com"><img src="https://avatars0.githubusercontent.com/u/20813991?v=4" width="100px;" alt=""/><br /><sub><b>Robert Rosenberg</b></sub></a><br />Website/Docs</td>
|
||||
<td align="center"><a href="http://simonknott.de"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4" width="100px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a><br />SuperJSON</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- markdownlint-enable -->
|
||||
@@ -206,17 +192,15 @@ _Issue triage, pull request triage, community encouragement and moderation, etc_
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/LoriKarikari"><img src="https://avatars1.githubusercontent.com/u/7902980?v=4" width="100px;" alt=""/><br /><sub><b>Lori Karikari</b></sub></a></td>
|
||||
<td align="center"><a href="https://corey-brown.com"><img src="https://avatars1.githubusercontent.com/u/12791148?v=4" width="100px;" alt=""/><br /><sub><b>Corey Brown</b></sub></a></td>
|
||||
<td align="center"><a href="http://simonknott.de"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4" width="100px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a></td>
|
||||
<td align="center"><a href="http://jeremyliberman.com/"><img src="https://avatars3.githubusercontent.com/u/2754163?v=4" width="100px;" alt=""/><br /><sub><b>Jeremy Liberman</b></td>
|
||||
<td align="center"><a href="http://jagascript.com"><img src="https://avatars0.githubusercontent.com/u/4562878?v=4" width="100px;" alt=""/><br /><sub><b>Jaga Santagostino</b></sub></a></td>
|
||||
<td align="center"><a href="https://twitter.com/nitaking_"><img src="https://avatars2.githubusercontent.com/u/10850034?v=4" width="100px;" alt=""/><br /><sub><b>Satoshi Nitawaki</b></sub></a></td>
|
||||
<td align="center"><a href="https://twitter.com/sandulat"><img src="https://avatars2.githubusercontent.com/u/7345874?v=4" width="100px;" alt=""/><br /><sub><b>Alexandru Stratulat</b></sub></a></td>
|
||||
<td align="center"><a href="https://simonpeterdebbarma.com"><img src="https://avatars3.githubusercontent.com/u/31207418?v=4" width="100px;" alt=""/><br /><sub><b>Simon Debbarma</b></sub></a></td>
|
||||
<td align="center"><a href="https://github.com/jclancy93"><img src="https://avatars2.githubusercontent.com/u/7850202?v=4" width="100px;" alt=""/><br /><sub><b>Jack Clancy</b></sub></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/engelkes-finstreet"><img src="https://avatars0.githubusercontent.com/u/36962022?s=460&u=34cfc4a3d6da0a87026f6068c371779c68daa3a2&v=4" width="100px;" alt=""/><br /><sub><b>Patrick Engelkes</b></sub></a></td>
|
||||
<td align="center"><a href="https://twitter.com/jdavenport97"><img src="https://avatars2.githubusercontent.com/u/1329874?v=4" width="100px;" alt=""/><br /><sub><b>Jamie Davenport</b></sub></a></td>
|
||||
<td align="center"><a href="https://twitter.com/myrondavis"><img src="https://avatars2.githubusercontent.com/u/1430136?v=4" width="100px;" alt=""/><br /><sub><b>Myron Davis</b></sub></a></td>
|
||||
<td align="center"><a href="https://flavioander.com/"><img src="https://avatars2.githubusercontent.com/u/14948074?s=460&u=31d7ea58b5c5cd9f724d684ed578f68896c4af71&v=4" width="100px;" alt=""/><br /><sub><b>Flavio Andrade</b></sub></a></td>
|
||||
<td align="center"><a href="https://twitter.com/NaReto1125_"><img src="https://avatars.githubusercontent.com/reo777" width="100px;" alt=""/><br /><sub><b>Reo Ishiyama</b></sub></a></td>
|
||||
<td align="center"><a href="https://twitter.com/ivandevp"><img src="https://avatars3.githubusercontent.com/u/9284690?v=4" width="100px;" alt=""/><br /><sub><b>Ivan Medina</b></sub></a></td>
|
||||
<td align="center"><a href="https://twitter.com/nitaking_"><img src="https://avatars2.githubusercontent.com/u/10850034?v=4" width="100px;" alt=""/><br /><sub><b>Satoshi Nitawaki</b></sub></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- markdownlint-enable -->
|
||||
@@ -276,7 +260,7 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
|
||||
<td align="center"><a href="https://mikeattara.com"><img src="https://avatars1.githubusercontent.com/u/31483629?v=4" width="100px;" alt=""/><br /><sub><b>Mike Perry Y Attara</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mikeattara" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://devanthe.dev"><img src="https://avatars0.githubusercontent.com/u/354652?v=4" width="100px;" alt=""/><br /><sub><b>Devan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=DevanB" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jclancy93"><img src="https://avatars2.githubusercontent.com/u/7850202?v=4" width="100px;" alt=""/><br /><sub><b>Jack Clancy</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jclancy93" title="Code">💻</a> <a href="#maintenance-jclancy93" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="https://github.com/ntgussoni"><img src="https://avatars0.githubusercontent.com/u/10161067?v=4" width="100px;" alt=""/><br /><sub><b>Nicolas Torres</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ntgussoni" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=ntgussoni" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Antgussoni" title="Reviewed Pull Requests">👀</a></td>
|
||||
<td align="center"><a href="https://github.com/ntgussoni"><img src="https://avatars0.githubusercontent.com/u/10161067?v=4" width="100px;" alt=""/><br /><sub><b>Nicolas Torres</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ntgussoni" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=ntgussoni" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://simonknott.de"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4" width="100px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Skn0tt" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=Skn0tt" title="Tests">⚠️</a> <a href="#maintenance-Skn0tt" title="Maintenance">🚧</a></td>
|
||||
@@ -311,7 +295,7 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
|
||||
<td align="center"><a href="https://kevo.dev"><img src="https://avatars3.githubusercontent.com/u/15717067?v=4" width="100px;" alt=""/><br /><sub><b>Kevin Tovar</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kevotovar" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://anteprimorac.com.hr"><img src="https://avatars0.githubusercontent.com/u/972083?v=4" width="100px;" alt=""/><br /><sub><b>Ante Primorac</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=anteprimorac" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=anteprimorac" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://mykalmachon.dev"><img src="https://avatars1.githubusercontent.com/u/7844994?v=4" width="100px;" alt=""/><br /><sub><b>Mykal Machon</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=MykalMachon" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://jamiedavenport.dev"><img src="https://avatars2.githubusercontent.com/u/1329874?v=4" width="100px;" alt=""/><br /><sub><b>Jamie Davenport</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jamiedavenport" title="Code">💻</a> <a href="#maintenance-jamiedavenport" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="https://jamiedavenport.dev"><img src="https://avatars2.githubusercontent.com/u/1329874?v=4" width="100px;" alt=""/><br /><sub><b>Jamie Davenport</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jamiedavenport" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://cloudnweb.dev/"><img src="https://avatars0.githubusercontent.com/u/17050715?v=4" width="100px;" alt=""/><br /><sub><b>GaneshMani</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ganeshmani" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -362,11 +346,11 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/jschepmans"><img src="https://avatars2.githubusercontent.com/u/5782977?v=4" width="100px;" alt=""/><br /><sub><b>Johan Schepmans</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jschepmans" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://twitter.com/dillonraphael"><img src="https://avatars0.githubusercontent.com/u/3496193?v=4" width="100px;" alt=""/><br /><sub><b>Dillon Raphael</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dillonraphael" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/clgeoio"><img src="https://avatars2.githubusercontent.com/u/37571416?v=4" width="100px;" alt=""/><br /><sub><b>Cody G</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=clgeoio" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=clgeoio" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/clgeoio"><img src="https://avatars2.githubusercontent.com/u/37571416?v=4" width="100px;" alt=""/><br /><sub><b>Cody G</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=clgeoio" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/madflow"><img src="https://avatars0.githubusercontent.com/u/183248?v=4" width="100px;" alt=""/><br /><sub><b>madflow</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=madflow" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://twitter.com/nitaking_"><img src="https://avatars2.githubusercontent.com/u/10850034?v=4" width="100px;" alt=""/><br /><sub><b>Satoshi Nitawaki</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nitaking" title="Code">💻</a> <a href="#maintenance-nitaking" title="Maintenance">🚧</a> <a href="#question-nitaking" title="Answering Questions">💬</a> <a href="https://github.com/blitz-js/blitz/commits?author=nitaking" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sirmyron"><img src="https://avatars2.githubusercontent.com/u/1430136?v=4" width="100px;" alt=""/><br /><sub><b>sirmyron</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sirmyron" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=sirmyron" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/engelkes-finstreet"><img src="https://avatars1.githubusercontent.com/u/36962022?v=4" width="100px;" alt=""/><br /><sub><b>engelkes-finstreet</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=engelkes-finstreet" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=engelkes-finstreet" title="Code">💻</a> <a href="#maintenance-engelkes-finstreet" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="https://twitter.com/nitaking_"><img src="https://avatars2.githubusercontent.com/u/10850034?v=4" width="100px;" alt=""/><br /><sub><b>Satoshi Nitawaki</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nitaking" title="Code">💻</a> <a href="#maintenance-nitaking" title="Maintenance">🚧</a> <a href="#question-nitaking" title="Answering Questions">💬</a></td>
|
||||
<td align="center"><a href="https://github.com/sirmyron"><img src="https://avatars2.githubusercontent.com/u/1430136?v=4" width="100px;" alt=""/><br /><sub><b>sirmyron</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sirmyron" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/engelkes-finstreet"><img src="https://avatars1.githubusercontent.com/u/36962022?v=4" width="100px;" alt=""/><br /><sub><b>engelkes-finstreet</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=engelkes-finstreet" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=engelkes-finstreet" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://twitter.com/pixelscommander"><img src="https://avatars2.githubusercontent.com/u/810671?v=4" width="100px;" alt=""/><br /><sub><b>Denis Radin</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3APixelsCommander" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=PixelsCommander" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=PixelsCommander" title="Documentation">📖</a></td>
|
||||
@@ -390,92 +374,11 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
|
||||
<td align="center"><a href="http://enricoschaaf.com"><img src="https://avatars1.githubusercontent.com/u/54645197?v=4" width="100px;" alt=""/><br /><sub><b>Enrico Schaaf</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=enricoschaaf" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://kitze.io"><img src="https://avatars0.githubusercontent.com/u/1160594?v=4" width="100px;" alt=""/><br /><sub><b>Kitze</b></sub></a><br /><a href="#ideas-kitze" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://github.com/drmas"><img src="https://avatars3.githubusercontent.com/u/644440?v=4" width="100px;" alt=""/><br /><sub><b>Mohamed Shaban</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=drmas" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/jorisre"><img src="https://avatars1.githubusercontent.com/u/7545547?v=4" width="100px;" alt=""/><br /><sub><b>Joris</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jorisre" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Kamshak"><img src="https://avatars3.githubusercontent.com/u/337968?v=4" width="100px;" alt=""/><br /><sub><b>Valentin Funk</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Kamshak" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://lukebennett.com"><img src="https://avatars1.githubusercontent.com/u/135390?v=4" width="100px;" alt=""/><br /><sub><b>Luke Bennett</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lukebennett" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://haseebmajid.dev"><img src="https://avatars0.githubusercontent.com/u/998807?v=4" width="100px;" alt=""/><br /><sub><b>Haseeb Majid</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=hmajid2301" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/phillippschmedt"><img src="https://avatars0.githubusercontent.com/u/16028406?v=4" width="100px;" alt=""/><br /><sub><b>Phillipp Schmedt</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=phillippschmedt" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://haspar.us"><img src="https://avatars0.githubusercontent.com/u/15332326?v=4" width="100px;" alt=""/><br /><sub><b>Piotr Monwid-Olechnowicz</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=hasparus" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://mizchi.dev"><img src="https://avatars2.githubusercontent.com/u/73962?v=4" width="100px;" alt=""/><br /><sub><b>Kotaro Chikuba</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mizchi" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=mizchi" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/konradkalemba"><img src="https://avatars0.githubusercontent.com/u/8682104?v=4" width="100px;" alt=""/><br /><sub><b>Konrad Kalemba</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=konradkalemba" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Alucard17"><img src="https://avatars1.githubusercontent.com/u/26205172?v=4" width="100px;" alt=""/><br /><sub><b>Alucard17</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Alucard17" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Dohxis"><img src="https://avatars2.githubusercontent.com/u/8768909?v=4" width="100px;" alt=""/><br /><sub><b>Domantas Mauruča</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Dohxis" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=Dohxis" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://sandulat.com/"><img src="https://avatars0.githubusercontent.com/u/7345874?v=4" width="100px;" alt=""/><br /><sub><b>Stratulat Alexandru</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sandulat" title="Code">💻</a> <a href="#maintenance-sandulat" title="Maintenance">🚧</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/aericson"><img src="https://avatars3.githubusercontent.com/u/692542?v=4" width="100px;" alt=""/><br /><sub><b>André Ericson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=aericson" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=aericson" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://Cajotafer.com"><img src="https://avatars2.githubusercontent.com/u/41461969?v=4" width="100px;" alt=""/><br /><sub><b>Carlos Fernández</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cajotafer" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://oesterkilde.dk/"><img src="https://avatars1.githubusercontent.com/u/6379824?v=4" width="100px;" alt=""/><br /><sub><b>Kevin Østerkilde</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Kosai106" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=Kosai106" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/aaronfulkerson"><img src="https://avatars0.githubusercontent.com/u/31112737?v=4" width="100px;" alt=""/><br /><sub><b>aaronfulkerson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=aaronfulkerson" title="Code">💻</a> <a href="#question-aaronfulkerson" title="Answering Questions">💬</a></td>
|
||||
<td align="center"><a href="https://github.com/alexnaiman"><img src="https://avatars3.githubusercontent.com/u/25799714?v=4" width="100px;" alt=""/><br /><sub><b>Alexandru Naiman</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=alexnaiman" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://davidlutta.github.io/portfolio/"><img src="https://avatars2.githubusercontent.com/u/14890315?v=4" width="100px;" alt=""/><br /><sub><b>David Ezekiel Lutta</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=davidlutta" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/wanjuntham"><img src="https://avatars1.githubusercontent.com/u/49380551?v=4" width="100px;" alt=""/><br /><sub><b>wanjuntham</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=wanjuntham" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://www.nahuelchaves.xyz"><img src="https://avatars3.githubusercontent.com/u/96837?v=4" width="100px;" alt=""/><br /><sub><b>Victor Nahuel Chaves</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nahue" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/peter50216"><img src="https://avatars3.githubusercontent.com/u/891109?v=4" width="100px;" alt=""/><br /><sub><b>Peter Shih</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=peter50216" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://seweryn.kale.mba"><img src="https://avatars3.githubusercontent.com/u/37031328?v=4" width="100px;" alt=""/><br /><sub><b>Seweryn Kalemba</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sewerynkalemba" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://nksaraf.github.io"><img src="https://avatars2.githubusercontent.com/u/11255148?v=4" width="100px;" alt=""/><br /><sub><b>Nikhil Saraf</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nksaraf" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=nksaraf" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://zane.sh"><img src="https://avatars0.githubusercontent.com/u/16865690?v=4" width="100px;" alt=""/><br /><sub><b>Zane</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=zanedb" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/dulcehc"><img src="https://avatars1.githubusercontent.com/u/19391835?v=4" width="100px;" alt=""/><br /><sub><b>Dulce Hernández</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dulcehc" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://markhaehnel.de"><img src="https://avatars2.githubusercontent.com/u/1516205?v=4" width="100px;" alt=""/><br /><sub><b>Mark Hähnel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=markhaehnel" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="http://stackoverflow.com/users/872395/nemesv"><img src="https://avatars0.githubusercontent.com/u/251330?v=4" width="100px;" alt=""/><br /><sub><b>Viktor Nemes</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nemesv" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://gabeoleary.com"><img src="https://avatars1.githubusercontent.com/u/16123225?v=4" width="100px;" alt=""/><br /><sub><b>Gabe O'Leary</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=goleary" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/machadolucasvp"><img src="https://avatars0.githubusercontent.com/u/44952113?v=4" width="100px;" alt=""/><br /><sub><b>Lucas Machado</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=machadolucasvp" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/maciekgrzybek"><img src="https://avatars2.githubusercontent.com/u/16546428?v=4" width="100px;" alt=""/><br /><sub><b>maciek_grzybek</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=maciekgrzybek" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/mweibel"><img src="https://avatars1.githubusercontent.com/u/307427?v=4" width="100px;" alt=""/><br /><sub><b>Michael Weibel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mweibel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://isoppp.com"><img src="https://avatars0.githubusercontent.com/u/16318727?v=4" width="100px;" alt=""/><br /><sub><b>Hiroki Isogai</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=isoppp" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/matamatanot"><img src="https://avatars2.githubusercontent.com/u/39780486?v=4" width="100px;" alt=""/><br /><sub><b>matamatanot</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=matamatanot" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/ericsakmar"><img src="https://avatars3.githubusercontent.com/u/5620709?v=4" width="100px;" alt=""/><br /><sub><b>Eric Sakmar</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ericsakmar" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/leggsimon"><img src="https://avatars2.githubusercontent.com/u/11544418?v=4" width="100px;" alt=""/><br /><sub><b>Simon Legg</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=leggsimon" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://robsoriano.com"><img src="https://avatars3.githubusercontent.com/u/13049130?v=4" width="100px;" alt=""/><br /><sub><b>Robert Soriano</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=wobsoriano" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/benediktms"><img src="https://avatars2.githubusercontent.com/u/48836135?v=4" width="100px;" alt=""/><br /><sub><b>Benedikt Schnatterbeck</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=benediktms" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://taloranderson.com"><img src="https://avatars2.githubusercontent.com/u/11509865?v=4" width="100px;" alt=""/><br /><sub><b>Talor Anderson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Talor-A" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/akirabaruah"><img src="https://avatars2.githubusercontent.com/u/6751517?v=4" width="100px;" alt=""/><br /><sub><b>Akira Baruah</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=akirabaruah" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://chriswray.dev/"><img src="https://avatars0.githubusercontent.com/u/53663762?v=4" width="100px;" alt=""/><br /><sub><b>Christopher Wray</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cwray-tech" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/piotrski"><img src="https://avatars0.githubusercontent.com/u/244174?v=4" width="100px;" alt=""/><br /><sub><b>Piotrek Tomczewski</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=piotrski" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://raph.site"><img src="https://avatars3.githubusercontent.com/u/1575946?v=4" width="100px;" alt=""/><br /><sub><b>Raphaël Huchet</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rap2hpoutre" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=rap2hpoutre" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=rap2hpoutre" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://kattcorp.com"><img src="https://avatars1.githubusercontent.com/u/459267?v=4" width="100px;" alt=""/><br /><sub><b>Alex Johansson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=KATT" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://davidmazza.com"><img src="https://avatars0.githubusercontent.com/u/120893?v=4" width="100px;" alt=""/><br /><sub><b>David Mazza</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dmzza" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/rayandrews"><img src="https://avatars1.githubusercontent.com/u/4437323?v=4" width="100px;" alt=""/><br /><sub><b>Ray Andrew</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rayandrews" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=rayandrews" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://Dal.Design"><img src="https://avatars3.githubusercontent.com/u/43112535?v=4" width="100px;" alt=""/><br /><sub><b>Abdullah Mzaien</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Mzaien" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://kwao.io"><img src="https://avatars2.githubusercontent.com/u/8839514?v=4" width="100px;" alt=""/><br /><sub><b>William Kwao</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=williamkwao" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/sakulstra"><img src="https://avatars3.githubusercontent.com/u/4396533?v=4" width="100px;" alt=""/><br /><sub><b>Lukas Strassel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sakulstra" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://thibpat.com"><img src="https://avatars3.githubusercontent.com/u/494686?v=4" width="100px;" alt=""/><br /><sub><b>Thibaut Patel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tpatel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://jonstuebe.com"><img src="https://avatars0.githubusercontent.com/u/156722?v=4" width="100px;" alt=""/><br /><sub><b>Jon Stuebe</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jonstuebe" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://ugogo.dev"><img src="https://avatars2.githubusercontent.com/u/5040476?v=4" width="100px;" alt=""/><br /><sub><b>Ugo Onali</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ugogo" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://saintmalik.me"><img src="https://avatars1.githubusercontent.com/u/37118134?v=4" width="100px;" alt=""/><br /><sub><b>SaintMalik</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=saintmalik" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://khaledgarbaya.net"><img src="https://avatars1.githubusercontent.com/u/1156093?v=4" width="100px;" alt=""/><br /><sub><b>Khaled Garbaya</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Khaledgarbaya" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://tundera.dev"><img src="https://avatars0.githubusercontent.com/u/61833561?v=4" width="100px;" alt=""/><br /><sub><b>tundera</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tundera" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/markylaing"><img src="https://avatars2.githubusercontent.com/u/41469221?v=4" width="100px;" alt=""/><br /><sub><b>markylaing</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=markylaing" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=markylaing" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://akfm.dev/"><img src="https://avatars2.githubusercontent.com/u/25711332?v=4" width="100px;" alt=""/><br /><sub><b>Akifumi Sato</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=AkifumiSato" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/beeplin"><img src="https://avatars3.githubusercontent.com/u/13058150?v=4" width="100px;" alt=""/><br /><sub><b>Beep LIN</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=beeplin" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://mattwood.tech/"><img src="https://avatars1.githubusercontent.com/u/22530815?v=4" width="100px;" alt=""/><br /><sub><b>Matt Wood</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mattfwood" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://joaquin.axai.mx"><img src="https://avatars1.githubusercontent.com/u/15214?v=4" width="100px;" alt=""/><br /><sub><b>Joaquin Bravo Contreras</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jackbravo" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/arjundubey-cr"><img src="https://avatars0.githubusercontent.com/u/40758425?v=4" width="100px;" alt=""/><br /><sub><b>Arjun Dubey</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=arjundubey-cr" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/chanand"><img src="https://avatars0.githubusercontent.com/u/1317789?v=4" width="100px;" alt=""/><br /><sub><b>chanand</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=chanand" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/phillipkregg"><img src="https://avatars0.githubusercontent.com/u/1066044?v=4" width="100px;" alt=""/><br /><sub><b>phillipkregg</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=phillipkregg" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://timothyreynolds.co.uk"><img src="https://avatars1.githubusercontent.com/u/168870?v=4" width="100px;" alt=""/><br /><sub><b>Tim Reynolds</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=timReynolds" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-enable -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.9 KiB |
@@ -23,115 +23,109 @@ assert(process.env.AUTH0_CLIENT_SECRET, "You must provide the AUTH0_CLIENT_SECRE
|
||||
|
||||
export default passportAuth({
|
||||
successRedirectUrl: "/",
|
||||
authenticateOptions: {scope: "openid email profile"}, //used for Auth0Strategy - without an empty profile is returned
|
||||
strategies: [
|
||||
{
|
||||
strategy: new TwitterStrategy(
|
||||
{
|
||||
consumerKey: process.env.TWITTER_CONSUMER_KEY,
|
||||
consumerSecret: process.env.TWITTER_CONSUMER_SECRET,
|
||||
callbackURL:
|
||||
process.env.NODE_ENV === "production"
|
||||
? "https://auth-example-flybayer.blitzjs.vercel.app/api/auth/twitter/callback"
|
||||
: "http://localhost:3000/api/auth/twitter/callback",
|
||||
includeEmail: true,
|
||||
},
|
||||
async function (_token, _tokenSecret, profile, done) {
|
||||
const email = profile.emails && profile.emails[0]?.value
|
||||
new TwitterStrategy(
|
||||
{
|
||||
consumerKey: process.env.TWITTER_CONSUMER_KEY,
|
||||
consumerSecret: process.env.TWITTER_CONSUMER_SECRET,
|
||||
callbackURL:
|
||||
process.env.NODE_ENV === "production"
|
||||
? "https://auth-example-flybayer.blitzjs.vercel.app/api/auth/twitter/callback"
|
||||
: "http://localhost:3000/api/auth/twitter/callback",
|
||||
includeEmail: true,
|
||||
},
|
||||
async function (_token, _tokenSecret, profile, done) {
|
||||
const email = profile.emails && profile.emails[0]?.value
|
||||
|
||||
if (!email) {
|
||||
// This can happen if you haven't enabled email access in your twitter app permissions
|
||||
return done(new Error("Twitter OAuth response doesn't have email."))
|
||||
}
|
||||
if (!email) {
|
||||
// This can happen if you haven't enabled email access in your twitter app permissions
|
||||
return done(new Error("Twitter OAuth response doesn't have email."))
|
||||
}
|
||||
|
||||
const user = await db.user.upsert({
|
||||
where: {email},
|
||||
create: {
|
||||
email,
|
||||
name: profile.displayName,
|
||||
},
|
||||
update: {email},
|
||||
})
|
||||
const user = await db.user.upsert({
|
||||
where: {email},
|
||||
create: {
|
||||
email,
|
||||
name: profile.displayName,
|
||||
},
|
||||
update: {email},
|
||||
})
|
||||
|
||||
const publicData = {userId: user.id, roles: [user.role], source: "twitter"}
|
||||
done(null, {publicData})
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
strategy: new GitHubStrategy(
|
||||
{
|
||||
clientID: process.env.GITHUB_CLIENT_ID,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
||||
callbackURL:
|
||||
process.env.NODE_ENV === "production"
|
||||
? "https://auth-example-flybayer.blitzjs.vercel.app/api/auth/github/callback"
|
||||
: "http://localhost:3000/api/auth/github/callback",
|
||||
},
|
||||
async function (_token, _tokenSecret, profile, done) {
|
||||
const email = profile.emails && profile.emails[0]?.value
|
||||
const publicData = {userId: user.id, roles: [user.role], source: "twitter"}
|
||||
done(null, {publicData})
|
||||
},
|
||||
),
|
||||
new GitHubStrategy(
|
||||
{
|
||||
clientID: process.env.GITHUB_CLIENT_ID,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
||||
callbackURL:
|
||||
process.env.NODE_ENV === "production"
|
||||
? "https://auth-example-flybayer.blitzjs.vercel.app/api/auth/github/callback"
|
||||
: "http://localhost:3000/api/auth/github/callback",
|
||||
},
|
||||
async function (_token, _tokenSecret, profile, done) {
|
||||
const email = profile.emails && profile.emails[0]?.value
|
||||
|
||||
if (!email) {
|
||||
// This can happen if you haven't enabled email access in your twitter app permissions
|
||||
return done(new Error("Twitter OAuth response doesn't have email."))
|
||||
}
|
||||
if (!email) {
|
||||
// This can happen if you haven't enabled email access in your twitter app permissions
|
||||
return done(new Error("Twitter OAuth response doesn't have email."))
|
||||
}
|
||||
|
||||
const user = await db.user.upsert({
|
||||
where: {email},
|
||||
create: {
|
||||
email,
|
||||
name: profile.displayName,
|
||||
},
|
||||
update: {email},
|
||||
})
|
||||
const user = await db.user.upsert({
|
||||
where: {email},
|
||||
create: {
|
||||
email,
|
||||
name: profile.displayName,
|
||||
},
|
||||
update: {email},
|
||||
})
|
||||
|
||||
const publicData = {
|
||||
userId: user.id,
|
||||
roles: [user.role],
|
||||
source: "github",
|
||||
githubUsername: profile.username,
|
||||
}
|
||||
done(null, {publicData})
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
authenticateOptions: {scope: "openid email profile"},
|
||||
strategy: new Auth0Strategy(
|
||||
{
|
||||
domain: process.env.AUTH0_DOMAIN,
|
||||
clientID: process.env.AUTH0_CLIENT_ID,
|
||||
clientSecret: process.env.AUTH0_CLIENT_SECRET,
|
||||
callbackURL:
|
||||
process.env.NODE_ENV === "production"
|
||||
? "https://auth-example-flybayer.blitzjs.vercel.app/api/auth/auth0/callback"
|
||||
: "http://localhost:3000/api/auth/auth0/callback",
|
||||
},
|
||||
async function (_token, _tokenSecret, extraParams, profile, done) {
|
||||
const email = profile.emails && profile.emails[0]?.value
|
||||
const publicData = {
|
||||
userId: user.id,
|
||||
roles: [user.role],
|
||||
source: "github",
|
||||
githubUsername: profile.username,
|
||||
}
|
||||
done(null, {publicData})
|
||||
},
|
||||
),
|
||||
new Auth0Strategy(
|
||||
{
|
||||
domain: process.env.AUTH0_DOMAIN,
|
||||
clientID: process.env.AUTH0_CLIENT_ID,
|
||||
clientSecret: process.env.AUTH0_CLIENT_SECRET,
|
||||
callbackURL:
|
||||
process.env.NODE_ENV === "production"
|
||||
? "https://auth-example-flybayer.blitzjs.vercel.app/api/auth/auth0/callback"
|
||||
: "http://localhost:3000/api/auth/auth0/callback",
|
||||
},
|
||||
async function (_token, _tokenSecret, extraParams, profile, done) {
|
||||
const email = profile.emails && profile.emails[0]?.value
|
||||
|
||||
if (!email) {
|
||||
// This can happen if you haven't enabled email access in your twitter app permissions
|
||||
return done(new Error("Auth0 response doesn't have email."))
|
||||
}
|
||||
if (!email) {
|
||||
// This can happen if you haven't enabled email access in your twitter app permissions
|
||||
return done(new Error("GitHub OAuth response doesn't have email."))
|
||||
}
|
||||
|
||||
const user = await db.user.upsert({
|
||||
where: {email},
|
||||
create: {
|
||||
email,
|
||||
name: profile.displayName,
|
||||
},
|
||||
update: {email},
|
||||
})
|
||||
const user = await db.user.upsert({
|
||||
where: {email},
|
||||
create: {
|
||||
email,
|
||||
name: profile.displayName,
|
||||
},
|
||||
update: {email},
|
||||
})
|
||||
|
||||
const publicData = {
|
||||
userId: user.id,
|
||||
roles: [user.role],
|
||||
source: "auth0",
|
||||
githubUsername: profile.username,
|
||||
}
|
||||
done(undefined, {publicData})
|
||||
},
|
||||
),
|
||||
},
|
||||
const publicData = {
|
||||
userId: user.id,
|
||||
roles: [user.role],
|
||||
source: "auth0",
|
||||
githubUsername: profile.username,
|
||||
}
|
||||
done(undefined, {publicData})
|
||||
},
|
||||
),
|
||||
],
|
||||
})
|
||||
|
||||
@@ -2,15 +2,15 @@ import {AuthenticationError} from "blitz"
|
||||
import SecurePassword from "secure-password"
|
||||
import db from "db"
|
||||
|
||||
const SP = () => new SecurePassword()
|
||||
const SP = new SecurePassword()
|
||||
|
||||
export const hashPassword = async (password: string) => {
|
||||
const hashedBuffer = await SP().hash(Buffer.from(password))
|
||||
const hashedBuffer = await SP.hash(Buffer.from(password))
|
||||
return hashedBuffer.toString("base64")
|
||||
}
|
||||
export const verifyPassword = async (hashedPassword: string, password: string) => {
|
||||
try {
|
||||
return await SP().verify(Buffer.from(password), Buffer.from(hashedPassword, "base64"))
|
||||
return await SP.verify(Buffer.from(password), Buffer.from(hashedPassword, "base64"))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return false
|
||||
@@ -18,7 +18,7 @@ export const verifyPassword = async (hashedPassword: string, password: string) =
|
||||
}
|
||||
|
||||
export const authenticateUser = async (email: string, password: string) => {
|
||||
const user = await db().user.findFirst({where: {email}})
|
||||
const user = await db.user.findOne({where: {email}})
|
||||
|
||||
if (!user || !user.hashedPassword) throw new AuthenticationError()
|
||||
|
||||
@@ -28,7 +28,7 @@ export const authenticateUser = async (email: string, password: string) => {
|
||||
case SecurePassword.VALID_NEEDS_REHASH:
|
||||
// Upgrade hashed password with a more secure hash
|
||||
const improvedHash = await hashPassword(password)
|
||||
await db().user.update({where: {id: user.id}, data: {hashedPassword: improvedHash}})
|
||||
await db.user.update({where: {id: user.id}, data: {hashedPassword: improvedHash}})
|
||||
break
|
||||
default:
|
||||
throw new AuthenticationError()
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
import {Link, useMutation, AuthenticationError} from "blitz"
|
||||
import React from "react"
|
||||
import {Link} from "blitz"
|
||||
import {LabeledTextField} from "app/components/LabeledTextField"
|
||||
import {Form, FORM_ERROR} from "app/components/Form"
|
||||
import login, {LoginInput} from "app/auth/mutations/login"
|
||||
import login from "app/auth/mutations/login"
|
||||
import {LoginInput} from "app/auth/validations"
|
||||
|
||||
type LoginFormProps = {
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
export const LoginForm = (props: LoginFormProps) => {
|
||||
const [loginMutation] = useMutation(login)
|
||||
return (
|
||||
<div>
|
||||
<h1>Login</h1>
|
||||
<Form
|
||||
submitText="Login"
|
||||
submitText="Log In"
|
||||
schema={LoginInput}
|
||||
initialValues={{email: undefined, password: undefined}}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await loginMutation(values)
|
||||
await login({email: values.email, password: values.password})
|
||||
props.onSuccess && props.onSuccess()
|
||||
} catch (error) {
|
||||
if (error instanceof AuthenticationError) {
|
||||
if (error.name === "AuthenticationError") {
|
||||
return {[FORM_ERROR]: "Sorry, those credentials are invalid"}
|
||||
} else {
|
||||
return {
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
import {Ctx} from "blitz"
|
||||
import {SessionContext} from "blitz"
|
||||
import {authenticateUser} from "app/auth/auth-utils"
|
||||
import * as z from "zod"
|
||||
import {LoginInput, LoginInputType} from "../validations"
|
||||
|
||||
export const LoginInput = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string(),
|
||||
})
|
||||
export type LoginInputType = z.infer<typeof LoginInput>
|
||||
|
||||
export default async function login(input: LoginInputType, {session}: Ctx) {
|
||||
export default async function login(input: LoginInputType, ctx: {session?: SessionContext} = {}) {
|
||||
// This throws an error if input is invalid
|
||||
const {email, password} = LoginInput.parse(input)
|
||||
|
||||
// This throws an error if credentials are invalid
|
||||
const user = await authenticateUser(email, password)
|
||||
|
||||
await session.create({userId: user.id, roles: [user.role]})
|
||||
await ctx.session!.create({userId: user.id, roles: [user.role]})
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {Ctx} from "blitz"
|
||||
import {SessionContext} from "blitz"
|
||||
|
||||
export default async function logout(_: any, {session}: Ctx) {
|
||||
return await session.revoke()
|
||||
export default async function logout(_ = null, ctx: {session?: SessionContext} = {}) {
|
||||
return await ctx.session!.revoke()
|
||||
}
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
import {Ctx} from "blitz"
|
||||
import db from "db"
|
||||
import {SessionContext} from "blitz"
|
||||
import {hashPassword} from "app/auth/auth-utils"
|
||||
import * as z from "zod"
|
||||
import {SignupInput, SignupInputType} from "app/auth/validations"
|
||||
|
||||
export const SignupInput = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(10).max(100),
|
||||
})
|
||||
export type SignupInputType = z.infer<typeof SignupInput>
|
||||
|
||||
export default async function signup(input: SignupInputType, {session}: Ctx) {
|
||||
export default async function signup(input: SignupInputType, ctx: {session?: SessionContext} = {}) {
|
||||
// This throws an error if input is invalid
|
||||
const {email, password} = SignupInput.parse(input)
|
||||
|
||||
const hashedPassword = await hashPassword(password)
|
||||
const user = await db().user.create({
|
||||
const user = await db.user.create({
|
||||
data: {email, hashedPassword, role: "user"},
|
||||
select: {id: true, name: true, email: true, role: true},
|
||||
})
|
||||
|
||||
await session.create({userId: user.id, roles: [user.role]})
|
||||
await ctx.session!.create({userId: user.id, roles: [user.role]})
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import {Head, useRouter, BlitzPage} from "blitz"
|
||||
import {LoginForm} from "app/auth/components/LoginForm"
|
||||
|
||||
@@ -7,7 +8,7 @@ const SignupPage: BlitzPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Login</title>
|
||||
<title>Log In</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import {Head, useRouter, BlitzPage, useMutation} from "blitz"
|
||||
import React from "react"
|
||||
import {Head, useRouter, BlitzPage} from "blitz"
|
||||
import {Form, FORM_ERROR} from "app/components/Form"
|
||||
import {LabeledTextField} from "app/components/LabeledTextField"
|
||||
import signup, {SignupInput} from "app/auth/mutations/signup"
|
||||
import signup from "app/auth/mutations/signup"
|
||||
import {SignupInput} from "app/auth/validations"
|
||||
|
||||
const SignupPage: BlitzPage = () => {
|
||||
const router = useRouter()
|
||||
const [signupMutation] = useMutation(signup)
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -22,7 +23,7 @@ const SignupPage: BlitzPage = () => {
|
||||
schema={SignupInput}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await signupMutation(values)
|
||||
await signup({email: values.email, password: values.password})
|
||||
router.push("/")
|
||||
} catch (error) {
|
||||
if (error.code === "P2002" && error.meta?.target?.includes("email")) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ReactNode, PropsWithoutRef} from "react"
|
||||
import React, {ReactNode, PropsWithoutRef} from "react"
|
||||
import {Form as FinalForm, FormProps as FinalFormProps} from "react-final-form"
|
||||
import * as z from "zod"
|
||||
export {FORM_ERROR} from "final-form"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {forwardRef, PropsWithoutRef} from "react"
|
||||
import React, {PropsWithoutRef} from "react"
|
||||
import {useField} from "react-final-form"
|
||||
|
||||
export interface LabeledTextFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["input"]> {
|
||||
@@ -11,7 +11,7 @@ export interface LabeledTextFieldProps extends PropsWithoutRef<JSX.IntrinsicElem
|
||||
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
|
||||
}
|
||||
|
||||
export const LabeledTextField = forwardRef<HTMLInputElement, LabeledTextFieldProps>(
|
||||
export const LabeledTextField = React.forwardRef<HTMLInputElement, LabeledTextFieldProps>(
|
||||
({name, label, outerProps, ...props}, ref) => {
|
||||
const {
|
||||
input,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import {useQuery} from "blitz"
|
||||
import {useQuery, useSession} from "blitz"
|
||||
import getCurrentUser from "app/users/queries/getCurrentUser"
|
||||
|
||||
export const useCurrentUser = () => {
|
||||
const [user] = useQuery(getCurrentUser, null)
|
||||
return user
|
||||
// We wouldn't have to useSession() here, but doing so improves perf on initial
|
||||
// load since we can skip the getCurrentUser() request.
|
||||
const session = useSession()
|
||||
const [user] = useQuery(getCurrentUser, null, {enabled: !!session.userId})
|
||||
return session.userId ? user : null
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import {useSession, useRouter, useMutation} from "blitz"
|
||||
import {useSession, useRouter} from "blitz"
|
||||
import logout from "app/auth/mutations/logout"
|
||||
|
||||
export default function Layout({children}: {children: React.ReactNode}) {
|
||||
const session = useSession()
|
||||
const router = useRouter()
|
||||
const [logoutMutation] = useMutation(logout)
|
||||
return (
|
||||
<div>
|
||||
{session.userId && (
|
||||
<button
|
||||
onClick={async () => {
|
||||
router.push("/")
|
||||
await logoutMutation()
|
||||
await logout()
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import {AppProps, ErrorComponent, useRouter, AuthenticationError, AuthorizationError} from "blitz"
|
||||
import {AppProps, ErrorComponent, useRouter} from "blitz"
|
||||
import {ErrorBoundary} from "react-error-boundary"
|
||||
import {queryCache} from "react-query"
|
||||
import LoginForm from "app/auth/components/LoginForm"
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window["DEBUG_BLITZ"] = 1
|
||||
}
|
||||
|
||||
export default function App({Component, pageProps}: AppProps) {
|
||||
const router = useRouter()
|
||||
return (
|
||||
@@ -25,9 +21,9 @@ export default function App({Component, pageProps}: AppProps) {
|
||||
}
|
||||
|
||||
function RootErrorFallback({error, resetErrorBoundary}) {
|
||||
if (error instanceof AuthenticationError) {
|
||||
if (error.name === "AuthenticationError") {
|
||||
return <LoginForm onSuccess={resetErrorBoundary} />
|
||||
} else if (error instanceof AuthorizationError) {
|
||||
} else if (error.name === "AuthorizationError") {
|
||||
return (
|
||||
<ErrorComponent
|
||||
statusCode={error.statusCode}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import {render} from "test/utils"
|
||||
|
||||
import Home from "./index"
|
||||
import {useCurrentUser} from "app/hooks/useCurrentUser"
|
||||
|
||||
jest.mock("app/hooks/useCurrentUser")
|
||||
const mockUseCurrentUser = useCurrentUser as jest.MockedFunction<typeof useCurrentUser>
|
||||
|
||||
test("renders blitz documentation link", () => {
|
||||
mockUseCurrentUser.mockReturnValue({
|
||||
id: 1,
|
||||
name: "User",
|
||||
email: "user@email.com",
|
||||
role: "user",
|
||||
})
|
||||
|
||||
const {getByText} = render(<Home />)
|
||||
const element = getByText(/powered by blitz/i)
|
||||
expect(element).toBeInTheDocument()
|
||||
})
|
||||
@@ -1,10 +1,9 @@
|
||||
import {Suspense} from "react"
|
||||
import {Head, Link, useSession, useRouterQuery, useMutation, invoke} from "blitz"
|
||||
import {Head, Link, useSession, useRouterQuery} from "blitz"
|
||||
import getUser from "app/users/queries/getUser"
|
||||
import trackView from "app/users/mutations/trackView"
|
||||
import Layout from "app/layouts/Layout"
|
||||
import {useCurrentUser} from "app/hooks/useCurrentUser"
|
||||
// import getUsers from "app/users/queries/getUsers"
|
||||
|
||||
const CurrentUserInfo = () => {
|
||||
const currentUser = useCurrentUser()
|
||||
@@ -12,16 +11,9 @@ const CurrentUserInfo = () => {
|
||||
return <pre>{JSON.stringify(currentUser, null, 2)}</pre>
|
||||
}
|
||||
|
||||
// const Users = () => {
|
||||
// const [users] = useQuery(getUsers, {})
|
||||
//
|
||||
// return <pre style={{maxWidth: "30rem"}}>{JSON.stringify(users, null, 2)}</pre>
|
||||
// }
|
||||
|
||||
const UserStuff = () => {
|
||||
const session = useSession()
|
||||
const query = useRouterQuery()
|
||||
const [trackViewMutation] = useMutation(trackView)
|
||||
|
||||
if (session.isLoading) return <div>Loading...</div>
|
||||
|
||||
@@ -33,7 +25,7 @@ const UserStuff = () => {
|
||||
<Link href="/signup">Sign Up</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Link href="/login">Login</Link>
|
||||
<Link href="/login">Log In</Link>
|
||||
</div>
|
||||
<a href="/api/auth/twitter" style={{display: "block"}}>
|
||||
Login with Twitter
|
||||
@@ -48,15 +40,10 @@ const UserStuff = () => {
|
||||
<Suspense fallback="Loading...">
|
||||
<CurrentUserInfo />
|
||||
</Suspense>
|
||||
{/*
|
||||
<Suspense fallback="Loading...">
|
||||
<Users />
|
||||
</Suspense>
|
||||
*/}
|
||||
<button
|
||||
onClick={async () => {
|
||||
try {
|
||||
const user = await invoke(getUser, {where: {id: session.userId as number}})
|
||||
const user = await getUser({where: {id: session.userId as number}})
|
||||
alert(JSON.stringify(user))
|
||||
} catch (error) {
|
||||
alert("error: " + JSON.stringify(error))
|
||||
@@ -68,7 +55,7 @@ const UserStuff = () => {
|
||||
<button
|
||||
onClick={async () => {
|
||||
try {
|
||||
await trackViewMutation()
|
||||
await trackView()
|
||||
} catch (error) {
|
||||
alert("error: " + error)
|
||||
console.log(error)
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import {FC} from "react"
|
||||
import * as React from "react"
|
||||
import {getSessionContext} from "@blitzjs/server"
|
||||
import {
|
||||
invokeWithMiddleware,
|
||||
ssrQuery,
|
||||
useRouter,
|
||||
GetServerSideProps,
|
||||
PromiseReturnType,
|
||||
ErrorComponent as ErrorPage,
|
||||
useMutation,
|
||||
AuthenticationError,
|
||||
AuthorizationError,
|
||||
} from "blitz"
|
||||
import getUser from "app/users/queries/getUser"
|
||||
import logout from "app/auth/mutations/logout"
|
||||
@@ -33,9 +30,9 @@ export const getServerSideProps: GetServerSideProps<PageProps> = async ({req, re
|
||||
const session = await getSessionContext(req, res)
|
||||
console.log("Session id:", session.userId)
|
||||
try {
|
||||
const user = await invokeWithMiddleware(
|
||||
const user = await ssrQuery(
|
||||
getUser,
|
||||
{where: {id: Number(session.userId)}},
|
||||
{where: {id: Number(session.userId)}, select: {id: true}},
|
||||
{res, req},
|
||||
)
|
||||
return {props: {user}}
|
||||
@@ -44,10 +41,11 @@ export const getServerSideProps: GetServerSideProps<PageProps> = async ({req, re
|
||||
res.statusCode = 404
|
||||
res.end()
|
||||
return {props: {}}
|
||||
} else if (error instanceof AuthenticationError) {
|
||||
res.writeHead(302, {location: "/login"}).end()
|
||||
} else if (error.name === "AuthenticationError") {
|
||||
res.writeHead(302, {location: "/login"})
|
||||
res.end()
|
||||
return {props: {}}
|
||||
} else if (error instanceof AuthorizationError) {
|
||||
} else if (error.name === "AuthorizationError") {
|
||||
return {
|
||||
props: {
|
||||
error: {
|
||||
@@ -62,9 +60,8 @@ export const getServerSideProps: GetServerSideProps<PageProps> = async ({req, re
|
||||
}
|
||||
}
|
||||
|
||||
const Test: FC<PageProps> = ({user, error}: PageProps) => {
|
||||
const Test: React.FC<PageProps> = ({user, error}: PageProps) => {
|
||||
const router = useRouter()
|
||||
const [logoutMutation] = useMutation(logout)
|
||||
|
||||
if (error) {
|
||||
return <ErrorPage statusCode={error.statusCode} title={error.message} />
|
||||
@@ -75,7 +72,7 @@ const Test: FC<PageProps> = ({user, error}: PageProps) => {
|
||||
<div>Logged in user id: {user?.id}</div>
|
||||
<button
|
||||
onClick={async () => {
|
||||
await logoutMutation()
|
||||
await logout()
|
||||
router.push("/")
|
||||
}}
|
||||
>
|
||||
|
||||
23
examples/auth/app/sessions/components/SessionForm.tsx
Normal file
23
examples/auth/app/sessions/components/SessionForm.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from "react"
|
||||
|
||||
type SessionFormProps = {
|
||||
initialValues: any
|
||||
onSubmit: React.FormEventHandler<HTMLFormElement>
|
||||
}
|
||||
|
||||
const SessionForm = ({initialValues, onSubmit}: SessionFormProps) => {
|
||||
return (
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault()
|
||||
onSubmit(event)
|
||||
}}
|
||||
>
|
||||
<div>Put your form fields here. But for now, just click submit</div>
|
||||
<div>{JSON.stringify(initialValues)}</div>
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default SessionForm
|
||||
16
examples/auth/app/sessions/mutations/createSession.ts
Normal file
16
examples/auth/app/sessions/mutations/createSession.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {SessionContext} from "blitz"
|
||||
import db, {SessionCreateArgs} from "db"
|
||||
|
||||
type CreateSessionInput = {
|
||||
data: SessionCreateArgs["data"]
|
||||
}
|
||||
export default async function createSession(
|
||||
{data}: CreateSessionInput,
|
||||
ctx: {session?: SessionContext} = {},
|
||||
) {
|
||||
ctx.session!.authorize()
|
||||
|
||||
const session = await db.session.create({data})
|
||||
|
||||
return session
|
||||
}
|
||||
17
examples/auth/app/sessions/mutations/deleteSession.ts
Normal file
17
examples/auth/app/sessions/mutations/deleteSession.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import {SessionContext} from "blitz"
|
||||
import db, {SessionDeleteArgs} from "db"
|
||||
|
||||
type DeleteSessionInput = {
|
||||
where: SessionDeleteArgs["where"]
|
||||
}
|
||||
|
||||
export default async function deleteSession(
|
||||
{where}: DeleteSessionInput,
|
||||
ctx: {session?: SessionContext} = {},
|
||||
) {
|
||||
ctx.session!.authorize()
|
||||
|
||||
const session = await db.session.delete({where})
|
||||
|
||||
return session
|
||||
}
|
||||
18
examples/auth/app/sessions/mutations/updateSession.ts
Normal file
18
examples/auth/app/sessions/mutations/updateSession.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import {SessionContext} from "blitz"
|
||||
import db, {SessionUpdateArgs} from "db"
|
||||
|
||||
type UpdateSessionInput = {
|
||||
where: SessionUpdateArgs["where"]
|
||||
data: SessionUpdateArgs["data"]
|
||||
}
|
||||
|
||||
export default async function updateSession(
|
||||
{where, data}: UpdateSessionInput,
|
||||
ctx: {session?: SessionContext} = {},
|
||||
) {
|
||||
ctx.session!.authorize()
|
||||
|
||||
const session = await db.session.update({where, data})
|
||||
|
||||
return session
|
||||
}
|
||||
60
examples/auth/app/sessions/pages/sessions/[sessionId].tsx
Normal file
60
examples/auth/app/sessions/pages/sessions/[sessionId].tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import React, {Suspense} from "react"
|
||||
import Layout from "app/layouts/Layout"
|
||||
import {Head, Link, useRouter, useQuery, useParam, BlitzPage} from "blitz"
|
||||
import getSession from "app/sessions/queries/getSession"
|
||||
import deleteSession from "app/sessions/mutations/deleteSession"
|
||||
|
||||
export const Session = () => {
|
||||
const router = useRouter()
|
||||
const sessionId = useParam("sessionId", "number")
|
||||
const [session] = useQuery(getSession, {where: {id: sessionId}})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Session {session.id}</h1>
|
||||
<pre>{JSON.stringify(session, null, 2)}</pre>
|
||||
|
||||
<Link href="/sessions/[sessionId]/edit" as={`/sessions/${session.id}/edit`}>
|
||||
<a>Edit</a>
|
||||
</Link>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
if (window.confirm("This will be deleted")) {
|
||||
await deleteSession({where: {id: session.id}})
|
||||
router.push("/sessions")
|
||||
}
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ShowSessionPage: BlitzPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>Session</title>
|
||||
</Head>
|
||||
|
||||
<main>
|
||||
<p>
|
||||
<Link href="/sessions">
|
||||
<a>Sessions</a>
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Session />
|
||||
</Suspense>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ShowSessionPage.getLayout = (page) => <Layout>{page}</Layout>
|
||||
|
||||
export default ShowSessionPage
|
||||
68
examples/auth/app/sessions/pages/sessions/index.tsx
Normal file
68
examples/auth/app/sessions/pages/sessions/index.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import React, {Suspense} from "react"
|
||||
import Layout from "app/layouts/Layout"
|
||||
import {Head, Link, usePaginatedQuery, useRouter, BlitzPage} from "blitz"
|
||||
import getSessions from "app/sessions/queries/getSessions"
|
||||
|
||||
const ITEMS_PER_PAGE = 100
|
||||
|
||||
export const SessionsList = () => {
|
||||
const router = useRouter()
|
||||
const page = Number(router.query.page) || 0
|
||||
const [{sessions, hasMore}] = usePaginatedQuery(getSessions, {
|
||||
orderBy: {id: "asc"},
|
||||
skip: ITEMS_PER_PAGE * page,
|
||||
take: ITEMS_PER_PAGE,
|
||||
})
|
||||
|
||||
const goToPreviousPage = () => router.push({query: {page: page - 1}})
|
||||
const goToNextPage = () => router.push({query: {page: page + 1}})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ul>
|
||||
{sessions.map((session) => (
|
||||
<li key={session.id}>
|
||||
<Link href="/sessions/[sessionId]" as={`/sessions/${session.id}`}>
|
||||
<a>{session.name}</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<button disabled={page === 0} onClick={goToPreviousPage}>
|
||||
Previous
|
||||
</button>
|
||||
<button disabled={!hasMore} onClick={goToNextPage}>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const SessionsPage: BlitzPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>Sessions</title>
|
||||
</Head>
|
||||
|
||||
<main>
|
||||
<h1>Sessions</h1>
|
||||
|
||||
<p>
|
||||
<Link href="/sessions/new">
|
||||
<a>Create Session</a>
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<SessionsList />
|
||||
</Suspense>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
SessionsPage.getLayout = (page) => <Layout>{page}</Layout>
|
||||
|
||||
export default SessionsPage
|
||||
21
examples/auth/app/sessions/queries/getSession.ts
Normal file
21
examples/auth/app/sessions/queries/getSession.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import {NotFoundError, SessionContext} from "blitz"
|
||||
import db, {FindOneSessionArgs} from "db"
|
||||
|
||||
type GetSessionInput = {
|
||||
where: FindOneSessionArgs["where"]
|
||||
// Only available if a model relationship exists
|
||||
// include?: FindOneSessionArgs['include']
|
||||
}
|
||||
|
||||
export default async function getSession(
|
||||
{where /* include */}: GetSessionInput,
|
||||
ctx: {session?: SessionContext} = {},
|
||||
) {
|
||||
ctx.session!.authorize()
|
||||
|
||||
const session = await db.session.findOne({where})
|
||||
|
||||
if (!session) throw new NotFoundError()
|
||||
|
||||
return session
|
||||
}
|
||||
35
examples/auth/app/sessions/queries/getSessions.ts
Normal file
35
examples/auth/app/sessions/queries/getSessions.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import {SessionContext} from "blitz"
|
||||
import db, {FindManySessionArgs} from "db"
|
||||
|
||||
type GetSessionsInput = {
|
||||
where?: FindManySessionArgs["where"]
|
||||
orderBy?: FindManySessionArgs["orderBy"]
|
||||
skip?: FindManySessionArgs["skip"]
|
||||
take?: FindManySessionArgs["take"]
|
||||
// Only available if a model relationship exists
|
||||
// include?: FindManySessionArgs['include']
|
||||
}
|
||||
|
||||
export default async function getSessions(
|
||||
{where, orderBy, skip = 0, take}: GetSessionsInput,
|
||||
ctx: {session?: SessionContext} = {},
|
||||
) {
|
||||
ctx.session!.authorize()
|
||||
|
||||
const sessions = await db.session.findMany({
|
||||
where,
|
||||
orderBy,
|
||||
take,
|
||||
skip,
|
||||
})
|
||||
|
||||
const count = await db.session.count()
|
||||
const hasMore = typeof take === "number" ? skip + take < count : false
|
||||
const nextPage = hasMore ? {take, skip: skip + take!} : null
|
||||
|
||||
return {
|
||||
sessions,
|
||||
nextPage,
|
||||
hasMore,
|
||||
}
|
||||
}
|
||||
23
examples/auth/app/users/components/UserForm.tsx
Normal file
23
examples/auth/app/users/components/UserForm.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from "react"
|
||||
|
||||
type UserFormProps = {
|
||||
initialValues: any
|
||||
onSubmit: React.FormEventHandler<HTMLFormElement>
|
||||
}
|
||||
|
||||
const UserForm = ({initialValues, onSubmit}: UserFormProps) => {
|
||||
return (
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault()
|
||||
onSubmit(event)
|
||||
}}
|
||||
>
|
||||
<div>Put your form fields here. But for now, just click submit</div>
|
||||
<div>{JSON.stringify(initialValues)}</div>
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserForm
|
||||
10
examples/auth/app/users/mutations/createUser.ts
Normal file
10
examples/auth/app/users/mutations/createUser.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import db, {UserCreateArgs} from "db"
|
||||
|
||||
type CreateUserInput = {
|
||||
data: UserCreateArgs["data"]
|
||||
}
|
||||
export default async function createUser({data}: CreateUserInput, ctx: Record<any, any> = {}) {
|
||||
const user = await db.user.create({data})
|
||||
|
||||
return user
|
||||
}
|
||||
11
examples/auth/app/users/mutations/deleteUser.ts
Normal file
11
examples/auth/app/users/mutations/deleteUser.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import db, {UserDeleteArgs} from "db"
|
||||
|
||||
type DeleteUserInput = {
|
||||
where: UserDeleteArgs["where"]
|
||||
}
|
||||
|
||||
export default async function deleteUser({where}: DeleteUserInput, ctx: Record<any, any> = {}) {
|
||||
const user = await db.user.delete({where})
|
||||
|
||||
return user
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import {Ctx} from "blitz"
|
||||
import {SessionContext} from "blitz"
|
||||
|
||||
export default async function trackView(_ = null, {session}: Ctx) {
|
||||
const currentViews = session.publicData.views || 0
|
||||
await session.setPublicData({views: currentViews + 1})
|
||||
await session.setPrivateData({views: currentViews + 1})
|
||||
export default async function trackView(_ = null, ctx: {session?: SessionContext} = {}) {
|
||||
const currentViews = ctx.session!.publicData.views || 0
|
||||
await ctx.session!.setPublicData({views: currentViews + 1})
|
||||
await ctx.session!.setPrivateData({views: currentViews + 1})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
15
examples/auth/app/users/mutations/updateUser.ts
Normal file
15
examples/auth/app/users/mutations/updateUser.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import db, {UserUpdateArgs} from "db"
|
||||
|
||||
type UpdateUserInput = {
|
||||
where: UserUpdateArgs["where"]
|
||||
data: UserUpdateArgs["data"]
|
||||
}
|
||||
|
||||
export default async function updateUser(
|
||||
{where, data}: UpdateUserInput,
|
||||
ctx: Record<any, any> = {},
|
||||
) {
|
||||
const user = await db.user.update({where, data})
|
||||
|
||||
return user
|
||||
}
|
||||
62
examples/auth/app/users/pages/users/[userId].tsx
Normal file
62
examples/auth/app/users/pages/users/[userId].tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import React, {Suspense} from "react"
|
||||
import {Head, Link, useRouter, useQuery, useParam, BlitzPage} from "blitz"
|
||||
import getUser from "app/users/queries/getUser"
|
||||
import deleteUser from "app/users/mutations/deleteUser"
|
||||
|
||||
export const User = () => {
|
||||
const router = useRouter()
|
||||
const userId = useParam("userId", "number")
|
||||
const [user] = useQuery(getUser, {where: {id: userId}})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>User {user.id}</h1>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
|
||||
{
|
||||
<Link href="/users/[userId]/edit" as={`/users/${user.id}/edit`}>
|
||||
<a>Edit</a>
|
||||
</Link>
|
||||
}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
if (window.confirm("This will be deleted")) {
|
||||
await deleteUser({where: {id: user.id}})
|
||||
router.push("/users")
|
||||
}
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ShowUserPage: BlitzPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>User</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
<main>
|
||||
<p>
|
||||
{
|
||||
<Link href="/users">
|
||||
<a>Users</a>
|
||||
</Link>
|
||||
}
|
||||
</p>
|
||||
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<User />
|
||||
</Suspense>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ShowUserPage
|
||||
63
examples/auth/app/users/pages/users/[userId]/edit.tsx
Normal file
63
examples/auth/app/users/pages/users/[userId]/edit.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import React, {Suspense} from "react"
|
||||
import {Head, Link, useRouter, useQuery, useParam, BlitzPage} from "blitz"
|
||||
import getUser from "app/users/queries/getUser"
|
||||
import updateUser from "app/users/mutations/updateUser"
|
||||
import UserForm from "app/users/components/UserForm"
|
||||
|
||||
export const EditUser = () => {
|
||||
const router = useRouter()
|
||||
const userId = useParam("userId", "number")
|
||||
const [user, {mutate}] = useQuery(getUser, {where: {id: userId}})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Edit User {user.id}</h1>
|
||||
<pre>{JSON.stringify(user)}</pre>
|
||||
|
||||
<UserForm
|
||||
initialValues={user}
|
||||
onSubmit={async () => {
|
||||
try {
|
||||
const updated = await updateUser({
|
||||
where: {id: user.id},
|
||||
data: {name: "MyNewName"},
|
||||
})
|
||||
mutate(updated)
|
||||
alert("Success!" + JSON.stringify(updated))
|
||||
router.push("/users/[userId]", `/users/${updated.id}`)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
alert("Error creating user " + JSON.stringify(error, null, 2))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const EditUserPage: BlitzPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>Edit User</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
<main>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<EditUser />
|
||||
</Suspense>
|
||||
|
||||
<p>
|
||||
{
|
||||
<Link href="/users">
|
||||
<a>Users</a>
|
||||
</Link>
|
||||
}
|
||||
</p>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditUserPage
|
||||
@@ -1,60 +1,49 @@
|
||||
import {Suspense} from "react"
|
||||
import Layout from "app/layouts/Layout"
|
||||
import {Link, usePaginatedQuery, useRouter, BlitzPage} from "blitz"
|
||||
import React, {Suspense} from "react"
|
||||
import {Head, Link, useQuery, BlitzPage} from "blitz"
|
||||
import getUsers from "app/users/queries/getUsers"
|
||||
|
||||
const ITEMS_PER_PAGE = 100
|
||||
import Layout from "app/layouts/Layout"
|
||||
|
||||
export const UsersList = () => {
|
||||
const router = useRouter()
|
||||
const page = Number(router.query.page) || 0
|
||||
const [{users, hasMore}] = usePaginatedQuery(getUsers, {
|
||||
orderBy: {id: "asc"},
|
||||
skip: ITEMS_PER_PAGE * page,
|
||||
take: ITEMS_PER_PAGE,
|
||||
})
|
||||
|
||||
const goToPreviousPage = () => router.push({query: {page: page - 1}})
|
||||
const goToNextPage = () => router.push({query: {page: page + 1}})
|
||||
const [users] = useQuery(getUsers, {orderBy: {id: "desc"}})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ul>
|
||||
{users.map((user) => (
|
||||
<li key={user.id}>
|
||||
<Link href="/users/[userId]" as={`/users/${user.id}`}>
|
||||
<a>{user.email}</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<button disabled={page === 0} onClick={goToPreviousPage}>
|
||||
Previous
|
||||
</button>
|
||||
<button disabled={!hasMore} onClick={goToNextPage}>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
<ul>
|
||||
{users?.map((user) => (
|
||||
<li key={user.id}>
|
||||
<Link href="/users/[userId]" as={`/users/${user.id}`}>
|
||||
<a>{user.email}</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
const UsersPage: BlitzPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
<Link href="/users/new">
|
||||
<a>Create User</a>
|
||||
</Link>
|
||||
</p>
|
||||
<Layout>
|
||||
<Head>
|
||||
<title>Users</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<UsersList />
|
||||
</Suspense>
|
||||
</div>
|
||||
<main>
|
||||
<h1>Users</h1>
|
||||
|
||||
<p>
|
||||
{
|
||||
<Link href="/users/new">
|
||||
<a>Create User</a>
|
||||
</Link>
|
||||
}
|
||||
</p>
|
||||
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<UsersList />
|
||||
</Suspense>
|
||||
</main>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
UsersPage.getLayout = (page) => <Layout>{page}</Layout>
|
||||
|
||||
export default UsersPage
|
||||
|
||||
44
examples/auth/app/users/pages/users/new.tsx
Normal file
44
examples/auth/app/users/pages/users/new.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from "react"
|
||||
import {Head, Link, useRouter, BlitzPage} from "blitz"
|
||||
import createUser from "app/users/mutations/createUser"
|
||||
import UserForm from "app/users/components/UserForm"
|
||||
|
||||
const NewUserPage: BlitzPage = () => {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>New User</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
<main>
|
||||
<h1>Create New User </h1>
|
||||
|
||||
<UserForm
|
||||
initialValues={{}}
|
||||
onSubmit={async () => {
|
||||
try {
|
||||
const user = await createUser({data: {name: "MyName"}})
|
||||
alert("Success!" + JSON.stringify(user))
|
||||
router.push("/users/[userId]", `/users/${user.id}`)
|
||||
} catch (error) {
|
||||
alert("Error creating user " + JSON.stringify(error, null, 2))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<p>
|
||||
{
|
||||
<Link href="/users">
|
||||
<a>Users</a>
|
||||
</Link>
|
||||
}
|
||||
</p>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NewUserPage
|
||||
@@ -1,11 +1,11 @@
|
||||
import {Ctx} from "blitz"
|
||||
import db from "db"
|
||||
import {SessionContext} from "blitz"
|
||||
|
||||
export default async function getCurrentUser(_ = null, ctx: Ctx) {
|
||||
if (!ctx.session.userId) return null
|
||||
export default async function getCurrentUser(_ = null, ctx: {session?: SessionContext} = {}) {
|
||||
if (!ctx.session?.userId) return null
|
||||
|
||||
const user = await db().user.findFirst({
|
||||
where: {id: ctx.session.userId},
|
||||
const user = await db.user.findOne({
|
||||
where: {id: ctx.session!.userId},
|
||||
select: {id: true, name: true, email: true, role: true},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import {Ctx, NotFoundError} from "blitz"
|
||||
import db, {Prisma} from "db"
|
||||
import db, {FindOneUserArgs} from "db"
|
||||
import {SessionContext, NotFoundError} from "blitz"
|
||||
|
||||
type GetUserInput = {
|
||||
where: Prisma.FindUniqueUserArgs["where"]
|
||||
where: FindOneUserArgs["where"]
|
||||
select?: FindOneUserArgs["select"]
|
||||
// Only available if a model relationship exists
|
||||
// include?: FindOneUserArgs['include']
|
||||
}
|
||||
|
||||
export default async function getUser({where}: GetUserInput, ctx: Ctx) {
|
||||
ctx.session.authorize()
|
||||
export default async function getUser(
|
||||
{where, select}: GetUserInput,
|
||||
ctx: {session?: SessionContext} = {},
|
||||
) {
|
||||
ctx.session?.authorize(["admin", "user"])
|
||||
|
||||
const user = await db().user.findFirst({where})
|
||||
const user = await db.user.findOne({where, select})
|
||||
|
||||
if (!user) throw new NotFoundError(`User with id ${where.id} does not exist`)
|
||||
|
||||
const {hashedPassword, ...rest} = user
|
||||
|
||||
return rest
|
||||
return user
|
||||
}
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
import {Ctx} from "blitz"
|
||||
import db, {FindManyUserArgs} from "db"
|
||||
import {SessionContext} from "blitz"
|
||||
|
||||
type GetUsersInput = Pick<FindManyUserArgs, "where" | "orderBy" | "skip" | "take">
|
||||
type GetUsersInput = {
|
||||
where?: FindManyUserArgs["where"]
|
||||
orderBy?: FindManyUserArgs["orderBy"]
|
||||
cursor?: FindManyUserArgs["cursor"]
|
||||
take?: FindManyUserArgs["take"]
|
||||
skip?: FindManyUserArgs["skip"]
|
||||
// Only available if a model relationship exists
|
||||
// include?: FindManyUserArgs['include']
|
||||
}
|
||||
|
||||
export default async function getUsers({where, orderBy, skip = 0, take}: GetUsersInput, ctx: Ctx) {
|
||||
ctx.session.authorize()
|
||||
export default async function getUsers(
|
||||
{where, orderBy, cursor, take, skip}: GetUsersInput,
|
||||
ctx: {session?: SessionContext} = {},
|
||||
) {
|
||||
ctx.session!.authorize(["admin"])
|
||||
|
||||
const users = await db().user.findMany({
|
||||
const users = await db.user.findMany({
|
||||
where,
|
||||
orderBy,
|
||||
cursor,
|
||||
take,
|
||||
skip,
|
||||
})
|
||||
|
||||
const count = await db().user.count()
|
||||
const hasMore = typeof take === "number" ? skip + take < count : false
|
||||
const nextPage = hasMore ? {take, skip: skip + take!} : null
|
||||
|
||||
return {
|
||||
users,
|
||||
nextPage,
|
||||
hasMore,
|
||||
count,
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const {sessionMiddleware, simpleRolesIsAuthorized} = require("@blitzjs/server")
|
||||
const {sessionMiddleware, unstable_simpleRolesIsAuthorized} = require("@blitzjs/server")
|
||||
const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
||||
enabled: process.env.ANALYZE === "true",
|
||||
})
|
||||
@@ -6,16 +6,10 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
||||
module.exports = withBundleAnalyzer({
|
||||
middleware: [
|
||||
sessionMiddleware({
|
||||
isAuthorized: simpleRolesIsAuthorized,
|
||||
sessionExpiryMinutes: 4,
|
||||
unstable_isAuthorized: unstable_simpleRolesIsAuthorized,
|
||||
// sessionExpiryMinutes: 1,
|
||||
}),
|
||||
],
|
||||
log: {
|
||||
level: "trace",
|
||||
},
|
||||
experimental: {
|
||||
isomorphicResolverImports: true,
|
||||
},
|
||||
/*
|
||||
webpack: (config, {buildId, dev, isServer, defaultLoaders, webpack}) => {
|
||||
// Note: we provide webpack above so you should not `require` it
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:3099",
|
||||
"defaultCommandTimeout": 10000,
|
||||
"video": false,
|
||||
"chromeWebSecurity": false
|
||||
"video": false
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ describe("index page", () => {
|
||||
})
|
||||
|
||||
it("goes to the login page", () => {
|
||||
cy.contains("a", /login/i).click()
|
||||
cy.contains("a", "Log In").click()
|
||||
cy.location("pathname").should("equal", "/login")
|
||||
})
|
||||
|
||||
@@ -30,11 +30,11 @@ describe("index page", () => {
|
||||
cy.signup(user)
|
||||
|
||||
cy.contains("button", "Logout").click()
|
||||
cy.contains("a", /login/i).click()
|
||||
cy.contains("a", "Log In").click()
|
||||
|
||||
cy.contains("Email").find("input").type(user.email)
|
||||
cy.contains("Password").find("input").type(user.password)
|
||||
cy.contains("button", /login/i).click()
|
||||
cy.contains("button", "Log In").click()
|
||||
|
||||
cy.location("pathname").should("equal", "/")
|
||||
cy.contains("button", "Logout")
|
||||
@@ -48,7 +48,7 @@ describe("index page", () => {
|
||||
cy.contains("button", "Logout").click()
|
||||
|
||||
cy.location("pathname").should("equal", "/")
|
||||
cy.contains("a", /login/i)
|
||||
cy.contains("a", "Log In")
|
||||
})
|
||||
|
||||
it("tracks anonymous sessions", () => {
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
//@ts-ignore
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import {PrismaClient} from "@prisma/client"
|
||||
export * from "@prisma/client"
|
||||
|
||||
export default function db(): PrismaClient {
|
||||
globalThis.prisma = globalThis.prisma || new PrismaClient()
|
||||
return globalThis.prisma
|
||||
let prisma: PrismaClient
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
prisma = new PrismaClient()
|
||||
} else {
|
||||
// Ensure the prisma instance is re-used during hot-reloading
|
||||
// Otherwise, a new client will be created on every reload
|
||||
globalThis["prisma"] = globalThis["prisma"] || new PrismaClient()
|
||||
prisma = globalThis["prisma"]
|
||||
}
|
||||
|
||||
export default prisma
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
const {pathsToModuleNameMapper} = require("ts-jest/utils")
|
||||
const {compilerOptions} = require("./tsconfig")
|
||||
|
||||
module.exports = {
|
||||
// Test setup file
|
||||
setupFilesAfterEnv: ["<rootDir>/test/setup.ts"],
|
||||
// Add type checking to Typescript test files
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "jest-environment-jsdom-fourteen",
|
||||
// Automatically clear mock calls and instances between every test
|
||||
clearMocks: true,
|
||||
testPathIgnorePatterns: ["/node_modules/", "/.blitz/", "/.next/", "<rootDir>/db/migrations"],
|
||||
transformIgnorePatterns: ["[/\\\\]node_modules[/\\\\].+\\.(ts|tsx)$"],
|
||||
transform: {
|
||||
"^.+\\.(ts|tsx)$": "babel-jest",
|
||||
},
|
||||
// This makes absolute imports work
|
||||
moduleDirectories: ["node_modules", "<rootDir>"],
|
||||
modulePathIgnorePatterns: ["<rootDir>/.blitz", "<rootDir>/.next", "<rootDir>/cypress"],
|
||||
moduleNameMapper: {
|
||||
// This ensures any path aliases in tsconfig also work in jest
|
||||
...pathsToModuleNameMapper(compilerOptions.paths || {}),
|
||||
"\\.(css|less|sass|scss)$": "identity-obj-proxy",
|
||||
"\\.(gif|ttf|eot|svg|png|jpg|jpeg)$": "<rootDir>/test/__mocks__/fileMock.js",
|
||||
},
|
||||
watchPlugins: ["jest-watch-typeahead/filename", "jest-watch-typeahead/testname"],
|
||||
// Coverage output
|
||||
coverageDirectory: ".coverage",
|
||||
collectCoverageFrom: ["**/*.{js,jsx,ts,tsx}", "!**/*.d.ts", "!**/node_modules/**"],
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@examples/auth",
|
||||
"version": "0.28.0-canary.1",
|
||||
"version": "0.23.1-canary.0",
|
||||
"scripts": {
|
||||
"start": "blitz start",
|
||||
"studio": "blitz db studio",
|
||||
@@ -9,17 +9,12 @@
|
||||
"analyze": "cross-env ANALYZE=true blitz build",
|
||||
"cy:open": "cypress open",
|
||||
"cy:run": "cypress run",
|
||||
"test": "prisma generate && yarn test:jest && yarn test:e2e",
|
||||
"test:jest": "jest",
|
||||
"test:server": "blitz db migrate && blitz build && blitz start --production -p 3099",
|
||||
"test:e2e": "cross-env NODE_ENV=test start-server-and-test test:server http://localhost:3099 cy:run"
|
||||
"test:start": "blitz db migrate && blitz start --production -p 3099",
|
||||
"test": "cross-env NODE_ENV=test start-server-and-test test:start http://localhost:3099 cy:run"
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
"prisma": {
|
||||
"schema": "db/schema.prisma"
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
"printWidth": 100,
|
||||
@@ -38,55 +33,47 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/cli": "2.12.0",
|
||||
"@prisma/client": "2.12.0",
|
||||
"blitz": "0.28.0-canary.1",
|
||||
"@prisma/cli": "2.4.1",
|
||||
"@prisma/client": "2.4.1",
|
||||
"blitz": "0.23.1-canary.0",
|
||||
"final-form": "4.20.1",
|
||||
"passport-auth0": "1.4.0",
|
||||
"passport-github2": "0.1.12",
|
||||
"passport-auth0": "1.3.3",
|
||||
"passport-github2": "0.1.11",
|
||||
"passport-twitter": "1.0.4",
|
||||
"react": "0.0.0-experimental-3310209d0",
|
||||
"react-dom": "0.0.0-experimental-3310209d0",
|
||||
"react-error-boundary": "3.1.0",
|
||||
"react-final-form": "6.5.2",
|
||||
"react": "0.0.0-experimental-7f28234f8",
|
||||
"react-dom": "0.0.0-experimental-7f28234f8",
|
||||
"react-error-boundary": "2.3.1",
|
||||
"react-final-form": "6.5.1",
|
||||
"secure-password": "4.0.0",
|
||||
"zod": "1.11.11"
|
||||
"zod": "1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cypress/skip-test": "2.5.1",
|
||||
"@cypress/skip-test": "2.5.0",
|
||||
"@next/bundle-analyzer": "latest",
|
||||
"@testing-library/jest-dom": "5.11.8",
|
||||
"@testing-library/react": "11.2.2",
|
||||
"@testing-library/react-hooks": "3.7.0",
|
||||
"@types/jest": "26.0.19",
|
||||
"@types/passport-auth0": "1.0.4",
|
||||
"@types/passport-github2": "1.2.4",
|
||||
"@types/passport-twitter": "1.0.36",
|
||||
"@types/react": "17.0.0",
|
||||
"@types/react": "16.9.38",
|
||||
"@types/secure-password": "3.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "4.11.1",
|
||||
"@typescript-eslint/parser": "4.11.1",
|
||||
"@typescript-eslint/eslint-plugin": "2.34.1-alpha.2",
|
||||
"@typescript-eslint/parser": "2.34.1-alpha.2",
|
||||
"babel-eslint": "10.1.0",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "6.2.0",
|
||||
"eslint": "7.16.0",
|
||||
"eslint-config-react-app": "6.0.0",
|
||||
"eslint-plugin-cypress": "2.11.2",
|
||||
"cross-env": "latest",
|
||||
"cypress": "4.11.0",
|
||||
"eslint": "7.6.0",
|
||||
"eslint-config-react-app": "5.2.1",
|
||||
"eslint-plugin-cypress": "2.11.1",
|
||||
"eslint-plugin-flowtype": "5.2.0",
|
||||
"eslint-plugin-import": "2.22.1",
|
||||
"eslint-plugin-jsx-a11y": "6.4.1",
|
||||
"eslint-plugin-react": "7.22.0",
|
||||
"eslint-plugin-react-hooks": "4.2.0",
|
||||
"husky": "4.3.6",
|
||||
"jest": "26.6.3",
|
||||
"jest-environment-jsdom-fourteen": "1.0.1",
|
||||
"jest-watch-typeahead": "0.6.1",
|
||||
"lint-staged": "10.5.3",
|
||||
"prettier": "2.2.1",
|
||||
"pretty-quick": "3.1.0",
|
||||
"start-server-and-test": "1.11.7",
|
||||
"ts-jest": "26.4.4",
|
||||
"typescript": "4.1.3"
|
||||
"eslint-plugin-import": "2.22.0",
|
||||
"eslint-plugin-jsx-a11y": "6.3.1",
|
||||
"eslint-plugin-react": "7.20.5",
|
||||
"eslint-plugin-react-hooks": "4.0.8",
|
||||
"husky": "4.2.5",
|
||||
"lint-staged": "10.2.13",
|
||||
"prettier": "2.0.5",
|
||||
"pretty-quick": "2.0.1",
|
||||
"start-server-and-test": "1.11.2",
|
||||
"typescript": "3.9.5"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = "test-file-stub"
|
||||
@@ -1,6 +0,0 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import "@testing-library/jest-dom/extend-expect"
|
||||
require("dotenv-flow").config({silent: true})
|
||||
@@ -1,88 +0,0 @@
|
||||
import {RouterContext, BlitzRouter} from "blitz"
|
||||
import {render as defaultRender} from "@testing-library/react"
|
||||
import {renderHook as defaultRenderHook} from "@testing-library/react-hooks"
|
||||
|
||||
export * from "@testing-library/react"
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// This file customizes the render() and renderHook() test functions provided
|
||||
// by React testing library. It adds a router context wrapper with a mocked router.
|
||||
//
|
||||
// You should always import `render` and `renderHook` from this file
|
||||
//
|
||||
// This is the place to add any other context providers you need while testing.
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
type DefaultParams = Parameters<typeof defaultRender>
|
||||
type RenderUI = DefaultParams[0]
|
||||
type RenderOptions = DefaultParams[1] & {router?: Partial<BlitzRouter>}
|
||||
|
||||
type DefaultHookParams = Parameters<typeof defaultRenderHook>
|
||||
type RenderHook = DefaultHookParams[0]
|
||||
type RenderHookOptions = DefaultHookParams[1] & {router?: Partial<BlitzRouter>}
|
||||
|
||||
// --------------------------------------------------
|
||||
// render()
|
||||
// --------------------------------------------------
|
||||
// Override the default test render with our own
|
||||
//
|
||||
// You can override the router mock like this:
|
||||
//
|
||||
// const { baseElement } = render(<MyComponent />, {
|
||||
// router: { pathname: '/my-custom-pathname' },
|
||||
// });
|
||||
// --------------------------------------------------
|
||||
export function render(ui: RenderUI, {wrapper, router, ...options}: RenderOptions = {}) {
|
||||
if (!wrapper) {
|
||||
// Add a default context wrapper if one isn't supplied from the test
|
||||
wrapper = ({children}) => (
|
||||
<RouterContext.Provider value={{...mockRouter, ...router}}>{children}</RouterContext.Provider>
|
||||
)
|
||||
}
|
||||
return defaultRender(ui, {wrapper, ...options})
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// renderHook()
|
||||
// --------------------------------------------------
|
||||
// Override the default test renderHook with our own
|
||||
//
|
||||
// You can override the router mock like this:
|
||||
//
|
||||
// const result = renderHook(() => myHook(), {
|
||||
// router: { pathname: '/my-custom-pathname' },
|
||||
// });
|
||||
// --------------------------------------------------
|
||||
export function renderHook(
|
||||
hook: RenderHook,
|
||||
{wrapper, router, ...options}: RenderHookOptions = {},
|
||||
) {
|
||||
if (!wrapper) {
|
||||
// Add a default context wrapper if one isn't supplied from the test
|
||||
wrapper = ({children}) => (
|
||||
<RouterContext.Provider value={{...mockRouter, ...router}}>{children}</RouterContext.Provider>
|
||||
)
|
||||
}
|
||||
return defaultRenderHook(hook, {wrapper, ...options})
|
||||
}
|
||||
|
||||
export const mockRouter: BlitzRouter = {
|
||||
basePath: "",
|
||||
pathname: "/",
|
||||
route: "/",
|
||||
asPath: "/",
|
||||
params: {},
|
||||
query: {},
|
||||
push: jest.fn(),
|
||||
replace: jest.fn(),
|
||||
reload: jest.fn(),
|
||||
back: jest.fn(),
|
||||
prefetch: jest.fn(),
|
||||
beforePopState: jest.fn(),
|
||||
events: {
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
emit: jest.fn(),
|
||||
},
|
||||
isFallback: false,
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import {DefaultCtx, SessionContext, DefaultPublicData} from "blitz"
|
||||
import {User} from "db"
|
||||
|
||||
declare module "blitz" {
|
||||
export interface Ctx extends DefaultCtx {
|
||||
session: SessionContext
|
||||
}
|
||||
export interface PublicData extends DefaultPublicData {
|
||||
userId: User["id"]
|
||||
views?: number
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
# This env file should be checked into source control
|
||||
# This is the place for default values that should be used in all environments
|
||||
@@ -1,41 +0,0 @@
|
||||
# Blitz Fauna Example
|
||||
|
||||
## Intro
|
||||
|
||||
This example shows how to use [Fauna](https://dashboard.fauna.com/accounts/register?utm_source=BlitzJS&utm_medium=sponsorship&utm_campaign=BlitzJS_Sponsorship_2020) instead of Prisma and Postgres.
|
||||
|
||||
The bulk of the integration is in the following files:
|
||||
|
||||
- `blitz.config.js`
|
||||
- `db/index.ts`
|
||||
|
||||
And then also the queries and mutations use Fauna.
|
||||
|
||||
By far the main integration work is providing the auth session hooks for reading and writing session data to Fauna. All this is in `blitz.config.js`.
|
||||
|
||||
This example use the Fauna GraphQL API since it's more familiar to most people than FQL.
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Sign up for a Fauna account
|
||||
1. Create a new database
|
||||
1. Click on the GraphQL menu item
|
||||
1. Upload the graphql schema located at `db/schema.graphql`
|
||||
1. Click on the Security menu item
|
||||
1. Create a new auth key, and add the auth key to `.env.local` like this:
|
||||
|
||||
```
|
||||
FAUNA_SECRET=YOUR_AUTH_KEY
|
||||
```
|
||||
|
||||
```
|
||||
yarn blitz db migrate
|
||||
```
|
||||
|
||||
2. Start the dev server
|
||||
|
||||
```
|
||||
yarn blitz start
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
@@ -1,67 +0,0 @@
|
||||
import { AuthenticationError } from "blitz"
|
||||
import SecurePassword from "secure-password"
|
||||
import db from "db"
|
||||
import { gql } from "graphql-request"
|
||||
|
||||
const SP = new SecurePassword()
|
||||
|
||||
export const hashPassword = async (password: string) => {
|
||||
const hashedBuffer = await SP.hash(Buffer.from(password))
|
||||
return hashedBuffer.toString("base64")
|
||||
}
|
||||
export const verifyPassword = async (hashedPassword: string, password: string) => {
|
||||
try {
|
||||
return await SP.verify(Buffer.from(password), Buffer.from(hashedPassword, "base64"))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const authenticateUser = async (email: string, password: string) => {
|
||||
const { user } = await db.request(
|
||||
gql`
|
||||
query getUser($email: String!) {
|
||||
user: findUserByEmail(email: $email) {
|
||||
id: _id
|
||||
email
|
||||
name
|
||||
role
|
||||
hashedPassword
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ email: email.toLowerCase() }
|
||||
)
|
||||
|
||||
if (!user || !user.hashedPassword) throw new AuthenticationError()
|
||||
|
||||
switch (await verifyPassword(user.hashedPassword, password)) {
|
||||
case SecurePassword.VALID:
|
||||
break
|
||||
case SecurePassword.VALID_NEEDS_REHASH:
|
||||
// Upgrade hashed password with a more secure hash
|
||||
const improvedHash = await hashPassword(password)
|
||||
await db.request(
|
||||
gql`
|
||||
mutation UpdateUser($data: UserInput!) {
|
||||
updateUser(data: $data) {
|
||||
id: _id
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
data: {
|
||||
id: user.id,
|
||||
hashedPassword: improvedHash,
|
||||
},
|
||||
}
|
||||
)
|
||||
break
|
||||
default:
|
||||
throw new AuthenticationError()
|
||||
}
|
||||
|
||||
const { hashedPassword, ...rest } = user
|
||||
return rest
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { Link, useMutation } from "blitz"
|
||||
import { LabeledTextField } from "app/components/LabeledTextField"
|
||||
import { Form, FORM_ERROR } from "app/components/Form"
|
||||
import login from "app/auth/mutations/login"
|
||||
import { LoginInput } from "app/auth/validations"
|
||||
|
||||
type LoginFormProps = {
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
export const LoginForm = (props: LoginFormProps) => {
|
||||
const [loginMutation] = useMutation(login)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Login</h1>
|
||||
|
||||
<Form
|
||||
submitText="Log In"
|
||||
schema={LoginInput}
|
||||
initialValues={{ email: "", password: "" }}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await loginMutation(values)
|
||||
props.onSuccess?.()
|
||||
} catch (error) {
|
||||
if (error.name === "AuthenticationError") {
|
||||
return { [FORM_ERROR]: "Sorry, those credentials are invalid" }
|
||||
} else {
|
||||
return {
|
||||
[FORM_ERROR]:
|
||||
"Sorry, we had an unexpected error. Please try again. - " + error.toString(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LabeledTextField name="email" label="Email" placeholder="Email" />
|
||||
<LabeledTextField name="password" label="Password" placeholder="Password" type="password" />
|
||||
</Form>
|
||||
|
||||
<div style={{ marginTop: "1rem" }}>
|
||||
Or <Link href="/signup">Sign Up</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginForm
|
||||
@@ -1,43 +0,0 @@
|
||||
import { useMutation } from "blitz"
|
||||
import { LabeledTextField } from "app/components/LabeledTextField"
|
||||
import { Form, FORM_ERROR } from "app/components/Form"
|
||||
import signup from "app/auth/mutations/signup"
|
||||
import { SignupInput } from "app/auth/validations"
|
||||
|
||||
type SignupFormProps = {
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
export const SignupForm = (props: SignupFormProps) => {
|
||||
const [signupMutation] = useMutation(signup)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Create an Account</h1>
|
||||
|
||||
<Form
|
||||
submitText="Create Account"
|
||||
schema={SignupInput}
|
||||
initialValues={{ email: "", password: "" }}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await signupMutation(values)
|
||||
props.onSuccess?.()
|
||||
} catch (error) {
|
||||
if (error.code === "P2002" && error.meta?.target?.includes("email")) {
|
||||
// This error comes from Prisma
|
||||
return { email: "This email is already being used" }
|
||||
} else {
|
||||
return { [FORM_ERROR]: error.toString() }
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LabeledTextField name="email" label="Email" placeholder="Email" />
|
||||
<LabeledTextField name="password" label="Password" placeholder="Password" type="password" />
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SignupForm
|
||||
@@ -1,15 +0,0 @@
|
||||
import { Ctx } from "blitz"
|
||||
import { authenticateUser } from "app/auth/auth-utils"
|
||||
import { LoginInput, LoginInputType } from "../validations"
|
||||
|
||||
export default async function login(input: LoginInputType, { session }: Ctx) {
|
||||
// This throws an error if input is invalid
|
||||
const { email, password } = LoginInput.parse(input)
|
||||
|
||||
// This throws an error if credentials are invalid
|
||||
const user = await authenticateUser(email, password)
|
||||
|
||||
await session.create({ userId: user.id, roles: [user.role] })
|
||||
|
||||
return user
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { Ctx } from "blitz"
|
||||
|
||||
export default async function logout(_: any, { session }: Ctx) {
|
||||
return await session.revoke()
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { Ctx } from "blitz"
|
||||
import db from "db"
|
||||
import { hashPassword } from "app/auth/auth-utils"
|
||||
import { SignupInput, SignupInputType } from "app/auth/validations"
|
||||
import { gql } from "graphql-request"
|
||||
|
||||
export default async function signup(input: SignupInputType, { session }: Ctx) {
|
||||
// This throws an error if input is invalid
|
||||
const { email, password } = SignupInput.parse(input)
|
||||
|
||||
const hashedPassword = await hashPassword(password)
|
||||
const { user } = await db.request(
|
||||
gql`
|
||||
mutation createUser($email: String!, $hashedPassword: String, $role: String!) {
|
||||
user: createUser(data: { email: $email, hashedPassword: $hashedPassword, role: $role }) {
|
||||
id: _id
|
||||
email
|
||||
name
|
||||
role
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ email: email.toLowerCase(), hashedPassword, role: "user" }
|
||||
)
|
||||
console.log("Create user result:", user)
|
||||
|
||||
await session.create({ userId: user.id, roles: [user.role] })
|
||||
|
||||
return user
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { useRouter, BlitzPage } from "blitz"
|
||||
import Layout from "app/layouts/Layout"
|
||||
import { LoginForm } from "app/auth/components/LoginForm"
|
||||
|
||||
const LoginPage: BlitzPage = () => {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LoginForm onSuccess={() => router.push("/")} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
LoginPage.getLayout = (page) => <Layout title="Log In">{page}</Layout>
|
||||
|
||||
export default LoginPage
|
||||
@@ -1,17 +0,0 @@
|
||||
import { useRouter, BlitzPage } from "blitz"
|
||||
import Layout from "app/layouts/Layout"
|
||||
import { SignupForm } from "app/auth/components/SignupForm"
|
||||
|
||||
const SignupPage: BlitzPage = () => {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SignupForm onSuccess={() => router.push("/")} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
SignupPage.getLayout = (page) => <Layout title="Sign Up">{page}</Layout>
|
||||
|
||||
export default SignupPage
|
||||
@@ -1,62 +0,0 @@
|
||||
import { ReactNode, PropsWithoutRef } from "react"
|
||||
import { Form as FinalForm, FormProps as FinalFormProps } from "react-final-form"
|
||||
import * as z from "zod"
|
||||
export { FORM_ERROR } from "final-form"
|
||||
|
||||
type FormProps<S extends z.ZodType<any, any>> = {
|
||||
/** All your form fields */
|
||||
children: ReactNode
|
||||
/** Text to display in the submit button */
|
||||
submitText: string
|
||||
schema?: S
|
||||
onSubmit: FinalFormProps<z.infer<S>>["onSubmit"]
|
||||
initialValues?: FinalFormProps<z.infer<S>>["initialValues"]
|
||||
} & Omit<PropsWithoutRef<JSX.IntrinsicElements["form"]>, "onSubmit">
|
||||
|
||||
export function Form<S extends z.ZodType<any, any>>({
|
||||
children,
|
||||
submitText,
|
||||
schema,
|
||||
initialValues,
|
||||
onSubmit,
|
||||
...props
|
||||
}: FormProps<S>) {
|
||||
return (
|
||||
<FinalForm
|
||||
initialValues={initialValues}
|
||||
validate={(values) => {
|
||||
if (!schema) return
|
||||
try {
|
||||
schema.parse(values)
|
||||
} catch (error) {
|
||||
return error.formErrors.fieldErrors
|
||||
}
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
render={({ handleSubmit, submitting, submitError }) => (
|
||||
<form onSubmit={handleSubmit} className="form" {...props}>
|
||||
{/* Form fields supplied as children are rendered here */}
|
||||
{children}
|
||||
|
||||
{submitError && (
|
||||
<div role="alert" style={{ color: "red" }}>
|
||||
{submitError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button type="submit" disabled={submitting}>
|
||||
{submitText}
|
||||
</button>
|
||||
|
||||
<style global jsx>{`
|
||||
.form > * + * {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
`}</style>
|
||||
</form>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Form
|
||||
@@ -1,57 +0,0 @@
|
||||
import { PropsWithoutRef } from "react"
|
||||
import { useField } from "react-final-form"
|
||||
|
||||
export interface LabeledTextFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["input"]> {
|
||||
/** Field name. */
|
||||
name: string
|
||||
/** Field label. */
|
||||
label: string
|
||||
/** Field type. Doesn't include radio buttons and checkboxes */
|
||||
type?: "text" | "password" | "email" | "number"
|
||||
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
|
||||
}
|
||||
|
||||
export const LabeledTextField = React.forwardRef<HTMLInputElement, LabeledTextFieldProps>(
|
||||
({ name, label, outerProps, ...props }, ref) => {
|
||||
const {
|
||||
input,
|
||||
meta: { touched, error, submitError, submitting },
|
||||
} = useField(name)
|
||||
|
||||
const normalizedError = Array.isArray(error) ? error.join(", ") : error || submitError
|
||||
|
||||
return (
|
||||
<div {...outerProps}>
|
||||
<label>
|
||||
{label}
|
||||
<input {...input} disabled={submitting} {...props} ref={ref} />
|
||||
</label>
|
||||
|
||||
{touched && normalizedError && (
|
||||
<div role="alert" style={{ color: "red" }}>
|
||||
{normalizedError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<style jsx>{`
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
font-size: 1rem;
|
||||
}
|
||||
input {
|
||||
font-size: 1rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
border: 1px solid purple;
|
||||
appearance: none;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export default LabeledTextField
|
||||
@@ -1,7 +0,0 @@
|
||||
import { useQuery } from "blitz"
|
||||
import getCurrentUser from "app/users/queries/getCurrentUser"
|
||||
|
||||
export const useCurrentUser = () => {
|
||||
const [user] = useQuery(getCurrentUser, null)
|
||||
return user
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { ReactNode } from "react"
|
||||
import { Head } from "blitz"
|
||||
|
||||
type LayoutProps = {
|
||||
title?: string
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const Layout = ({ title, children }: LayoutProps) => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{title || "fauna"}</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Layout
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Head, ErrorComponent } from "blitz"
|
||||
|
||||
// ------------------------------------------------------
|
||||
// This page is rendered if a route match is not found
|
||||
// ------------------------------------------------------
|
||||
export default function Page404() {
|
||||
const statusCode = 404
|
||||
const title = "This page could not be found"
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>
|
||||
{statusCode}: {title}
|
||||
</title>
|
||||
</Head>
|
||||
<ErrorComponent statusCode={statusCode} title={title} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { AppProps, ErrorComponent, useRouter } from "blitz"
|
||||
import { ErrorBoundary, FallbackProps } from "react-error-boundary"
|
||||
import { queryCache } from "react-query"
|
||||
import LoginForm from "app/auth/components/LoginForm"
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
const getLayout = Component.getLayout || ((page) => page)
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
FallbackComponent={RootErrorFallback}
|
||||
resetKeys={[router.asPath]}
|
||||
onReset={() => {
|
||||
// This ensures the Blitz useQuery hooks will automatically refetch
|
||||
// data any time you reset the error boundary
|
||||
queryCache.resetErrorBoundaries()
|
||||
}}
|
||||
>
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
function RootErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
||||
if (error?.name === "AuthenticationError") {
|
||||
return <LoginForm onSuccess={resetErrorBoundary} />
|
||||
} else if (error?.name === "AuthorizationError") {
|
||||
return (
|
||||
<ErrorComponent
|
||||
statusCode={(error as any).statusCode}
|
||||
title="Sorry, you are not authorized to access this"
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<ErrorComponent
|
||||
statusCode={(error as any)?.statusCode || 400}
|
||||
title={error?.message || error?.name}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { render } from "test/utils"
|
||||
|
||||
import Home from "./index"
|
||||
import { useCurrentUser } from "app/hooks/useCurrentUser"
|
||||
|
||||
jest.mock("app/hooks/useCurrentUser")
|
||||
const mockUseCurrentUser = useCurrentUser as jest.MockedFunction<typeof useCurrentUser>
|
||||
|
||||
test.skip("renders blitz documentation link", () => {
|
||||
// This is an example of how to ensure a specific item is in the document
|
||||
// But it's disabled by default (by test.skip) so the test doesn't fail
|
||||
// when you remove the the default content from the page
|
||||
|
||||
// This is an example on how to mock api hooks when testing
|
||||
mockUseCurrentUser.mockReturnValue({
|
||||
id: 1,
|
||||
name: "User",
|
||||
email: "user@email.com",
|
||||
role: "user",
|
||||
})
|
||||
|
||||
const { getByText } = render(<Home />)
|
||||
const linkElement = getByText(/Documentation/i)
|
||||
expect(linkElement).toBeInTheDocument()
|
||||
})
|
||||
@@ -1,272 +0,0 @@
|
||||
import { Link, BlitzPage, useMutation } from "blitz"
|
||||
import Layout from "app/layouts/Layout"
|
||||
import logout from "app/auth/mutations/logout"
|
||||
import { useCurrentUser } from "app/hooks/useCurrentUser"
|
||||
import { Suspense } from "react"
|
||||
|
||||
/*
|
||||
* This file is just for a pleasant getting started page for your new app.
|
||||
* You can delete everything in here and start from scratch if you like.
|
||||
*/
|
||||
|
||||
const UserInfo = () => {
|
||||
const currentUser = useCurrentUser()
|
||||
const [logoutMutation] = useMutation(logout)
|
||||
|
||||
if (currentUser) {
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className="button small"
|
||||
onClick={async () => {
|
||||
await logoutMutation()
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
<div>
|
||||
User id: <code>{currentUser.id}</code>
|
||||
<br />
|
||||
User role: <code>{currentUser.role}</code>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<Link href="/signup">
|
||||
<a className="button small">
|
||||
<strong>Sign Up</strong>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/login">
|
||||
<a className="button small">
|
||||
<strong>Login</strong>
|
||||
</a>
|
||||
</Link>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const Home: BlitzPage = () => {
|
||||
return (
|
||||
<div className="container">
|
||||
<main>
|
||||
<div className="logo">
|
||||
<img src="/logo.png" alt="blitz.js" />
|
||||
</div>
|
||||
<p>
|
||||
<strong>Congrats!</strong> Your app is ready, including user sign-up and log-in.
|
||||
</p>
|
||||
<div className="buttons" style={{ marginTop: "1rem", marginBottom: "1rem" }}>
|
||||
<Suspense fallback="Loading...">
|
||||
<UserInfo />
|
||||
</Suspense>
|
||||
</div>
|
||||
<p>
|
||||
<strong>
|
||||
To add a new model to your app, <br />
|
||||
run the following in your terminal:
|
||||
</strong>
|
||||
</p>
|
||||
<pre>
|
||||
<code>blitz generate all project name:string</code>
|
||||
</pre>
|
||||
<pre>
|
||||
<code>blitz db migrate</code>
|
||||
</pre>
|
||||
<div>
|
||||
<p>
|
||||
Then <strong>restart the server</strong>
|
||||
</p>
|
||||
<pre>
|
||||
<code>Ctrl + c</code>
|
||||
</pre>
|
||||
<pre>
|
||||
<code>blitz start</code>
|
||||
</pre>
|
||||
<p>
|
||||
and go to{" "}
|
||||
<Link href="/projects">
|
||||
<a>/projects</a>
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
<div className="buttons" style={{ marginTop: "5rem" }}>
|
||||
<a
|
||||
className="button"
|
||||
href="https://blitzjs.com/docs/getting-started?utm_source=blitz-new&utm_medium=app-template&utm_campaign=blitz-new"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
<a
|
||||
className="button-outline"
|
||||
href="https://github.com/blitz-js/blitz"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Github Repo
|
||||
</a>
|
||||
<a
|
||||
className="button-outline"
|
||||
href="https://slack.blitzjs.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Slack Community
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<a
|
||||
href="https://blitzjs.com?utm_source=blitz-new&utm_medium=app-template&utm_campaign=blitz-new"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Powered by Blitz.js
|
||||
</a>
|
||||
</footer>
|
||||
|
||||
<style jsx global>{`
|
||||
@import url("https://fonts.googleapis.com/css2?family=Libre+Franklin:wght@300;700&display=swap");
|
||||
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: "Libre Franklin", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 5rem 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
main p {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
footer {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
border-top: 1px solid #eaeaea;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #45009d;
|
||||
}
|
||||
|
||||
footer a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: #f4f4f4;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.logo img {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-gap: 0.5rem;
|
||||
}
|
||||
.button {
|
||||
font-size: 1rem;
|
||||
background-color: #6700eb;
|
||||
padding: 1rem 2rem;
|
||||
color: #f4f4f4;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button.small {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #45009d;
|
||||
}
|
||||
|
||||
.button-outline {
|
||||
border: 2px solid #6700eb;
|
||||
padding: 1rem 2rem;
|
||||
color: #6700eb;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button-outline:hover {
|
||||
border-color: #45009d;
|
||||
color: #45009d;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #fafafa;
|
||||
border-radius: 5px;
|
||||
padding: 0.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
code {
|
||||
font-size: 0.9rem;
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
max-width: 800px;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.grid {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Home.getLayout = (page) => <Layout title="Home">{page}</Layout>
|
||||
|
||||
export default Home
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Ctx } from "blitz"
|
||||
import db from "db"
|
||||
import { gql } from "graphql-request"
|
||||
|
||||
export default async function getCurrentUser(_ = null, { session }: Ctx) {
|
||||
if (!session.userId) return null
|
||||
|
||||
const { user } = await db.request(
|
||||
gql`
|
||||
query getUser($id: ID!) {
|
||||
user: findUserByID(id: $id) {
|
||||
id: _id
|
||||
email
|
||||
name
|
||||
role
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ id: session.userId }
|
||||
)
|
||||
|
||||
return user
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
const { sessionMiddleware, simpleRolesIsAuthorized } = require("@blitzjs/server")
|
||||
const { GraphQLClient, gql } = require("graphql-request")
|
||||
|
||||
const graphQLClient = new GraphQLClient("https://graphql.fauna.com/graphql", {
|
||||
headers: {
|
||||
authorization: "Bearer " + process.env.FAUNA_SECRET,
|
||||
},
|
||||
})
|
||||
|
||||
const normalizeSession = (faunaSession) => {
|
||||
if (!faunaSession) return null
|
||||
const { user, expiresAt, ...rest } = faunaSession
|
||||
return {
|
||||
...rest,
|
||||
userId: user.id,
|
||||
expiresAt: new Date(expiresAt),
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
middleware: [
|
||||
sessionMiddleware({
|
||||
isAuthorized: simpleRolesIsAuthorized,
|
||||
getSession: async (handle) => {
|
||||
const { findSessionByHandle: session } = await graphQLClient.request(
|
||||
gql`
|
||||
query getSession($handle: String!) {
|
||||
findSessionByHandle(handle: $handle) {
|
||||
id: _id
|
||||
publicData
|
||||
privateData
|
||||
antiCSRFToken
|
||||
expiresAt
|
||||
hashedSessionToken
|
||||
handle
|
||||
user {
|
||||
id: _id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ handle: handle }
|
||||
)
|
||||
if (!session) return null
|
||||
const { user, expiresAt, ...rest } = session
|
||||
return {
|
||||
...rest,
|
||||
userId: user.id,
|
||||
expiresAt: new Date(expiresAt),
|
||||
}
|
||||
},
|
||||
// getSessions: (userId) => getDb().session.findMany({ where: { userId } }),
|
||||
createSession: async (session) => {
|
||||
const { userId, ...sessionInput } = session
|
||||
const userInput = { connect: userId }
|
||||
|
||||
const { createSession: sessionRes } = await graphQLClient.request(
|
||||
gql`
|
||||
mutation CreateSession($data: SessionInput!) {
|
||||
createSession(data: $data) {
|
||||
id: _id
|
||||
publicData
|
||||
privateData
|
||||
antiCSRFToken
|
||||
expiresAt
|
||||
hashedSessionToken
|
||||
handle
|
||||
user {
|
||||
id: _id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
data: {
|
||||
...sessionInput,
|
||||
expiresAt: sessionInput.expiresAt.toISOString(),
|
||||
user: userInput,
|
||||
},
|
||||
}
|
||||
)
|
||||
return normalizeSession(sessionRes)
|
||||
},
|
||||
updateSession: async (sessionHandle, session) => {
|
||||
const { findSessionByHandle: existingSession } = await graphQLClient.request(
|
||||
gql`
|
||||
query getSession($handle: String!) {
|
||||
findSessionByHandle(handle: $handle) {
|
||||
id: _id
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ handle: sessionHandle }
|
||||
)
|
||||
|
||||
const { userId, handle, ...sessionInput } = session
|
||||
|
||||
const { updateSession: sessionRes } = await graphQLClient.request(
|
||||
gql`
|
||||
mutation UpdateSession($data: SessionInput!) {
|
||||
updateSession(data: $data) {
|
||||
id: _id
|
||||
publicData
|
||||
privateData
|
||||
antiCSRFToken
|
||||
expiresAt
|
||||
hashedSessionToken
|
||||
handle
|
||||
user {
|
||||
id: _id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
data: {
|
||||
...sessionInput,
|
||||
id: existingSession.id,
|
||||
expiresAt: sessionInput.expiresAt.toISOString(),
|
||||
},
|
||||
}
|
||||
)
|
||||
return normalizeSession(sessionRes)
|
||||
},
|
||||
deleteSession: async (handle) => {
|
||||
const { findSessionByHandle: existingSession } = await graphQLClient.request(
|
||||
gql`
|
||||
query getSession($handle: String!) {
|
||||
findSessionByHandle(handle: $handle) {
|
||||
id: _id
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ handle: handle }
|
||||
)
|
||||
|
||||
await graphQLClient.request(
|
||||
gql`
|
||||
mutation DeleteSession($id ID!) {
|
||||
deleteSession(id: $id) {
|
||||
id: _id
|
||||
handle
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
id: existingSession.id,
|
||||
}
|
||||
)
|
||||
},
|
||||
}),
|
||||
],
|
||||
/* Uncomment this to customize the webpack config
|
||||
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
|
||||
// Note: we provide webpack above so you should not `require` it
|
||||
// Perform customizations to webpack config
|
||||
// Important: return the modified config
|
||||
return config
|
||||
},
|
||||
*/
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { GraphQLClient } from "graphql-request"
|
||||
|
||||
const graphQLClient = new GraphQLClient("https://graphql.fauna.com/graphql", {
|
||||
headers: {
|
||||
authorization: "Bearer " + process.env.FAUNA_SECRET,
|
||||
},
|
||||
})
|
||||
|
||||
export default graphQLClient
|
||||
@@ -1,23 +0,0 @@
|
||||
type User {
|
||||
name: String
|
||||
email: String! @unique
|
||||
hashedPassword: String
|
||||
role: String!
|
||||
sessions: [Session!] @relation
|
||||
}
|
||||
|
||||
type Session {
|
||||
expiresAt: Time
|
||||
handle: String! @unique
|
||||
user: User
|
||||
hashedSessionToken: String
|
||||
antiCSRFToken: String
|
||||
publicData: String
|
||||
privateData: String
|
||||
}
|
||||
|
||||
type Query {
|
||||
allUsers: [User!]
|
||||
findUserByEmail(email: String!): User @index(name: "unique_User_email")
|
||||
findSessionByHandle(handle: String!): Session @index(name: "unique_Session_handle")
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// import db from "./index"
|
||||
|
||||
/*
|
||||
* This seed function is executed when you run `blitz db seed`.
|
||||
*
|
||||
* Probably you want to use a library like https://chancejs.com
|
||||
* or https://github.com/Marak/Faker.js to easily generate
|
||||
* realistic data.
|
||||
*/
|
||||
const seed = async () => {
|
||||
// for (let i = 0; i < 5; i++) {
|
||||
// await db.project.create({ data: { name: "Project " + i } })
|
||||
// }
|
||||
}
|
||||
|
||||
export default seed
|
||||
@@ -1,30 +0,0 @@
|
||||
const { pathsToModuleNameMapper } = require("ts-jest/utils")
|
||||
const { compilerOptions } = require("./tsconfig")
|
||||
|
||||
module.exports = {
|
||||
// Test setup file
|
||||
setupFilesAfterEnv: ["<rootDir>/test/setup.ts"],
|
||||
// Add type checking to Typescript test files
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "jest-environment-jsdom-fourteen",
|
||||
// Automatically clear mock calls and instances between every test
|
||||
clearMocks: true,
|
||||
testPathIgnorePatterns: ["/node_modules/", "/.blitz/", "/.next/", "<rootDir>/db/migrations"],
|
||||
transformIgnorePatterns: ["[/\\\\]node_modules[/\\\\].+\\.(ts|tsx)$"],
|
||||
transform: {
|
||||
"^.+\\.(ts|tsx)$": "babel-jest",
|
||||
},
|
||||
// This makes absolute imports work
|
||||
moduleDirectories: ["node_modules", "."],
|
||||
modulePathIgnorePatterns: [".blitz"],
|
||||
moduleNameMapper: {
|
||||
// This ensures any path aliases in tsconfig also work in jest
|
||||
...pathsToModuleNameMapper(compilerOptions.paths || {}),
|
||||
"\\.(css|less|sass|scss)$": "identity-obj-proxy",
|
||||
"\\.(gif|ttf|eot|svg|png|jpg|jpeg)$": "<rootDir>/test/__mocks__/fileMock.js",
|
||||
},
|
||||
watchPlugins: ["jest-watch-typeahead/filename", "jest-watch-typeahead/testname"],
|
||||
// Coverage output
|
||||
coverageDirectory: ".coverage",
|
||||
collectCoverageFrom: ["**/*.{js,jsx,ts,tsx}", "!**/*.d.ts", "!**/node_modules/**"],
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
{
|
||||
"name": "@examples/fauna",
|
||||
"version": "0.28.0-canary.1",
|
||||
"scripts": {
|
||||
"start": "blitz start",
|
||||
"studio": "blitz db studio",
|
||||
"build": "blitz build",
|
||||
"lint": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
|
||||
"test": "echo \"No tests yet\""
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
"printWidth": 100
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "tsc && lint-staged && pretty-quick --staged",
|
||||
"pre-push": "npm run lint && npm run test"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,tsx}": [
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"blitz": "0.28.0-canary.1",
|
||||
"final-form": "4.20.1",
|
||||
"graphql": "15.4.0",
|
||||
"graphql-request": "3.4.0",
|
||||
"react": "0.0.0-experimental-3310209d0",
|
||||
"react-dom": "0.0.0-experimental-3310209d0",
|
||||
"react-error-boundary": "3.1.0",
|
||||
"react-final-form": "6.5.2",
|
||||
"secure-password": "4.0.0",
|
||||
"zod": "1.11.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "5.11.8",
|
||||
"@testing-library/react": "11.2.2",
|
||||
"@testing-library/react-hooks": "3.7.0",
|
||||
"@types/jest": "26.0.19",
|
||||
"@types/react": "17.0.0",
|
||||
"@types/secure-password": "3.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "4.11.1",
|
||||
"@typescript-eslint/parser": "4.11.1",
|
||||
"babel-eslint": "10.1.0",
|
||||
"eslint": "7.16.0",
|
||||
"eslint-config-react-app": "6.0.0",
|
||||
"eslint-plugin-flowtype": "5.2.0",
|
||||
"eslint-plugin-import": "2.22.1",
|
||||
"eslint-plugin-jsx-a11y": "6.4.1",
|
||||
"eslint-plugin-react": "7.22.0",
|
||||
"eslint-plugin-react-hooks": "4.2.0",
|
||||
"husky": "4.3.6",
|
||||
"jest": "26.6.3",
|
||||
"jest-environment-jsdom-fourteen": "1.0.1",
|
||||
"jest-watch-typeahead": "0.6.1",
|
||||
"lint-staged": "10.5.3",
|
||||
"prettier": "2.2.1",
|
||||
"pretty-quick": "3.1.0",
|
||||
"start-server-and-test": "1.11.7",
|
||||
"ts-jest": "26.4.4",
|
||||
"typescript": "4.1.3"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 33 KiB |
@@ -1 +0,0 @@
|
||||
module.exports = "test-file-stub"
|
||||
@@ -1,6 +0,0 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import "@testing-library/jest-dom/extend-expect"
|
||||
require("dotenv-flow").config({ silent: true })
|
||||
@@ -1,92 +0,0 @@
|
||||
import { RouterContext, BlitzRouter } from "blitz"
|
||||
import { render as defaultRender } from "@testing-library/react"
|
||||
import { renderHook as defaultRenderHook } from "@testing-library/react-hooks"
|
||||
|
||||
export * from "@testing-library/react"
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// This file customizes the render() and renderHook() test functions provided
|
||||
// by React testing library. It adds a router context wrapper with a mocked router.
|
||||
//
|
||||
// You should always import `render` and `renderHook` from this file
|
||||
//
|
||||
// This is the place to add any other context providers you need while testing.
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
type DefaultParams = Parameters<typeof defaultRender>
|
||||
type RenderUI = DefaultParams[0]
|
||||
type RenderOptions = DefaultParams[1] & { router?: Partial<BlitzRouter> }
|
||||
|
||||
type DefaultHookParams = Parameters<typeof defaultRenderHook>
|
||||
type RenderHook = DefaultHookParams[0]
|
||||
type RenderHookOptions = DefaultHookParams[1] & { router?: Partial<BlitzRouter> }
|
||||
|
||||
// --------------------------------------------------
|
||||
// render()
|
||||
// --------------------------------------------------
|
||||
// Override the default test render with our own
|
||||
//
|
||||
// You can override the router mock like this:
|
||||
//
|
||||
// const { baseElement } = render(<MyComponent />, {
|
||||
// router: { pathname: '/my-custom-pathname' },
|
||||
// });
|
||||
// --------------------------------------------------
|
||||
export function render(ui: RenderUI, { wrapper, router, ...options }: RenderOptions = {}) {
|
||||
if (!wrapper) {
|
||||
// Add a default context wrapper if one isn't supplied from the test
|
||||
wrapper = ({ children }) => (
|
||||
<RouterContext.Provider value={{ ...mockRouter, ...router }}>
|
||||
{children}
|
||||
</RouterContext.Provider>
|
||||
)
|
||||
}
|
||||
return defaultRender(ui, { wrapper, ...options })
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// renderHook()
|
||||
// --------------------------------------------------
|
||||
// Override the default test renderHook with our own
|
||||
//
|
||||
// You can override the router mock like this:
|
||||
//
|
||||
// const result = renderHook(() => myHook(), {
|
||||
// router: { pathname: '/my-custom-pathname' },
|
||||
// });
|
||||
// --------------------------------------------------
|
||||
export function renderHook(
|
||||
hook: RenderHook,
|
||||
{ wrapper, router, ...options }: RenderHookOptions = {}
|
||||
) {
|
||||
if (!wrapper) {
|
||||
// Add a default context wrapper if one isn't supplied from the test
|
||||
wrapper = ({ children }) => (
|
||||
<RouterContext.Provider value={{ ...mockRouter, ...router }}>
|
||||
{children}
|
||||
</RouterContext.Provider>
|
||||
)
|
||||
}
|
||||
return defaultRenderHook(hook, { wrapper, ...options })
|
||||
}
|
||||
|
||||
export const mockRouter: BlitzRouter = {
|
||||
basePath: "",
|
||||
pathname: "/",
|
||||
route: "/",
|
||||
asPath: "/",
|
||||
params: {},
|
||||
query: {},
|
||||
push: jest.fn(),
|
||||
replace: jest.fn(),
|
||||
reload: jest.fn(),
|
||||
back: jest.fn(),
|
||||
prefetch: jest.fn(),
|
||||
beforePopState: jest.fn(),
|
||||
events: {
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
emit: jest.fn(),
|
||||
},
|
||||
isFallback: false,
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { DefaultCtx, SessionContext, DefaultPublicData } from "blitz"
|
||||
import { User } from "db"
|
||||
|
||||
declare module "blitz" {
|
||||
export interface Ctx extends DefaultCtx {
|
||||
session: SessionContext
|
||||
}
|
||||
export interface PublicData extends DefaultPublicData {
|
||||
userId: User["id"]
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "no-prisma",
|
||||
"version": "0.28.0-canary.1",
|
||||
"version": "0.23.1-canary.0",
|
||||
"scripts": {
|
||||
"start": "blitz start",
|
||||
"build": "blitz build",
|
||||
@@ -26,29 +26,29 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"blitz": "0.28.0-canary.1",
|
||||
"knex": "0.21.15",
|
||||
"react": "0.0.0-experimental-3310209d0",
|
||||
"react-dom": "0.0.0-experimental-3310209d0",
|
||||
"blitz": "0.23.1-canary.0",
|
||||
"knex": "0.21.2",
|
||||
"react": "0.0.0-experimental-7f28234f8",
|
||||
"react-dom": "0.0.0-experimental-7f28234f8",
|
||||
"sqlite3": "5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "17.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "4.11.1",
|
||||
"@typescript-eslint/parser": "4.11.1",
|
||||
"@types/react": "16.9.43",
|
||||
"@typescript-eslint/eslint-plugin": "2.34.1-alpha.2",
|
||||
"@typescript-eslint/parser": "2.34.1-alpha.2",
|
||||
"babel-eslint": "10.1.0",
|
||||
"eslint": "7.16.0",
|
||||
"eslint-config-react-app": "6.0.0",
|
||||
"eslint": "7.6.0",
|
||||
"eslint-config-react-app": "5.2.1",
|
||||
"eslint-plugin-flowtype": "5.2.0",
|
||||
"eslint-plugin-import": "2.22.1",
|
||||
"eslint-plugin-jsx-a11y": "6.4.1",
|
||||
"eslint-plugin-react": "7.22.0",
|
||||
"eslint-plugin-react-hooks": "4.2.0",
|
||||
"husky": "4.3.6",
|
||||
"lint-staged": "10.5.3",
|
||||
"prettier": "2.2.1",
|
||||
"pretty-quick": "3.1.0",
|
||||
"typescript": "4.1.3"
|
||||
"eslint-plugin-import": "2.22.0",
|
||||
"eslint-plugin-jsx-a11y": "6.3.1",
|
||||
"eslint-plugin-react": "7.20.5",
|
||||
"eslint-plugin-react-hooks": "4.0.8",
|
||||
"husky": "4.2.5",
|
||||
"lint-staged": "10.2.13",
|
||||
"prettier": "2.0.5",
|
||||
"pretty-quick": "2.0.1",
|
||||
"typescript": "3.9.6"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export const EditProject = () => {
|
||||
},
|
||||
})
|
||||
alert("Success!" + JSON.stringify(updated))
|
||||
router.push(`/projects/${updated.id}`)
|
||||
router.push("/projects/[id]", `/projects/${updated.id}`)
|
||||
} catch (error) {
|
||||
alert("Error creating project " + JSON.stringify(error, null, 2))
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ const NewProjectPage = () => {
|
||||
},
|
||||
})
|
||||
alert("Success!" + JSON.stringify(project))
|
||||
router.push(`/projects/${project.id}`)
|
||||
router.push("/projects/[id]", `/projects/${project.id}`)
|
||||
} catch (error) {
|
||||
alert("Error creating project " + JSON.stringify(error, null, 2))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import db from "db"
|
||||
export default async function getProject(args) {
|
||||
const project = await db.project.findFirst(args)
|
||||
const project = await db.project.findOne(args)
|
||||
return project
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@examples/plain-js",
|
||||
"version": "0.28.0-canary.1",
|
||||
"version": "0.23.1-canary.0",
|
||||
"scripts": {
|
||||
"start": "blitz start",
|
||||
"build": "blitz db migrate && blitz build",
|
||||
@@ -29,28 +29,29 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/cli": "2.12.0",
|
||||
"@prisma/client": "2.12.0",
|
||||
"blitz": "0.28.0-canary.1",
|
||||
"react": "0.0.0-experimental-3310209d0",
|
||||
"react-dom": "0.0.0-experimental-3310209d0"
|
||||
"@prisma/cli": "2.4.1",
|
||||
"@prisma/client": "2.4.1",
|
||||
"blitz": "0.23.1-canary.0",
|
||||
"react": "0.0.0-experimental-7f28234f8",
|
||||
"react-dom": "0.0.0-experimental-7f28234f8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "4.11.1",
|
||||
"@typescript-eslint/parser": "4.11.1",
|
||||
"@types/react": "16.9.35",
|
||||
"@typescript-eslint/eslint-plugin": "2.31.0",
|
||||
"@typescript-eslint/parser": "2.31.0",
|
||||
"babel-eslint": "10.1.0",
|
||||
"eslint": "7.16.0",
|
||||
"eslint-config-react-app": "6.0.0",
|
||||
"eslint": "7.6.0",
|
||||
"eslint-config-react-app": "5.2.1",
|
||||
"eslint-plugin-flowtype": "5.2.0",
|
||||
"eslint-plugin-import": "2.22.1",
|
||||
"eslint-plugin-jsx-a11y": "6.4.1",
|
||||
"eslint-plugin-react": "7.22.0",
|
||||
"eslint-plugin-react-hooks": "4.2.0",
|
||||
"husky": "4.3.6",
|
||||
"lint-staged": "10.5.3",
|
||||
"prettier": "2.2.1",
|
||||
"pretty-quick": "3.1.0",
|
||||
"typescript": "4.1.3"
|
||||
"eslint-plugin-import": "2.22.0",
|
||||
"eslint-plugin-jsx-a11y": "6.3.1",
|
||||
"eslint-plugin-react": "7.20.5",
|
||||
"eslint-plugin-react-hooks": "4.0.8",
|
||||
"husky": "4.2.5",
|
||||
"lint-staged": "10.2.2",
|
||||
"prettier": "2.0.5",
|
||||
"pretty-quick": "2.0.1",
|
||||
"typescript": "3.9.2"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +0,0 @@
|
||||
import db, {Product} from "db"
|
||||
import {BlitzApiRequest, BlitzApiResponse} from "blitz"
|
||||
import {mean} from "lodash"
|
||||
|
||||
// this is here mainly as an integration test for
|
||||
// importing from api/
|
||||
export function meanPrice(products: Product[]) {
|
||||
const prices = products.map((p) => p.price).filter((p) => !!p) as number[]
|
||||
return mean(prices)
|
||||
}
|
||||
|
||||
export default async function users(_req: BlitzApiRequest, res: BlitzApiResponse) {
|
||||
const products = await db.product.findMany()
|
||||
res.status(200).send(products)
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/* integration test for scss modules */
|
||||
.red {
|
||||
border: 2px solid red;
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
import {Link} from "blitz"
|
||||
|
||||
import styles from "./index.module.scss"
|
||||
|
||||
function StoreAdminPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1 className={styles.red}>Store Admin</h1>
|
||||
<h1>Store Admin</h1>
|
||||
<div>
|
||||
<p>
|
||||
<Link href="/admin/products">
|
||||
|
||||
@@ -2,20 +2,19 @@ import {Suspense} from "react"
|
||||
import {Link, useRouter, useQuery, useParam} from "blitz"
|
||||
import getProduct from "app/products/queries/getProduct"
|
||||
import ProductForm from "app/products/components/ProductForm"
|
||||
import {queryCache} from "react-query"
|
||||
|
||||
function Product() {
|
||||
const router = useRouter()
|
||||
const id = useParam("id", "number")
|
||||
const [product] = useQuery(getProduct, {where: {id}})
|
||||
|
||||
// Here to test for https://github.com/blitz-js/blitz/issues/1443
|
||||
if (!product) throw new Error("useQuery did not throw!")
|
||||
|
||||
return (
|
||||
<ProductForm
|
||||
product={product}
|
||||
onSuccess={async () => {
|
||||
await router.push("/admin/products")
|
||||
onSuccess={() => {
|
||||
queryCache.invalidateQueries("/api/products/queries/getProducts")
|
||||
router.push("/admin/products")
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1,56 +1,28 @@
|
||||
import {Suspense, useState} from "react"
|
||||
import {useQuery, Link, useRouterQuery, invalidateQuery, setQueryData} from "blitz"
|
||||
import {Suspense} from "react"
|
||||
import {useQuery, Link, useRouterQuery} from "blitz"
|
||||
import getProducts from "app/products/queries/getProducts"
|
||||
import {meanPrice} from "app/admin/api/users"
|
||||
|
||||
function reversedProductList(productsList) {
|
||||
return {...productsList, products: [...productsList.products].reverse()}
|
||||
}
|
||||
import getProduct from "app/products/queries/getProduct"
|
||||
|
||||
function ProductsList() {
|
||||
const {orderby = "id", order = "desc"} = useRouterQuery()
|
||||
const [refetch, setRefetch] = useState(false)
|
||||
const params = {
|
||||
|
||||
const [{products}] = useQuery(getProducts, {
|
||||
orderBy: {
|
||||
[Array.isArray(orderby) ? orderby[0] : orderby]: order,
|
||||
},
|
||||
}
|
||||
|
||||
const [{products}] = useQuery(getProducts, params)
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => setQueryData(getProducts, params, reversedProductList, {refetch})}>
|
||||
Reverse
|
||||
</button>
|
||||
<label>
|
||||
<input
|
||||
name="refetch"
|
||||
type="checkbox"
|
||||
checked={refetch}
|
||||
onChange={(event) => setRefetch(event.target.checked)}
|
||||
/>
|
||||
Refetch
|
||||
</label>
|
||||
|
||||
<ul>
|
||||
{products.map((product) => (
|
||||
<li key={product.id}>
|
||||
<Link href="/admin/products/[id]" as={`/admin/products/${product.id}`}>
|
||||
<a
|
||||
// Disable until prefetch api added
|
||||
//onMouseEnter={() => getProduct({where: {id: product.id}})}
|
||||
>
|
||||
{product.name}
|
||||
</a>
|
||||
</Link>{" "}
|
||||
- Created: {product.createdAt.toISOString()}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<p>Mean price: {meanPrice(products)}</p>
|
||||
</>
|
||||
<ul>
|
||||
{products.map((product) => (
|
||||
<li key={product.id}>
|
||||
<Link href="/admin/products/[id]" as={`/admin/products/${product.id}`}>
|
||||
<a onMouseEnter={() => getProduct({where: {id: product.id}})}>{product.name}</a>
|
||||
</Link>{" "}
|
||||
- Created: {product.createdAt.toISOString()}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -59,8 +31,6 @@ function AdminProducts() {
|
||||
<div>
|
||||
<h1>Products</h1>
|
||||
|
||||
<button onClick={() => invalidateQuery(getProducts)}>Invalidate query</button>
|
||||
|
||||
<p>
|
||||
<Link href="/admin/products/new">
|
||||
<a>Create Product</a>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user