diff --git a/.env b/.env
index c5c1fa4..3fa1d4f 100644
--- a/.env
+++ b/.env
@@ -1,5 +1,22 @@
NODE_ENV=development
// IP_ADDRESS=192.168.0.203
IP_ADDRESS=localhost
-NEXT_PUBLIC_API_ENDPOINT=http://localhost:8000/api/v1/
-NEXT_PUBLIC_MOCK_API_ENDPOINT=http://localhost:3000/api
+# NEXT_PUBLIC_API_ENDPOINT=http://localhost:8000/api/v1/
+# NEXT_PUBLIC_MOCK_API_ENDPOINT=http://localhost:3000/api
+
+# - SHARED **************************************************************************************************
+NEXT_PUBLIC_WEBAPP_URL='http://localhost:3000'
+# Change to 'http://localhost:3001' if running the website simultaneously
+NEXT_PUBLIC_WEBSITE_URL='http://localhost:3000'
+
+# - NEXTAUTH
+# Required for Vercel hosting - set NEXTAUTH_URL to equal your BASE_URL
+NEXTAUTH_URL='http://localhost:3000'
+JWT_SECRET='secret'
+# Used for cross-domain cookie authentication
+# NEXTAUTH_COOKIE_DOMAIN=.example.com
+
+POSTGRES_USER=seye
+POSTGRES_PASSWORD=seye
+POSTGRES_DB=seyedb
+DATABASE_URL=postgresql://seye:seye@localhost:5432/seyedb?schema=public&sslmode=prefer&connect_timeout=300
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..b8756a9
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,13 @@
+version: "3.8"
+services:
+ postgres:
+ image: postgres:14
+ restart: always
+ env_file:
+ - .env
+ volumes:
+ - postgres:/var/lib/postgresql/data
+ ports:
+ - "5432:5432"
+volumes:
+ postgres:
diff --git a/package-lock.json b/package-lock.json
index b8518e2..a406951 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,26 +7,34 @@
"dependencies": {
"@headlessui/react": "^1.5.0",
"@heroicons/react": "^1.0.6",
+ "@next-auth/prisma-adapter": "^1.0.3",
+ "@prisma/client": "^3.12.0",
+ "@radix-ui/react-id": "^0.1.5",
"@reduxjs/toolkit": "^1.7.2",
"@turf/turf": "^6.5.0",
"axios": "^0.26.0",
+ "bcryptjs": "^2.4.3",
"cssnano": "^5.1.7",
"maplibre-gl": "^2.1.6",
"next": "12.1.0",
+ "next-auth": "^4.3.1",
"next-images": "^1.8.4",
"react": "17.0.2",
"react-dom": "17.0.2",
- "react-hook-form": "^7.27.1",
+ "react-hook-form": "^7.29.0",
+ "react-hot-toast": "^2.2.0",
"react-icons": "^4.3.1",
"react-query": "^3.34.16",
"react-redux": "^7.2.6",
"react-scroll": "^1.8.6",
"react-window": "^1.8.6",
"sass": "^1.49.9",
+ "short-uuid": "^4.2.0",
"styled-components": "^5.3.3"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.0",
+ "@types/bcryptjs": "^2.4.2",
"@types/node": "17.0.18",
"@types/react": "17.0.39",
"@types/react-scroll": "^1.8.3",
@@ -41,10 +49,12 @@
"file-loader": "^6.2.0",
"postcss": "^8.4.5",
"postcss-loader": "^6.2.1",
+ "prisma": "^3.12.0",
"sass-loader": "^12.6.0",
"style-loader": "^3.3.1",
"tailwindcss": "^3.0.23",
"ts-loader": "^9.2.6",
+ "ts-node": "^10.7.0",
"tsconfig-paths": "^3.12.0",
"typescript": "4.5.5",
"webpack": "^5.69.1",
@@ -335,6 +345,27 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@cspotcode/source-map-consumer": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz",
+ "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz",
+ "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==",
+ "dev": true,
+ "dependencies": {
+ "@cspotcode/source-map-consumer": "0.8.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@discoveryjs/json-ext": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz",
@@ -487,6 +518,15 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@next-auth/prisma-adapter": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@next-auth/prisma-adapter/-/prisma-adapter-1.0.3.tgz",
+ "integrity": "sha512-3Lq1cD3ytKM3EGKJZ4UZvlqshLtlPvYxLeCrUV9ifYwYlq51kmDaHjsIawlp8EbH5pE1UhlsvtlXMery7RghtA==",
+ "peerDependencies": {
+ "@prisma/client": ">=2.26.0 || >=3",
+ "next-auth": "^4.0.1"
+ }
+ },
"node_modules/@next/env": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/@next/env/-/env-12.1.0.tgz",
@@ -701,6 +741,70 @@
"node": ">= 8"
}
},
+ "node_modules/@panva/hkdf": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.0.1.tgz",
+ "integrity": "sha512-mMyQ9vjpuFqePkfe5bZVIf/H3Dmk6wA8Kjxff9RcO4kqzJo+Ek9pGKwZHpeMr7Eku0QhLXMCd7fNCSnEnRMubg==",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/@prisma/client": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-3.12.0.tgz",
+ "integrity": "sha512-4NEQjUcWja/NVBvfuDFscWSk1/rXg3+wj+TSkqXCb1tKlx/bsUE00rxsvOvGg7VZ6lw1JFpGkwjwmsOIc4zvQw==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@prisma/engines-version": "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980"
+ },
+ "engines": {
+ "node": ">=12.6"
+ },
+ "peerDependencies": {
+ "prisma": "*"
+ },
+ "peerDependenciesMeta": {
+ "prisma": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@prisma/engines": {
+ "version": "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980",
+ "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980.tgz",
+ "integrity": "sha512-zULjkN8yhzS7B3yeEz4aIym4E2w1ChrV12i14pht3ePFufvsAvBSoZ+tuXMvfSoNTgBS5E4bolRzLbMmbwkkMQ==",
+ "devOptional": true,
+ "hasInstallScript": true
+ },
+ "node_modules/@prisma/engines-version": {
+ "version": "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980",
+ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980.tgz",
+ "integrity": "sha512-o+jo8d7ZEiVpcpNWUDh3fj2uPQpBxl79XE9ih9nkogJbhw6P33274SHnqheedZ7PyvPIK/mvU8MLNYgetgXPYw=="
+ },
+ "node_modules/@radix-ui/react-id": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-0.1.5.tgz",
+ "integrity": "sha512-IPc4H/63bes0IZ1GJJozSEkSWcDyhNGtKFWUpJ+XtaLyQ1X3x7Mf6fWwWhDcpqlYEP+5WtAvfqcyEsyjP+ZhBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-use-layout-effect": "0.1.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0"
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-0.1.0.tgz",
+ "integrity": "sha512-+wdeS51Y+E1q1Wmd+1xSSbesZkpVj4jsg0BojCbopWvgq5iBvixw5vgemscdh58ep98BwUbsFYnrywFhV9yrVg==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0"
+ }
+ },
"node_modules/@reduxjs/toolkit": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.7.2.tgz",
@@ -750,6 +854,30 @@
"node": ">=10.13.0"
}
},
+ "node_modules/@tsconfig/node10": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
+ "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==",
+ "dev": true
+ },
+ "node_modules/@tsconfig/node12": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz",
+ "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==",
+ "dev": true
+ },
+ "node_modules/@tsconfig/node14": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz",
+ "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==",
+ "dev": true
+ },
+ "node_modules/@tsconfig/node16": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz",
+ "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==",
+ "dev": true
+ },
"node_modules/@turf/along": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@turf/along/-/along-6.5.0.tgz",
@@ -2315,6 +2443,12 @@
"url": "https://opencollective.com/turf"
}
},
+ "node_modules/@types/bcryptjs": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz",
+ "integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==",
+ "dev": true
+ },
"node_modules/@types/eslint": {
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz",
@@ -2815,6 +2949,11 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/any-base": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz",
+ "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="
+ },
"node_modules/anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
@@ -3052,6 +3191,11 @@
],
"optional": true
},
+ "node_modules/bcryptjs": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
+ "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms="
+ },
"node_modules/big-integer": {
"version": "1.6.51",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
@@ -3456,6 +3600,14 @@
"dev": true,
"optional": true
},
+ "node_modules/cookie": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
+ "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/core-js-pure": {
"version": "3.21.1",
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz",
@@ -3490,6 +3642,12 @@
"node": ">=10"
}
},
+ "node_modules/create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true
+ },
"node_modules/cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
@@ -3942,6 +4100,15 @@
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"dev": true
},
+ "node_modules/diff": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -5017,6 +5184,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/goober": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.8.tgz",
+ "integrity": "sha512-S0C85gCzcfFCMSdjD/CxyQMt1rbf2qEg6hmDzxk2FfD7+7Ogk55m8ZFUMtqNaZM4VVX/qaU9AzSORG+Gf4ZpAQ==",
+ "peerDependencies": {
+ "csstype": "^3.0.10"
+ }
+ },
"node_modules/graceful-fs": {
"version": "4.2.9",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
@@ -5594,6 +5769,14 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
+ "node_modules/jose": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-4.6.1.tgz",
+ "integrity": "sha512-EFnufEivlIB6j7+JwaenYQzdUDs/McajDr9WnhT6EI0WxbexnfuZimpWX1GnobF6OnQsUFmWFXUXdWyZHWdQow==",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
"node_modules/js-sha3": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
@@ -5828,7 +6011,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
@@ -5836,6 +6018,12 @@
"node": ">=10"
}
},
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true
+ },
"node_modules/maplibre-gl": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-2.1.6.tgz",
@@ -6096,6 +6284,35 @@
}
}
},
+ "node_modules/next-auth": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.3.1.tgz",
+ "integrity": "sha512-DBYEPBLq5naIqh/1i2zEHljcA1OXXecKW3NRU1W4s6R3UX3RdLZ2lWlqgBHUiZQ1zdNikFM/bYQxVGyG7bx8oA==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.3",
+ "@panva/hkdf": "^1.0.1",
+ "cookie": "^0.4.1",
+ "jose": "^4.3.7",
+ "oauth": "^0.9.15",
+ "openid-client": "^5.1.0",
+ "preact": "^10.6.3",
+ "preact-render-to-string": "^5.1.19",
+ "uuid": "^8.3.2"
+ },
+ "engines": {
+ "node": "^12.19.0 || ^14.15.0 || ^16.13.0"
+ },
+ "peerDependencies": {
+ "nodemailer": "^6.6.5",
+ "react": "^17.0.2 || ^18.0.0-0",
+ "react-dom": "^17.0.2 || ^18.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "nodemailer": {
+ "optional": true
+ }
+ }
+ },
"node_modules/next-images": {
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/next-images/-/next-images-1.8.4.tgz",
@@ -6214,6 +6431,11 @@
"node": ">=0.10.0"
}
},
+ "node_modules/oauth": {
+ "version": "0.9.15",
+ "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
+ "integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE="
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -6226,7 +6448,6 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
- "dev": true,
"engines": {
"node": ">= 6"
}
@@ -6347,6 +6568,14 @@
"resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz",
"integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw=="
},
+ "node_modules/oidc-token-hash": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz",
+ "integrity": "sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ==",
+ "engines": {
+ "node": "^10.13.0 || >=12.0.0"
+ }
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -6370,6 +6599,23 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/openid-client": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.1.4.tgz",
+ "integrity": "sha512-36/PZY3rDgiIFj2uCL9a1fILPmIwu3HksoWO4mukgXe74ZOsEisJMMqTMfmPNw6j/7kO0mBc2xqy4eYRrB8xPA==",
+ "dependencies": {
+ "jose": "^4.1.4",
+ "lru-cache": "^6.0.0",
+ "object-hash": "^2.0.1",
+ "oidc-token-hash": "^5.0.1"
+ },
+ "engines": {
+ "node": "^12.19.0 || ^14.15.0 || ^16.13.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
"node_modules/optionator": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@@ -7219,6 +7465,26 @@
"resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
"integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ=="
},
+ "node_modules/preact": {
+ "version": "10.7.1",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.7.1.tgz",
+ "integrity": "sha512-MufnRFz39aIhs9AMFisonjzTud1PK1bY+jcJLo6m2T9Uh8AqjD77w11eAAawmjUogoGOnipECq7e/1RClIKsxg==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/preact"
+ }
+ },
+ "node_modules/preact-render-to-string": {
+ "version": "5.1.21",
+ "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.1.21.tgz",
+ "integrity": "sha512-wbMtNU4JpfvbE04iCe7BZ1yLYN8i6NRrq+NhR0fUINjPXGu3ZIc4GM5ScOiwdIP1sPXv9SVETuud/tmQGMvdNQ==",
+ "dependencies": {
+ "pretty-format": "^3.8.0"
+ },
+ "peerDependencies": {
+ "preact": ">=10"
+ }
+ },
"node_modules/prebuild-install": {
"version": "5.3.6",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.6.tgz",
@@ -7258,6 +7524,28 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/pretty-format": {
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
+ "integrity": "sha1-v77VbV6ad2ZF9LH/eqGjrE+jw4U="
+ },
+ "node_modules/prisma": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/prisma/-/prisma-3.12.0.tgz",
+ "integrity": "sha512-ltCMZAx1i0i9xuPM692Srj8McC665h6E5RqJom999sjtVSccHSD8Z+HSdBN2183h9PJKvC5dapkn78dd0NWMBg==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "@prisma/engines": "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980"
+ },
+ "bin": {
+ "prisma": "build/index.js",
+ "prisma2": "build/index.js"
+ },
+ "engines": {
+ "node": ">=12.6"
+ }
+ },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -7409,9 +7697,9 @@
}
},
"node_modules/react-hook-form": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.27.1.tgz",
- "integrity": "sha512-N3a7A6zIQ8DJeThisVZGtOUabTbJw+7DHJidmB9w8m3chckv2ZWKb5MHps9d2pPJqmCDoWe53Bos56bYmJms5w==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.29.0.tgz",
+ "integrity": "sha512-NcJqWRF6el5HMW30fqZRt27s+lorvlCCDbTpAyHoodQeYWXgQCvZJJQLC1kRMKdrJknVH0NIg3At6TUzlZJFOQ==",
"engines": {
"node": ">=12.22.0"
},
@@ -7420,7 +7708,22 @@
"url": "https://opencollective.com/react-hook-form"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17"
+ "react": "^16.8.0 || ^17 || ^18"
+ }
+ },
+ "node_modules/react-hot-toast": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.2.0.tgz",
+ "integrity": "sha512-248rXw13uhf/6TNDVzagX+y7R8J183rp7MwUMNkcrBRyHj/jWOggfXTGlM8zAOuh701WyVW+eUaWG2LeSufX9g==",
+ "dependencies": {
+ "goober": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">=16",
+ "react-dom": ">=16"
}
},
"node_modules/react-icons": {
@@ -7898,6 +8201,18 @@
"node": ">=8"
}
},
+ "node_modules/short-uuid": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-4.2.0.tgz",
+ "integrity": "sha512-r3cxuPPZSuF0QkKsK9bBR7u+7cwuCRzWzgjPh07F5N2iIUNgblnMHepBY16xgj5t1lG9iOP9k/TEafY1qhRzaw==",
+ "dependencies": {
+ "any-base": "^1.1.0",
+ "uuid": "^8.3.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
@@ -8556,6 +8871,65 @@
"webpack": "^5.0.0"
}
},
+ "node_modules/ts-node": {
+ "version": "10.7.0",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz",
+ "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@cspotcode/source-map-support": "0.7.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.0",
+ "yn": "3.1.1"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js",
+ "ts-node-cwd": "dist/bin-cwd.js",
+ "ts-node-esm": "dist/bin-esm.js",
+ "ts-node-script": "dist/bin-script.js",
+ "ts-node-transpile-only": "dist/bin-transpile.js",
+ "ts-script": "dist/bin-script-deprecated.js"
+ },
+ "peerDependencies": {
+ "@swc/core": ">=1.2.50",
+ "@swc/wasm": ">=1.2.50",
+ "@types/node": "*",
+ "typescript": ">=2.7"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "@swc/wasm": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ts-node/node_modules/acorn-walk": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
+ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/ts-node/node_modules/arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true
+ },
"node_modules/tsconfig-paths": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz",
@@ -8718,12 +9092,26 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
+ "node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"node_modules/v8-compile-cache": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true
},
+ "node_modules/v8-compile-cache-lib": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz",
+ "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==",
+ "dev": true
+ },
"node_modules/vt-pbf": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz",
@@ -8968,8 +9356,7 @@
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yaml": {
"version": "1.10.2",
@@ -8978,6 +9365,15 @@
"engines": {
"node": ">= 6"
}
+ },
+ "node_modules/yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
}
},
"dependencies": {
@@ -9196,6 +9592,21 @@
"to-fast-properties": "^2.0.0"
}
},
+ "@cspotcode/source-map-consumer": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz",
+ "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==",
+ "dev": true
+ },
+ "@cspotcode/source-map-support": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz",
+ "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==",
+ "dev": true,
+ "requires": {
+ "@cspotcode/source-map-consumer": "0.8.0"
+ }
+ },
"@discoveryjs/json-ext": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz",
@@ -9321,6 +9732,12 @@
"resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz",
"integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q=="
},
+ "@next-auth/prisma-adapter": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@next-auth/prisma-adapter/-/prisma-adapter-1.0.3.tgz",
+ "integrity": "sha512-3Lq1cD3ytKM3EGKJZ4UZvlqshLtlPvYxLeCrUV9ifYwYlq51kmDaHjsIawlp8EbH5pE1UhlsvtlXMery7RghtA==",
+ "requires": {}
+ },
"@next/env": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/@next/env/-/env-12.1.0.tgz",
@@ -9427,6 +9844,47 @@
"fastq": "^1.6.0"
}
},
+ "@panva/hkdf": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.0.1.tgz",
+ "integrity": "sha512-mMyQ9vjpuFqePkfe5bZVIf/H3Dmk6wA8Kjxff9RcO4kqzJo+Ek9pGKwZHpeMr7Eku0QhLXMCd7fNCSnEnRMubg=="
+ },
+ "@prisma/client": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-3.12.0.tgz",
+ "integrity": "sha512-4NEQjUcWja/NVBvfuDFscWSk1/rXg3+wj+TSkqXCb1tKlx/bsUE00rxsvOvGg7VZ6lw1JFpGkwjwmsOIc4zvQw==",
+ "requires": {
+ "@prisma/engines-version": "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980"
+ }
+ },
+ "@prisma/engines": {
+ "version": "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980",
+ "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980.tgz",
+ "integrity": "sha512-zULjkN8yhzS7B3yeEz4aIym4E2w1ChrV12i14pht3ePFufvsAvBSoZ+tuXMvfSoNTgBS5E4bolRzLbMmbwkkMQ==",
+ "devOptional": true
+ },
+ "@prisma/engines-version": {
+ "version": "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980",
+ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980.tgz",
+ "integrity": "sha512-o+jo8d7ZEiVpcpNWUDh3fj2uPQpBxl79XE9ih9nkogJbhw6P33274SHnqheedZ7PyvPIK/mvU8MLNYgetgXPYw=="
+ },
+ "@radix-ui/react-id": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-0.1.5.tgz",
+ "integrity": "sha512-IPc4H/63bes0IZ1GJJozSEkSWcDyhNGtKFWUpJ+XtaLyQ1X3x7Mf6fWwWhDcpqlYEP+5WtAvfqcyEsyjP+ZhBQ==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-use-layout-effect": "0.1.0"
+ }
+ },
+ "@radix-ui/react-use-layout-effect": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-0.1.0.tgz",
+ "integrity": "sha512-+wdeS51Y+E1q1Wmd+1xSSbesZkpVj4jsg0BojCbopWvgq5iBvixw5vgemscdh58ep98BwUbsFYnrywFhV9yrVg==",
+ "requires": {
+ "@babel/runtime": "^7.13.10"
+ }
+ },
"@reduxjs/toolkit": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.7.2.tgz",
@@ -9458,6 +9916,30 @@
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA=="
},
+ "@tsconfig/node10": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
+ "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==",
+ "dev": true
+ },
+ "@tsconfig/node12": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz",
+ "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==",
+ "dev": true
+ },
+ "@tsconfig/node14": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz",
+ "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==",
+ "dev": true
+ },
+ "@tsconfig/node16": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz",
+ "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==",
+ "dev": true
+ },
"@turf/along": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@turf/along/-/along-6.5.0.tgz",
@@ -10701,6 +11183,12 @@
"d3-voronoi": "1.1.2"
}
},
+ "@types/bcryptjs": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz",
+ "integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==",
+ "dev": true
+ },
"@types/eslint": {
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz",
@@ -11108,6 +11596,11 @@
"color-convert": "^2.0.1"
}
},
+ "any-base": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz",
+ "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="
+ },
"anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
@@ -11282,6 +11775,11 @@
"dev": true,
"optional": true
},
+ "bcryptjs": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
+ "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms="
+ },
"big-integer": {
"version": "1.6.51",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
@@ -11602,6 +12100,11 @@
"dev": true,
"optional": true
},
+ "cookie": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
+ "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="
+ },
"core-js-pure": {
"version": "3.21.1",
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz",
@@ -11628,6 +12131,12 @@
"yaml": "^1.10.0"
}
},
+ "create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true
+ },
"cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
@@ -11965,6 +12474,12 @@
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"dev": true
},
+ "diff": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "dev": true
+ },
"dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -12786,6 +13301,12 @@
"slash": "^3.0.0"
}
},
+ "goober": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.8.tgz",
+ "integrity": "sha512-S0C85gCzcfFCMSdjD/CxyQMt1rbf2qEg6hmDzxk2FfD7+7Ogk55m8ZFUMtqNaZM4VVX/qaU9AzSORG+Gf4ZpAQ==",
+ "requires": {}
+ },
"graceful-fs": {
"version": "4.2.9",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
@@ -13178,6 +13699,11 @@
}
}
},
+ "jose": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-4.6.1.tgz",
+ "integrity": "sha512-EFnufEivlIB6j7+JwaenYQzdUDs/McajDr9WnhT6EI0WxbexnfuZimpWX1GnobF6OnQsUFmWFXUXdWyZHWdQow=="
+ },
"js-sha3": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
@@ -13369,11 +13895,16 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
+ "make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true
+ },
"maplibre-gl": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-2.1.6.tgz",
@@ -13575,6 +14106,22 @@
"use-subscription": "1.5.1"
}
},
+ "next-auth": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.3.1.tgz",
+ "integrity": "sha512-DBYEPBLq5naIqh/1i2zEHljcA1OXXecKW3NRU1W4s6R3UX3RdLZ2lWlqgBHUiZQ1zdNikFM/bYQxVGyG7bx8oA==",
+ "requires": {
+ "@babel/runtime": "^7.16.3",
+ "@panva/hkdf": "^1.0.1",
+ "cookie": "^0.4.1",
+ "jose": "^4.3.7",
+ "oauth": "^0.9.15",
+ "openid-client": "^5.1.0",
+ "preact": "^10.6.3",
+ "preact-render-to-string": "^5.1.19",
+ "uuid": "^8.3.2"
+ }
+ },
"next-images": {
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/next-images/-/next-images-1.8.4.tgz",
@@ -13668,6 +14215,11 @@
"dev": true,
"optional": true
},
+ "oauth": {
+ "version": "0.9.15",
+ "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
+ "integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE="
+ },
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -13676,8 +14228,7 @@
"object-hash": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
- "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
- "dev": true
+ "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="
},
"object-inspect": {
"version": "1.12.0",
@@ -13759,6 +14310,11 @@
"resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz",
"integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw=="
},
+ "oidc-token-hash": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz",
+ "integrity": "sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ=="
+ },
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -13776,6 +14332,17 @@
"mimic-fn": "^2.1.0"
}
},
+ "openid-client": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.1.4.tgz",
+ "integrity": "sha512-36/PZY3rDgiIFj2uCL9a1fILPmIwu3HksoWO4mukgXe74ZOsEisJMMqTMfmPNw6j/7kO0mBc2xqy4eYRrB8xPA==",
+ "requires": {
+ "jose": "^4.1.4",
+ "lru-cache": "^6.0.0",
+ "object-hash": "^2.0.1",
+ "oidc-token-hash": "^5.0.1"
+ }
+ },
"optionator": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@@ -14316,6 +14883,19 @@
"resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
"integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ=="
},
+ "preact": {
+ "version": "10.7.1",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.7.1.tgz",
+ "integrity": "sha512-MufnRFz39aIhs9AMFisonjzTud1PK1bY+jcJLo6m2T9Uh8AqjD77w11eAAawmjUogoGOnipECq7e/1RClIKsxg=="
+ },
+ "preact-render-to-string": {
+ "version": "5.1.21",
+ "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.1.21.tgz",
+ "integrity": "sha512-wbMtNU4JpfvbE04iCe7BZ1yLYN8i6NRrq+NhR0fUINjPXGu3ZIc4GM5ScOiwdIP1sPXv9SVETuud/tmQGMvdNQ==",
+ "requires": {
+ "pretty-format": "^3.8.0"
+ }
+ },
"prebuild-install": {
"version": "5.3.6",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.6.tgz",
@@ -14346,6 +14926,20 @@
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true
},
+ "pretty-format": {
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
+ "integrity": "sha1-v77VbV6ad2ZF9LH/eqGjrE+jw4U="
+ },
+ "prisma": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/prisma/-/prisma-3.12.0.tgz",
+ "integrity": "sha512-ltCMZAx1i0i9xuPM692Srj8McC665h6E5RqJom999sjtVSccHSD8Z+HSdBN2183h9PJKvC5dapkn78dd0NWMBg==",
+ "devOptional": true,
+ "requires": {
+ "@prisma/engines": "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980"
+ }
+ },
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -14466,11 +15060,19 @@
}
},
"react-hook-form": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.27.1.tgz",
- "integrity": "sha512-N3a7A6zIQ8DJeThisVZGtOUabTbJw+7DHJidmB9w8m3chckv2ZWKb5MHps9d2pPJqmCDoWe53Bos56bYmJms5w==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.29.0.tgz",
+ "integrity": "sha512-NcJqWRF6el5HMW30fqZRt27s+lorvlCCDbTpAyHoodQeYWXgQCvZJJQLC1kRMKdrJknVH0NIg3At6TUzlZJFOQ==",
"requires": {}
},
+ "react-hot-toast": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.2.0.tgz",
+ "integrity": "sha512-248rXw13uhf/6TNDVzagX+y7R8J183rp7MwUMNkcrBRyHj/jWOggfXTGlM8zAOuh701WyVW+eUaWG2LeSufX9g==",
+ "requires": {
+ "goober": "^2.1.1"
+ }
+ },
"react-icons": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz",
@@ -14783,6 +15385,15 @@
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true
},
+ "short-uuid": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-4.2.0.tgz",
+ "integrity": "sha512-r3cxuPPZSuF0QkKsK9bBR7u+7cwuCRzWzgjPh07F5N2iIUNgblnMHepBY16xgj5t1lG9iOP9k/TEafY1qhRzaw==",
+ "requires": {
+ "any-base": "^1.1.0",
+ "uuid": "^8.3.2"
+ }
+ },
"side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
@@ -15244,6 +15855,41 @@
"semver": "^7.3.4"
}
},
+ "ts-node": {
+ "version": "10.7.0",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz",
+ "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==",
+ "dev": true,
+ "requires": {
+ "@cspotcode/source-map-support": "0.7.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.0",
+ "yn": "3.1.1"
+ },
+ "dependencies": {
+ "acorn-walk": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
+ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
+ "dev": true
+ },
+ "arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true
+ }
+ }
+ },
"tsconfig-paths": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz",
@@ -15359,12 +16005,23 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
+ "uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
+ },
"v8-compile-cache": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true
},
+ "v8-compile-cache-lib": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz",
+ "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==",
+ "dev": true
+ },
"vt-pbf": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz",
@@ -15539,13 +16196,18 @@
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
+ },
+ "yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true
}
}
}
diff --git a/package.json b/package.json
index ee9abc1..34e75a3 100644
--- a/package.json
+++ b/package.json
@@ -6,31 +6,46 @@
"build": "next build",
"build:widgets": "cross-env NODE_ENV=production TS_NODE_PROJECT=\"./src/widgets/tsconfig.json\" webpack --mode=production --config ./src/widgets/webpack.config.widget.js",
"start": "next start",
- "lint": "next lint"
+ "lint": "next lint",
+ "db:migrate": "yarn prisma migrate dev --schema=./src/lib/prisma/schema.prisma",
+ "db:generate": "prisma generate --schema=./src/lib/prisma/schema.prisma && prisma format --schema=./src/lib/prisma/schema.prisma",
+ "db:reset": "prisma migrate reset --schema=./src/lib/prisma/schema.prisma",
+ "db:deploy": "yarn prisma migrate deploy --schema=./src/lib/prisma/schema.prisma",
+ "db:studio": "yarn prisma studio",
+ "db:seed": "yarn prisma db seed",
+ "db:nuke": "docker-compose down --volumes --remove-orphans"
},
"dependencies": {
"@headlessui/react": "^1.5.0",
"@heroicons/react": "^1.0.6",
+ "@next-auth/prisma-adapter": "^1.0.3",
+ "@prisma/client": "^3.12.0",
+ "@radix-ui/react-id": "^0.1.5",
"@reduxjs/toolkit": "^1.7.2",
"@turf/turf": "^6.5.0",
"axios": "^0.26.0",
+ "bcryptjs": "^2.4.3",
"cssnano": "^5.1.7",
"maplibre-gl": "^2.1.6",
"next": "12.1.0",
+ "next-auth": "^4.3.1",
"next-images": "^1.8.4",
"react": "17.0.2",
"react-dom": "17.0.2",
- "react-hook-form": "^7.27.1",
+ "react-hook-form": "^7.29.0",
+ "react-hot-toast": "^2.2.0",
"react-icons": "^4.3.1",
"react-query": "^3.34.16",
"react-redux": "^7.2.6",
"react-scroll": "^1.8.6",
"react-window": "^1.8.6",
"sass": "^1.49.9",
+ "short-uuid": "^4.2.0",
"styled-components": "^5.3.3"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.0",
+ "@types/bcryptjs": "^2.4.2",
"@types/node": "17.0.18",
"@types/react": "17.0.39",
"@types/react-scroll": "^1.8.3",
@@ -45,13 +60,18 @@
"file-loader": "^6.2.0",
"postcss": "^8.4.5",
"postcss-loader": "^6.2.1",
+ "prisma": "^3.12.0",
"sass-loader": "^12.6.0",
"style-loader": "^3.3.1",
"tailwindcss": "^3.0.23",
"ts-loader": "^9.2.6",
+ "ts-node": "^10.7.0",
"tsconfig-paths": "^3.12.0",
"typescript": "4.5.5",
"webpack": "^5.69.1",
"webpack-cli": "^4.9.2"
+ },
+ "prisma": {
+ "seed": "ts-node ./src/lib/prisma/seed.ts"
}
}
diff --git a/src/components/layouts/dashboard/topbar.tsx b/src/components/layouts/dashboard/topbar.tsx
index 78d6332..07b3a8f 100644
--- a/src/components/layouts/dashboard/topbar.tsx
+++ b/src/components/layouts/dashboard/topbar.tsx
@@ -2,6 +2,7 @@ import { Fragment } from "react";
import { Menu, Popover, Transition } from "@headlessui/react";
import { BellIcon, MenuIcon, XIcon } from "@heroicons/react/outline";
import { classNames } from "@lib/classnames";
+import { signOut } from "next-auth/react";
const user = {
name: "Chelsea Hagon",
@@ -18,7 +19,11 @@ const navigationTop = [
const userNavigation = [
{ name: "Your Profile", href: "#" },
{ name: "Settings", href: "#" },
- { name: "Sign out", href: "#" },
+ {
+ name: "Sign out",
+ href: "#",
+ onClick: () => signOut({ callbackUrl: window.location.origin }),
+ },
];
export function TopBar() {
@@ -99,6 +104,9 @@ export function TopBar() {
active ? "bg-gray-100" : "",
"block py-2 px-4 text-sm text-gray-700"
)}
+ onClick={() =>
+ item.onClick ? item.onClick() : null
+ }
>
{item.name}
diff --git a/src/components/loader.tsx b/src/components/loader.tsx
new file mode 100644
index 0000000..29ac50d
--- /dev/null
+++ b/src/components/loader.tsx
@@ -0,0 +1,7 @@
+export default function Loader() {
+ return (
+
+
+
+ );
+}
diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx
new file mode 100644
index 0000000..aea85b5
--- /dev/null
+++ b/src/components/ui/alert.tsx
@@ -0,0 +1,67 @@
+import {
+ CheckCircleIcon,
+ ExclamationIcon,
+ InformationCircleIcon,
+ XCircleIcon,
+} from "@heroicons/react/solid";
+import { classNames } from "@lib/classnames";
+import { ReactNode } from "react";
+
+export interface AlertProps {
+ title?: ReactNode;
+ message?: ReactNode;
+ actions?: ReactNode;
+ className?: string;
+ severity: "success" | "warning" | "error" | "info";
+}
+export function Alert(props: AlertProps) {
+ const { severity } = props;
+
+ return (
+
+
+
+ {severity === "error" && (
+
+ )}
+ {severity === "warning" && (
+
+ )}
+ {severity === "info" && (
+
+ )}
+ {severity === "success" && (
+
+ )}
+
+
+
{props.title}
+
{props.message}
+
+ {props.actions &&
{props.actions}
}
+
+
+ );
+}
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
new file mode 100644
index 0000000..c6f532a
--- /dev/null
+++ b/src/components/ui/button.tsx
@@ -0,0 +1,149 @@
+import { classNames } from "@lib/classnames";
+import Link, { LinkProps } from "next/link";
+import React, { forwardRef } from "react";
+
+type SVGComponent = React.FunctionComponent>;
+
+export type ButtonBaseProps = {
+ color?: "main" | "primary" | "secondary" | "minimal" | "warn";
+ size?: "base" | "sm" | "lg" | "fab" | "icon";
+ loading?: boolean;
+ disabled?: boolean;
+ onClick?: (event: React.MouseEvent) => void;
+ StartIcon?: SVGComponent;
+ EndIcon?: SVGComponent;
+ shallow?: boolean;
+};
+export type ButtonProps = ButtonBaseProps &
+ (
+ | (Omit & { href: LinkProps["href"] })
+ | (JSX.IntrinsicElements["button"] & { href?: never })
+ );
+
+export const Button = forwardRef<
+ HTMLAnchorElement | HTMLButtonElement,
+ ButtonProps
+>(function Button(props: ButtonProps, forwardedRef) {
+ const {
+ loading = false,
+ color = "primary",
+ size = "base",
+ StartIcon,
+ EndIcon,
+ shallow,
+ // attributes propagated from `HTMLAnchorProps` or `HTMLButtonProps`
+ ...passThroughProps
+ } = props;
+ // Buttons are **always** disabled if we're in a `loading` state
+ const disabled = props.disabled || loading;
+
+ // If pass an `href`-attr is passed it's ``, otherwise it's a ` `
+ const isLink = typeof props.href !== "undefined";
+ const elementType = isLink ? "a" : "button";
+
+ const element = React.createElement(
+ elementType,
+ {
+ ...passThroughProps,
+ disabled,
+ ref: forwardedRef,
+ className: classNames(
+ // base styles independent what type of button it is
+ "inline-flex items-center",
+ // different styles depending on size
+ size === "sm" && "px-3 py-2 text-sm leading-4 font-medium rounded-sm",
+ size === "base" && "px-3 py-2 text-sm font-medium rounded-sm",
+ size === "lg" && "px-4 py-2 text-base font-medium rounded-sm",
+ size === "icon" &&
+ "group p-2 border rounded-sm border-transparent text-neutral-400 hover:border-gray-200 transition",
+ // turn button into a floating action button (fab)
+ size === "fab" ? "fixed" : "relative",
+ size === "fab" &&
+ "justify-center bottom-20 right-8 rounded-full p-4 w-14 h-14",
+
+ // different styles depending on color
+ color === "main" &&
+ (disabled
+ ? "text-white bg-gray-400"
+ : "border border-transparent text-white bg-indigo-600 hover:bg-indigo-700 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-neutral-900"),
+ color === "primary" &&
+ (disabled
+ ? "border border-transparent bg-gray-400 text-white"
+ : "border border-transparent dark:text-darkmodebrandcontrast text-brandcontrast bg-brand dark:bg-darkmodebrand hover:bg-opacity-90 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-neutral-900"),
+ color === "secondary" &&
+ (disabled
+ ? "border border-gray-200 text-gray-400 bg-white"
+ : "border border-gray-300 text-gray-700 bg-white hover:bg-gray-50 hover:text-gray-900 hover:shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-neutral-900 dark:bg-transparent dark:text-white dark:border-gray-800 dark:hover:bg-gray-800"),
+ color === "minimal" &&
+ (disabled
+ ? "text-gray-400 bg-transparent"
+ : "text-gray-700 bg-transparent hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-1 focus:bg-gray-100 focus:ring-neutral-500"),
+ color === "warn" &&
+ (disabled
+ ? "text-gray-400 bg-transparent"
+ : "text-gray-700 bg-transparent hover:text-red-700 hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-offset-1 focus:bg-red-50 focus:ring-red-500"),
+ // set not-allowed cursor if disabled
+ loading ? "cursor-wait" : disabled ? "cursor-not-allowed" : "",
+ props.className
+ ),
+ // if we click a disabled button, we prevent going through the click handler
+ onClick: disabled
+ ? (e: React.MouseEvent) => {
+ e.preventDefault();
+ }
+ : props.onClick,
+ },
+ <>
+ {StartIcon && (
+
+ )}
+ {props.children}
+ {loading && (
+
+ )}
+ {EndIcon && (
+
+ )}
+ >
+ );
+ return props.href ? (
+
+ {element}
+
+ ) : (
+ element
+ );
+});
+
+export default Button;
diff --git a/src/components/ui/form/fields.tsx b/src/components/ui/form/fields.tsx
new file mode 100644
index 0000000..d6a7beb
--- /dev/null
+++ b/src/components/ui/form/fields.tsx
@@ -0,0 +1,285 @@
+import { useId } from "@radix-ui/react-id";
+import { forwardRef, ReactElement, ReactNode, Ref } from "react";
+import {
+ FieldValues,
+ FormProvider,
+ SubmitHandler,
+ useFormContext,
+ UseFormReturn,
+} from "react-hook-form";
+
+import { getErrorFromUnknown } from "@lib/errors";
+import showToast from "@lib/notification";
+
+import { Alert } from "../alert";
+import { classNames } from "@lib/classnames";
+
+type InputProps = Omit & {
+ name: string;
+};
+
+export const Input = forwardRef(function Input(
+ props,
+ ref
+) {
+ return (
+
+ );
+});
+
+export function Label(props: JSX.IntrinsicElements["label"]) {
+ return (
+
+ {props.children}
+
+ );
+}
+
+export function InputLeading(props: JSX.IntrinsicElements["div"]) {
+ return (
+
+ {props.children}
+
+ );
+}
+
+type InputFieldProps = {
+ label?: ReactNode;
+ addOnLeading?: ReactNode;
+} & React.ComponentProps & {
+ labelProps?: React.ComponentProps;
+ };
+
+const InputField = forwardRef(
+ function InputField(props, ref) {
+ const id = useId();
+ const methods = useFormContext();
+ const {
+ label = props.name,
+ labelProps,
+ placeholder = props.name + "_placeholder" !== props.name + "_placeholder"
+ ? props.name + "_placeholder"
+ : "",
+ className,
+ addOnLeading,
+ ...passThrough
+ } = props;
+ return (
+
+ {!!props.name && (
+
+ {label}
+
+ )}
+ {addOnLeading ? (
+
+ {addOnLeading}
+
+
+ ) : (
+
+ )}
+ {methods?.formState?.errors[props.name] && (
+
+ )}
+
+ );
+ }
+);
+
+export const TextField = forwardRef(
+ function TextField(props, ref) {
+ return ;
+ }
+);
+
+export const PasswordField = forwardRef(
+ function PasswordField(props, ref) {
+ return ;
+ }
+);
+
+export const EmailInput = forwardRef(
+ function EmailInput(props, ref) {
+ return (
+
+ );
+ }
+);
+
+export const EmailField = forwardRef(
+ function EmailField(props, ref) {
+ return (
+
+ );
+ }
+);
+
+type TextAreaProps = Omit & {
+ name: string;
+};
+
+export const TextArea = forwardRef(
+ function TextAreaInput(props, ref) {
+ return (
+
+ );
+ }
+);
+
+type TextAreaFieldProps = {
+ label?: ReactNode;
+} & React.ComponentProps & {
+ labelProps?: React.ComponentProps;
+ };
+
+export const TextAreaField = forwardRef<
+ HTMLTextAreaElement,
+ TextAreaFieldProps
+>(function TextField(props, ref) {
+ const id = useId();
+ const methods = useFormContext();
+ const {
+ label = props.name as string,
+ labelProps,
+ placeholder = props.name + "_placeholder" !== props.name + "_placeholder"
+ ? props.name + "_placeholder"
+ : "",
+ ...passThrough
+ } = props;
+ return (
+
+ {!!props.name && (
+
+ {label}
+
+ )}
+
+ {methods?.formState?.errors[props.name] && (
+
+ )}
+
+ );
+});
+
+type FormProps = {
+ form: UseFormReturn;
+ handleSubmit: SubmitHandler;
+} & Omit;
+
+const PlainForm = (
+ props: FormProps,
+ ref: Ref
+) => {
+ const { form, handleSubmit, ...passThrough } = props;
+
+ return (
+
+
+
+ );
+};
+
+export const Form = forwardRef(PlainForm) as (
+ p: FormProps & { ref?: Ref }
+) => ReactElement;
+
+export function FieldsetLegend(props: JSX.IntrinsicElements["legend"]) {
+ return (
+
+ {props.children}
+
+ );
+}
+
+export function InputGroupBox(props: JSX.IntrinsicElements["div"]) {
+ return (
+
+ {props.children}
+
+ );
+}
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
new file mode 100644
index 0000000..ea1bbbb
--- /dev/null
+++ b/src/lib/auth.ts
@@ -0,0 +1,40 @@
+import { IdentityProvider } from "@prisma/client";
+import { compare, hash } from "bcryptjs";
+import { Session } from "next-auth";
+import {
+ getSession as getSessionInner,
+ GetSessionParams,
+} from "next-auth/react";
+
+export async function hashPassword(password: string) {
+ const hashedPassword = await hash(password, 12);
+ return hashedPassword;
+}
+
+export async function verifyPassword(password: string, hashedPassword: string) {
+ const isValid = await compare(password, hashedPassword);
+ return isValid;
+}
+
+export async function getSession(
+ options: GetSessionParams
+): Promise {
+ const session = await getSessionInner(options);
+
+ // that these are equal are ensured in `[...nextauth]`'s callback
+ return session as Session | null;
+}
+
+export enum ErrorCode {
+ UserNotFound = "user-not-found",
+ IncorrectPassword = "incorrect-password",
+ UserMissingPassword = "missing-password",
+ InternalServerError = "internal-server-error",
+ NewPasswordMatchesOld = "new-password-matches-old",
+ ThirdPartyIdentityProviderEnabled = "third-party-identity-provider-enabled",
+}
+
+export const identityProviderNameMap: { [key in IdentityProvider]: string } = {
+ [IdentityProvider.STORE_EYE]: "StoreEye",
+ [IdentityProvider.GOOGLE]: "Google",
+};
diff --git a/src/lib/errors.ts b/src/lib/errors.ts
new file mode 100644
index 0000000..78d3500
--- /dev/null
+++ b/src/lib/errors.ts
@@ -0,0 +1,32 @@
+import { Prisma } from "@prisma/client";
+
+export function getErrorFromUnknown(cause: unknown): Error & { statusCode?: number; code?: string } {
+ if (cause instanceof Prisma.PrismaClientKnownRequestError) {
+ return cause;
+ }
+ if (cause instanceof Error) {
+ return cause;
+ }
+ if (typeof cause === "string") {
+ // @ts-expect-error https://github.com/tc39/proposal-error-cause
+ return new Error(cause, { cause });
+ }
+
+ return new Error(`Unhandled error of type '${typeof cause}''`);
+}
+
+export function handleErrorsJson(response: Response) {
+ if (!response.ok) {
+ response.json().then(console.log);
+ throw Error(response.statusText);
+ }
+ return response.json();
+}
+
+export function handleErrorsRaw(response: Response) {
+ if (!response.ok) {
+ response.text().then(console.log);
+ throw Error(response.statusText);
+ }
+ return response.text();
+}
diff --git a/src/lib/notification.ts b/src/lib/notification.ts
new file mode 100644
index 0000000..548ef70
--- /dev/null
+++ b/src/lib/notification.ts
@@ -0,0 +1,52 @@
+import toast from "react-hot-toast";
+
+export default function showToast(
+ message: string,
+ variant: "success" | "warning" | "error"
+) {
+ switch (variant) {
+ case "success":
+ toast.success(message, {
+ duration: 6000,
+ style: {
+ borderRadius: "2px",
+ background: "#333",
+ color: "#fff",
+ boxShadow: "none",
+ },
+ });
+ break;
+ case "error":
+ toast.error(message, {
+ duration: 6000,
+ style: {
+ borderRadius: "2px",
+ background: "#FEE2E2",
+ color: "#B91C1C",
+ boxShadow: "none",
+ },
+ });
+ break;
+ case "warning":
+ toast(message, {
+ duration: 6000,
+ style: {
+ borderRadius: "2px",
+ background: "#FFEDD5",
+ color: "#C2410C",
+ boxShadow: "none",
+ },
+ });
+ break;
+ default:
+ toast.success(message, {
+ duration: 6000,
+ style: {
+ borderRadius: "2px",
+ background: "#333",
+ color: "#fff",
+ boxShadow: "none",
+ },
+ });
+ }
+}
diff --git a/src/lib/prisma/client/index.d.ts b/src/lib/prisma/client/index.d.ts
new file mode 100644
index 0000000..554f9f4
--- /dev/null
+++ b/src/lib/prisma/client/index.d.ts
@@ -0,0 +1 @@
+export * from ".prisma/client/index.d";
diff --git a/src/lib/prisma/client/runtime.d.ts b/src/lib/prisma/client/runtime.d.ts
new file mode 100644
index 0000000..f85afae
--- /dev/null
+++ b/src/lib/prisma/client/runtime.d.ts
@@ -0,0 +1 @@
+export * from "@prisma/client/runtime";
diff --git a/src/lib/prisma/fixtures/amrest.ts b/src/lib/prisma/fixtures/amrest.ts
new file mode 100644
index 0000000..e0d2abd
--- /dev/null
+++ b/src/lib/prisma/fixtures/amrest.ts
@@ -0,0 +1,64 @@
+import { ApplicationStatus, UserPlan } from "@prisma/client";
+
+const organization = {
+ name: "AmRest",
+ slug: "amrest",
+};
+
+const brands = [
+ {
+ name: "KFC",
+ fullName: "KFC",
+ },
+ {
+ name: "PH",
+ fullName: "Pizza Hut",
+ },
+];
+
+const countries = [
+ {
+ name: "Poland",
+ code: "PL",
+ },
+ {
+ name: "Czech",
+ code: "CZ",
+ },
+];
+
+const users = [
+ {
+ email: "tazo@gmail.com",
+ password: "pass",
+ username: "tazo90",
+ name: "ala",
+ plan: UserPlan.FREE,
+ },
+ {
+ email: "team2pro@example.com",
+ password: "teampro",
+ username: "teampro",
+ name: "Team Pro Example",
+ plan: UserPlan.PRO,
+ },
+];
+
+const apps = [
+ {
+ uid: "123",
+ title: "Stores KFC PL",
+ token: "abcd",
+ status: ApplicationStatus.DRAFT,
+ paid: false,
+ domain: "localhost",
+ },
+];
+
+export default {
+ organization,
+ brands,
+ countries,
+ users,
+ apps,
+};
diff --git a/src/lib/prisma/index.ts b/src/lib/prisma/index.ts
new file mode 100644
index 0000000..7dda059
--- /dev/null
+++ b/src/lib/prisma/index.ts
@@ -0,0 +1,19 @@
+import { PrismaClient } from "@prisma/client";
+
+declare global {
+ // allow global `var` declarations
+ // eslint-disable-next-line no-var
+ var prisma: PrismaClient | undefined;
+}
+
+const prisma =
+ global.prisma ||
+ new PrismaClient({
+ log: ["query"],
+ });
+
+if (process.env.NODE_ENV !== "production") {
+ global.prisma = prisma;
+}
+
+export default prisma;
diff --git a/src/lib/prisma/migrations/20220413211308_initial/migration.sql b/src/lib/prisma/migrations/20220413211308_initial/migration.sql
new file mode 100644
index 0000000..38471bf
--- /dev/null
+++ b/src/lib/prisma/migrations/20220413211308_initial/migration.sql
@@ -0,0 +1,251 @@
+-- CreateEnum
+CREATE TYPE "UserPlan" AS ENUM ('FREE', 'TRIAL', 'PRO');
+
+-- CreateEnum
+CREATE TYPE "IdentityProvider" AS ENUM ('STORE_EYE', 'GOOGLE');
+
+-- CreateEnum
+CREATE TYPE "MembershipRole" AS ENUM ('MEMBER', 'ADMIN', 'OWNER');
+
+-- CreateEnum
+CREATE TYPE "PaymentType" AS ENUM ('STRIPE');
+
+-- CreateEnum
+CREATE TYPE "ApplicationStatus" AS ENUM ('active', 'draft', 'closed');
+
+-- CreateTable
+CREATE TABLE "Credential" (
+ "id" SERIAL NOT NULL,
+ "type" TEXT NOT NULL,
+ "key" JSONB NOT NULL,
+ "userId" INTEGER,
+
+ CONSTRAINT "Credential_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "users" (
+ "id" SERIAL NOT NULL,
+ "username" TEXT,
+ "name" TEXT,
+ "email" TEXT NOT NULL,
+ "emailVerified" TIMESTAMP(3),
+ "password" TEXT,
+ "avatar" TEXT,
+ "created" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "identityProvider" "IdentityProvider" NOT NULL DEFAULT E'STORE_EYE',
+ "identityProviderId" TEXT,
+ "plan" "UserPlan" NOT NULL DEFAULT E'TRIAL',
+ "verified" BOOLEAN DEFAULT false,
+
+ CONSTRAINT "users_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "Organization" (
+ "id" SERIAL NOT NULL,
+ "name" TEXT NOT NULL,
+ "slug" TEXT,
+ "logo" TEXT,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" TIMESTAMP(3) NOT NULL,
+
+ CONSTRAINT "Organization_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "Brand" (
+ "id" SERIAL NOT NULL,
+ "name" TEXT NOT NULL,
+ "full_name" TEXT NOT NULL,
+ "organizationId" INTEGER NOT NULL,
+ "applicationId" INTEGER,
+
+ CONSTRAINT "Brand_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "Country" (
+ "id" SERIAL NOT NULL,
+ "name" TEXT NOT NULL,
+ "code" TEXT NOT NULL,
+ "applicationId" INTEGER,
+
+ CONSTRAINT "Country_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "Membership" (
+ "role" "MembershipRole" NOT NULL,
+ "accepted" BOOLEAN NOT NULL DEFAULT false,
+ "organizationId" INTEGER NOT NULL,
+ "userId" INTEGER NOT NULL,
+
+ CONSTRAINT "Membership_pkey" PRIMARY KEY ("userId","organizationId")
+);
+
+-- CreateTable
+CREATE TABLE "VerificationRequest" (
+ "id" SERIAL NOT NULL,
+ "identifier" TEXT NOT NULL,
+ "token" TEXT NOT NULL,
+ "expires" TIMESTAMP(3) NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" TIMESTAMP(3) NOT NULL,
+
+ CONSTRAINT "VerificationRequest_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "ResetPasswordRequest" (
+ "id" TEXT NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" TIMESTAMP(3) NOT NULL,
+ "email" TEXT NOT NULL,
+ "expires" TIMESTAMP(3) NOT NULL,
+
+ CONSTRAINT "ResetPasswordRequest_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "Payment" (
+ "id" SERIAL NOT NULL,
+ "uid" TEXT NOT NULL,
+ "type" "PaymentType" NOT NULL,
+ "applicationId" INTEGER NOT NULL,
+ "amount" INTEGER NOT NULL,
+ "fee" INTEGER NOT NULL,
+ "currency" TEXT NOT NULL,
+ "success" BOOLEAN NOT NULL,
+ "refunded" BOOLEAN NOT NULL,
+ "data" JSONB NOT NULL,
+ "externalId" TEXT NOT NULL,
+
+ CONSTRAINT "Payment_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "Application" (
+ "id" SERIAL NOT NULL,
+ "uid" TEXT NOT NULL,
+ "userId" INTEGER NOT NULL,
+ "organizationId" INTEGER NOT NULL,
+ "title" TEXT NOT NULL,
+ "description" TEXT,
+ "token" TEXT NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" TIMESTAMP(3),
+ "status" "ApplicationStatus" NOT NULL DEFAULT E'draft',
+ "paid" BOOLEAN NOT NULL,
+ "domain" TEXT NOT NULL,
+
+ CONSTRAINT "Application_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "Component" (
+ "id" SERIAL NOT NULL,
+ "name" TEXT NOT NULL,
+ "slug" TEXT NOT NULL,
+ "description" TEXT,
+ "applicationId" INTEGER NOT NULL,
+ "clientId" TEXT,
+ "secretId" TEXT,
+ "tokenUrl" TEXT,
+ "dataUrl" TEXT,
+
+ CONSTRAINT "Component_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "_CountryToOrganization" (
+ "A" INTEGER NOT NULL,
+ "B" INTEGER NOT NULL
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "users_username_key" ON "users"("username");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Organization_name_key" ON "Organization"("name");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Organization_slug_key" ON "Organization"("slug");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Brand_name_key" ON "Brand"("name");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Brand_full_name_key" ON "Brand"("full_name");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Brand_name_organizationId_key" ON "Brand"("name", "organizationId");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Country_name_key" ON "Country"("name");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Country_code_key" ON "Country"("code");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Country_name_code_key" ON "Country"("name", "code");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "VerificationRequest_token_key" ON "VerificationRequest"("token");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "VerificationRequest_identifier_token_key" ON "VerificationRequest"("identifier", "token");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Payment_uid_key" ON "Payment"("uid");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Payment_externalId_key" ON "Payment"("externalId");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Application_uid_key" ON "Application"("uid");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "_CountryToOrganization_AB_unique" ON "_CountryToOrganization"("A", "B");
+
+-- CreateIndex
+CREATE INDEX "_CountryToOrganization_B_index" ON "_CountryToOrganization"("B");
+
+-- AddForeignKey
+ALTER TABLE "Credential" ADD CONSTRAINT "Credential_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "Brand" ADD CONSTRAINT "Brand_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "Brand" ADD CONSTRAINT "Brand_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application"("id") ON DELETE SET NULL ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "Country" ADD CONSTRAINT "Country_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application"("id") ON DELETE SET NULL ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "Membership" ADD CONSTRAINT "Membership_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "Membership" ADD CONSTRAINT "Membership_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "Payment" ADD CONSTRAINT "Payment_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "Application" ADD CONSTRAINT "Application_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "Application" ADD CONSTRAINT "Application_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "Component" ADD CONSTRAINT "Component_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "_CountryToOrganization" ADD FOREIGN KEY ("A") REFERENCES "Country"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "_CountryToOrganization" ADD FOREIGN KEY ("B") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/src/lib/prisma/migrations/migration_lock.toml b/src/lib/prisma/migrations/migration_lock.toml
new file mode 100644
index 0000000..fbffa92
--- /dev/null
+++ b/src/lib/prisma/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (i.e. Git)
+provider = "postgresql"
\ No newline at end of file
diff --git a/src/lib/prisma/schema.prisma b/src/lib/prisma/schema.prisma
new file mode 100644
index 0000000..91a7e4f
--- /dev/null
+++ b/src/lib/prisma/schema.prisma
@@ -0,0 +1,297 @@
+// This is your Prisma schema file,
+// learn more about it in the docs: https://pris.ly/d/prisma-schema
+
+datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+}
+
+generator client {
+ provider = "prisma-client-js"
+ previewFeatures = ["filterJson"]
+}
+
+enum UserPlan {
+ FREE
+ TRIAL
+ PRO
+}
+
+enum IdentityProvider {
+ STORE_EYE
+ GOOGLE
+}
+
+model Credential {
+ id Int @id @default(autoincrement())
+ type String
+ key Json
+ user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
+ userId Int?
+
+ @@map(name: "credential")
+}
+
+model User {
+ id Int @id @default(autoincrement())
+ username String? @unique
+ name String?
+ email String @unique
+ emailVerified DateTime?
+ password String?
+ avatar String?
+ createdDate DateTime @default(now()) @map(name: "created")
+ credentials Credential[]
+ teams Membership[] // organizations
+ applications Application[]
+ identityProvider IdentityProvider @default(STORE_EYE)
+ identityProviderId String?
+ plan UserPlan @default(TRIAL)
+ verified Boolean? @default(false)
+
+ @@map(name: "users")
+}
+
+model Organization {
+ id Int @id @default(autoincrement())
+ name String @unique
+ slug String? @unique
+ logo String?
+ members Membership[]
+ applications Application[]
+ brands Brand[]
+ countries Country[]
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ jobs Job[]
+ stores Store[]
+
+ @@map(name: "organization")
+}
+
+model Country {
+ id Int @id @default(autoincrement())
+ name String @unique
+ code String @unique
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
+ organizationId Int
+
+ applications Application[]
+ job Job[]
+ store Store[]
+
+ @@unique([name, code])
+ @@map(name: "country")
+}
+
+model Brand {
+ id Int @id @default(autoincrement())
+ name String @unique
+ fullName String @unique
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
+ organizationId Int
+
+ applications Application[]
+ job Job[]
+ store Store[]
+
+ @@unique([name, organizationId])
+ @@map(name: "brand")
+}
+
+enum MembershipRole {
+ MEMBER
+ ADMIN
+ OWNER
+}
+
+model Membership {
+ role MembershipRole
+ accepted Boolean @default(false)
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
+ organizationId Int
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+ userId Int
+
+ @@id([userId, organizationId])
+ @@map(name: "membership")
+}
+
+model VerificationRequest {
+ id Int @id @default(autoincrement())
+ identifier String
+ token String @unique
+ expires DateTime
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+
+ @@unique([identifier, token])
+ @@map(name: "verification_request")
+}
+
+model ResetPasswordRequest {
+ id String @id @default(cuid())
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ email String
+ expires DateTime
+
+ @@map(name: "reset_password_request")
+}
+
+enum PaymentType {
+ STRIPE
+}
+
+model Payment {
+ id Int @id @default(autoincrement())
+ uid String @unique
+ type PaymentType
+ applicationId Int
+ application Application? @relation(fields: [applicationId], references: [id], onDelete: Cascade)
+ amount Int
+ fee Int
+ currency String
+ success Boolean
+ refunded Boolean
+ data Json
+ externalId String @unique
+
+ @@map(name: "payment")
+}
+
+enum ApplicationStatus {
+ ACTIVE @map("active")
+ DRAFT @map("draft")
+ CLOSED @map("closed")
+}
+
+model Application {
+ id Int @id @default(autoincrement())
+ uid String @unique
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+ userId Int
+ organizationId Int
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
+ title String
+ description String?
+ brands Brand[]
+ countries Country[]
+ token String
+ createdAt DateTime @default(now())
+ updatedAt DateTime?
+ status ApplicationStatus @default(DRAFT)
+ paid Boolean
+ payment Payment[]
+ expires DateTime
+ domain String
+ components Component[]
+
+ // TODO: Allow to define colors per application
+ @@map(name: "application")
+}
+
+model Component {
+ id Int @id @default(autoincrement())
+ name String
+ slug String
+ description String?
+ application Application @relation(fields: [applicationId], references: [id], onDelete: Cascade)
+ applicationId Int
+ clientId String?
+ secretId String?
+ tokenUrl String?
+ dataUrl String?
+
+ @@map(name: "component")
+}
+
+model Job {
+ id Int @id @default(autoincrement())
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
+ organizationId Int
+
+ brand Brand @relation(fields: [brandId], references: [id], onDelete: Cascade)
+ brandId Int
+
+ country Country @relation(fields: [countryId], references: [id], onDelete: Cascade)
+ countryId Int
+
+ profession_name String?
+ job_category_name String?
+
+ title String
+ banner_url String?
+ body String?
+ footer String?
+
+ apply_url String?
+ is_manager Boolean?
+ language String?
+ externalId String
+
+ @@map(name: "job")
+}
+
+enum StoreType {
+ RESTAURANT
+ FASTFOOD
+}
+
+enum StoreStatus {
+ CLOSED
+ OPEN
+}
+
+model Store {
+ id Int @id @default(autoincrement())
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
+ organizationId Int
+
+ brand Brand @relation(fields: [brandId], references: [id], onDelete: Cascade)
+ brandId Int
+
+ country Country @relation(fields: [countryId], references: [id], onDelete: Cascade)
+ countryId Int
+
+ osm_id String?
+ store_id String?
+ cost_center String?
+ type StoreType @default(FASTFOOD)
+ status StoreStatus @default(OPEN)
+
+ lat String
+ lng String
+ name String
+ name_osm String?
+
+ address_osm String?
+ address_google String?
+ address String?
+
+ located_in_name String?
+ located_in_url String?
+
+ phone String?
+
+ image_place_url String?
+ image_street_view_url String?
+ manu_url String?
+ map_url String?
+ order_url String?
+ street_view_url String?
+ reviews_url String?
+
+ payment_types String[]
+ photos String[]
+ orders Json
+ features String[]
+ services Json
+ franchisee_details Json
+ opening_hours Json
+ ownership String
+
+ @@map(name: "store")
+}
diff --git a/src/lib/prisma/seed.ts b/src/lib/prisma/seed.ts
new file mode 100644
index 0000000..45d7ef9
--- /dev/null
+++ b/src/lib/prisma/seed.ts
@@ -0,0 +1,177 @@
+import { PrismaClient, Prisma, MembershipRole, UserPlan } from "@prisma/client";
+
+import { hashPassword } from "../auth";
+import data from "./fixtures/amrest";
+
+require("dotenv");
+
+const prisma = new PrismaClient();
+
+async function dropTables() {
+ await prisma.brand.deleteMany();
+ await prisma.user.deleteMany();
+ await prisma.membership.deleteMany();
+ await prisma.organization.deleteMany();
+
+ // await prisma.$queryRaw`ALTER TABLE brand AUTO_INCREMENT = 1`;
+ // await prisma.$queryRaw`ALTER TABLE user AUTO_INCREMENT = 1`;
+ // await prisma.$queryRaw`ALTER TABLE membership AUTO_INCREMENT = 1`;
+ // await prisma.$queryRaw`ALTER TABLE organization AUTO_INCREMENT = 1`;
+
+ console.log("Dropped tables");
+}
+
+async function createUser(opts: {
+ user: {
+ email: string;
+ password: string;
+ username: string;
+ plan: UserPlan;
+ name: string;
+ };
+}) {
+ const userData = {
+ ...opts.user,
+ password: await hashPassword(opts.user.password),
+ emailVerified: new Date(),
+ };
+
+ const user = await prisma.user.upsert({
+ where: { email: opts.user.email },
+ update: userData,
+ create: userData,
+ });
+
+ console.log(
+ `👤 Upserted '${user.username}' with email "${user.email}" & password "${user.password}".`
+ );
+
+ return user;
+}
+
+async function createOrganizationAndUsers(
+ organizationInput: Prisma.OrganizationCreateInput,
+ users: { id: number; username: string; role?: MembershipRole }[]
+) {
+ const createOrganization = async (
+ organization: Prisma.OrganizationCreateInput
+ ) => {
+ return await prisma.organization.create({
+ data: {
+ ...organization,
+ },
+ });
+ };
+
+ const organization = await createOrganization(organizationInput);
+ if (!organization) {
+ return;
+ }
+
+ console.log(`🏢 Created organization '${organizationInput.name}'`);
+
+ for (const user of users) {
+ const { role = MembershipRole.OWNER, id, username } = user;
+ await prisma.membership.create({
+ data: {
+ organizationId: organization.id,
+ userId: id,
+ role: role,
+ accepted: true,
+ },
+ });
+ console.log(
+ `\t👤 Added '${organizationInput.name}' membership for '${username}' with role '${role}'`
+ );
+ }
+
+ return organization;
+}
+
+async function createBrands(
+ organization: { id: number; name: string },
+ brands: { name: string; fullName: string }[]
+) {
+ brands.map(async (brand) => {
+ await prisma.brand.create({
+ data: {
+ ...brand,
+ organizationId: organization.id,
+ },
+ });
+
+ console.log(`\t👤 Added brand '${brand.name}' to '${organization.name}'`);
+ });
+}
+
+async function createCountries(
+ organization: { id: number; name: string },
+ countries: { name: string; code: string }[]
+) {
+ countries.map(async (country) => {
+ await prisma.country.create({
+ data: {
+ ...country,
+ organizationId: organization.id,
+ },
+ });
+
+ console.log(
+ `\t👤 Added country '${country.name}' to '${organization.name}'`
+ );
+ });
+}
+
+async function createApps(organization: any, user: any, apps: any) {
+ apps.map(async (app: any) => {
+ await prisma.application.create({
+ data: {
+ ...app,
+ user: { connect: { id: user.id } },
+ organization: { connect: { id: organization.id } },
+ expires: new Date(
+ "Tue Sep 21 2022 16:16:50 GMT-0400 (Eastern Daylight Time)"
+ ),
+ },
+ });
+ });
+}
+
+async function main() {
+ await dropTables();
+
+ const freeUser = await createUser({
+ user: data.users[0],
+ });
+
+ const proUser = await createUser({
+ user: data.users[1],
+ });
+
+ const org = await createOrganizationAndUsers(data.organization, [
+ {
+ id: freeUser.id,
+ username: freeUser.name || "Unknown",
+ },
+ {
+ id: proUser.id,
+ username: proUser.name || "Unknown",
+ },
+ ]);
+
+ if (org) {
+ await createBrands(org, data.brands);
+ await createCountries(org, data.countries);
+
+ await createApps(org, proUser, data.apps);
+ }
+}
+
+main()
+ .catch((e) => {
+ console.error(e);
+ process.exit(1);
+ })
+ .finally(async () => {
+ await prisma.$disconnect();
+ });
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index 4ccba58..218781a 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -2,6 +2,7 @@ import type { AppProps } from "next/app";
import { useRouter } from "next/router";
import { useRef } from "react";
import { Provider } from "react-redux";
+import { SessionProvider } from "next-auth/react";
import { Hydrate } from "react-query/hydration";
import { QueryClient, QueryClientProvider } from "react-query";
@@ -10,9 +11,11 @@ import store from "../store";
// Import css
import "@styles/index.scss";
-function App({ Component, pageProps }: AppProps) {
+const Noop = ({ children }) => <>{children}>;
+
+function App({ Component, pageProps: { session, ...pageProps } }: AppProps) {
const router = useRouter();
- const Layout = (Component as any).Layout;
+ const Layout = (Component as any).Layout || Noop;
const queryClientRef = useRef();
if (!queryClientRef.current) {
@@ -22,11 +25,14 @@ function App({ Component, pageProps }: AppProps) {
return (
-
-
-
-
-
+
+
+ {/* TODO: layout causes weird error, show console on login page */}
+
+
+
+
+
);
diff --git a/src/pages/api/auth/[...nextauth].tsx b/src/pages/api/auth/[...nextauth].tsx
new file mode 100644
index 0000000..ecab5a2
--- /dev/null
+++ b/src/pages/api/auth/[...nextauth].tsx
@@ -0,0 +1,127 @@
+import { IdentityProvider } from "@prisma/client";
+import NextAuth, { Session } from "next-auth";
+import { Provider } from "next-auth/providers";
+import CredentialsProvider from "next-auth/providers/credentials";
+
+import { ErrorCode, verifyPassword } from "@lib/auth";
+import prisma from "@lib/prisma";
+
+const providers: Provider[] = [
+ CredentialsProvider({
+ id: "credentials",
+ name: "storeeye",
+ type: "credentials",
+ credentials: {
+ email: {
+ label: "Email Address",
+ type: "email",
+ placeholder: "john.doe@example.com",
+ },
+ password: {
+ label: "Password",
+ type: "password",
+ placeholder: "Your super secure password",
+ },
+ },
+ async authorize(credentials) {
+ if (!credentials) {
+ console.error("For some reason credentials are missing");
+ throw new Error(ErrorCode.InternalServerError);
+ }
+
+ console.log("USER", credentials.email);
+
+ const user = await prisma.user.findUnique({
+ where: {
+ email: credentials.email.toLowerCase(),
+ },
+ });
+
+ if (!user) {
+ throw new Error(ErrorCode.UserNotFound);
+ }
+
+ if (user.identityProvider !== IdentityProvider.STORE_EYE) {
+ throw new Error(ErrorCode.ThirdPartyIdentityProviderEnabled);
+ }
+
+ if (!user.password) {
+ throw new Error(ErrorCode.UserMissingPassword);
+ }
+
+ const isCorrectPassword = await verifyPassword(
+ credentials.password,
+ user.password
+ );
+ if (!isCorrectPassword) {
+ throw new Error(ErrorCode.IncorrectPassword);
+ }
+
+ return {
+ id: user.id,
+ username: user.username,
+ email: user.email,
+ name: user.name,
+ };
+ },
+ }),
+];
+
+export default NextAuth({
+ session: {
+ strategy: "jwt",
+ },
+ secret: process.env.JWT_SECRET,
+ pages: {
+ signIn: "/login",
+ error: "/login", // Error code passed in query string as ?error=
+ },
+ providers,
+ callbacks: {
+ async jwt({ token, user, account }) {
+ const autoMergeIdentities = async () => {
+ const existingUser = await prisma.user.findFirst({
+ where: { email: token.email! },
+ });
+
+ if (!existingUser) {
+ return token;
+ }
+
+ return {
+ id: existingUser.id,
+ username: existingUser.username,
+ name: existingUser.name,
+ email: existingUser.email,
+ };
+ };
+
+ if (!user) {
+ return await autoMergeIdentities();
+ }
+
+ if (account && account.type === "credentials") {
+ return {
+ id: user.id,
+ name: user.name,
+ username: user.username,
+ email: user.email,
+ };
+ }
+
+ return token;
+ },
+ async session({ session, token }) {
+ const appSession: Session = {
+ ...session,
+ user: {
+ ...session.user,
+ id: token.id as number,
+ name: token.name,
+ username: token.username as string,
+ },
+ };
+ return appSession;
+ },
+ },
+});
diff --git a/src/pages/api/hello.ts b/src/pages/api/hello.ts
deleted file mode 100644
index f8bcc7e..0000000
--- a/src/pages/api/hello.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
-import type { NextApiRequest, NextApiResponse } from 'next'
-
-type Data = {
- name: string
-}
-
-export default function handler(
- req: NextApiRequest,
- res: NextApiResponse
-) {
- res.status(200).json({ name: 'John Doe' })
-}
diff --git a/src/pages/apps/jobs/index.tsx b/src/pages/apps/jobs/index.tsx
index cc1847e..4ab09d1 100644
--- a/src/pages/apps/jobs/index.tsx
+++ b/src/pages/apps/jobs/index.tsx
@@ -2,22 +2,22 @@ import { useEffect, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { MenuIcon, MapIcon, ShoppingBagIcon } from "@heroicons/react/outline";
-import DashboardLayout from "../../../components/layouts/dashboard";
-import Map from "../../../components/map";
-import { Search } from "../../../components/common/search";
-import Listbox from "../../../components/listbox/listbox";
+import DashboardLayout from "@components/layouts/dashboard";
+import Map from "@components/map";
+import { Search } from "@components/common/search";
+import Listbox from "@components/listbox/listbox";
import {
BrandFilter,
CountryFilter,
MoreFilter,
-} from "../../../components/stores/filters";
-import { setStore, setStores } from "../../../slices/store.slice";
-import { useJobsQuery } from "../../../api/jobs/get-all-jobs";
-import Drawer from "../../../components/ui/drawer";
-import Autocomplete from "../../../components/ui/autocomplete";
-import { setUserLocation } from "../../../slices/location.slice";
-import { ListboxJob } from "../../../components/jobs/listbox-job/listbox-job";
-import { JobDetail } from "../../../components/jobs/job-detail";
+} from "@components/stores/filters";
+import { setStore, setStores } from "@slices/store.slice";
+import { useJobsQuery } from "@api/jobs/get-all-jobs";
+import Drawer from "@components/ui/drawer";
+import Autocomplete from "@components/ui/autocomplete";
+import { setUserLocation } from "@slices/location.slice";
+import { ListboxJob } from "@components/jobs/listbox-job/listbox-job";
+import { JobDetail } from "@components/jobs/job-detail";
export default function JobsPage() {
const dispatch = useDispatch();
diff --git a/src/pages/apps/stores/index.tsx b/src/pages/apps/stores/index.tsx
index 31689cd..fb8bed0 100644
--- a/src/pages/apps/stores/index.tsx
+++ b/src/pages/apps/stores/index.tsx
@@ -3,20 +3,20 @@ import { useDispatch, useSelector } from "react-redux";
import { MenuIcon, MapIcon, ShoppingBagIcon } from "@heroicons/react/outline";
import DashboardLayout from "@components/layouts/dashboard";
-import Map from "../../../components/map";
-import { Search } from "../../../components/common/search";
-import Listbox from "../../../components/listbox/listbox";
+import Map from "@components/map";
+import { Search } from "@components/common/search";
+import Listbox from "@components/listbox/listbox";
import {
BrandFilter,
CountryFilter,
MoreFilter,
-} from "../../../components/stores/filters";
-import { setStore, setStores } from "../../../slices/store.slice";
-import { useStoresQuery } from "../../../api/stores/get-all-stores";
-import Drawer from "../../../components/ui/drawer";
-import Autocomplete from "../../../components/ui/autocomplete";
-import { setUserLocation } from "../../../slices/location.slice";
-import { StoreDetail } from "../../../components/stores/store-detail";
+} from "@components/stores/filters";
+import { setStore, setStores } from "@slices/store.slice";
+import { useStoresQuery } from "@api/stores/get-all-stores";
+import Drawer from "@components/ui/drawer";
+import Autocomplete from "@components/ui/autocomplete";
+import { setUserLocation } from "@slices/location.slice";
+import { StoreDetail } from "@components/stores/store-detail";
export default function StoresPage() {
const dispatch = useDispatch();
diff --git a/src/pages/forgot-password.tsx b/src/pages/forgot-password.tsx
index 2565830..975ee91 100644
--- a/src/pages/forgot-password.tsx
+++ b/src/pages/forgot-password.tsx
@@ -1,3 +1,4 @@
+import Link from "next/link";
import { useRouter } from "next/router";
import BaseLayout from "../components/layouts/base";
@@ -6,7 +7,7 @@ export default function ForgotPasswordPage() {
return (
<>
-
+
Return to{" "}
- router.push("/login")}
- className="font-medium text-indigo-600 hover:text-indigo-500"
- >
- sign in
-
+
+
+ sign in
+
+
diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx
new file mode 100644
index 0000000..3ea0be3
--- /dev/null
+++ b/src/pages/home/index.tsx
@@ -0,0 +1,859 @@
+import BaseLayout from "@components/layouts/base";
+
+import { Fragment } from "react";
+import { Popover, Transition } from "@headlessui/react";
+import {
+ CloudUploadIcon,
+ CogIcon,
+ LockClosedIcon,
+ MenuIcon,
+ RefreshIcon,
+ ServerIcon,
+ ShieldCheckIcon,
+ XIcon,
+} from "@heroicons/react/outline";
+import { ChevronRightIcon, ExternalLinkIcon } from "@heroicons/react/solid";
+
+const navigation = [
+ { name: "Products", href: "#" },
+ { name: "Use cases", href: "#" },
+ { name: "Developers", href: "#" },
+ { name: "Pricing", href: "#" },
+];
+const features = [
+ {
+ name: "Push to Deploy",
+ description:
+ "Ac tincidunt sapien vehicula erat auctor pellentesque rhoncus. Et magna sit morbi vitae lobortis.",
+ icon: CloudUploadIcon,
+ },
+ {
+ name: "SSL Certificates",
+ description:
+ "Qui aut temporibus nesciunt vitae dicta repellat sit dolores pariatur. Temporibus qui illum aut.",
+ icon: LockClosedIcon,
+ },
+ {
+ name: "Simple Queues",
+ description:
+ "Rerum quas incidunt deleniti quaerat suscipit mollitia. Amet repellendus ut odit dolores qui.",
+ icon: RefreshIcon,
+ },
+ {
+ name: "Advanced Security",
+ description:
+ "Ullam laboriosam est voluptatem maxime ut mollitia commodi. Et dignissimos suscipit perspiciatis.",
+ icon: ShieldCheckIcon,
+ },
+ {
+ name: "Powerful API",
+ description:
+ "Ab a facere voluptatem in quia corrupti veritatis aliquam. Veritatis labore quaerat ipsum quaerat id.",
+ icon: CogIcon,
+ },
+ {
+ name: "Database Backups",
+ description:
+ "Quia qui et est officia cupiditate qui consectetur. Ratione similique et impedit ea ipsum et.",
+ icon: ServerIcon,
+ },
+];
+
+const footerNavigation = {
+ solutions: [
+ { name: "Marketing", href: "#" },
+ { name: "Analytics", href: "#" },
+ { name: "Commerce", href: "#" },
+ { name: "Insights", href: "#" },
+ ],
+ support: [
+ { name: "Pricing", href: "#" },
+ { name: "Documentation", href: "#" },
+ { name: "Guides", href: "#" },
+ { name: "API Status", href: "#" },
+ ],
+ company: [
+ { name: "About", href: "#" },
+ { name: "Blog", href: "#" },
+ { name: "Jobs", href: "#" },
+ { name: "Press", href: "#" },
+ { name: "Partners", href: "#" },
+ ],
+ legal: [
+ { name: "Claim", href: "#" },
+ { name: "Privacy", href: "#" },
+ { name: "Terms", href: "#" },
+ ],
+ social: [
+ {
+ name: "Facebook",
+ href: "#",
+ icon: (props) => (
+
+
+
+ ),
+ },
+ {
+ name: "Instagram",
+ href: "#",
+ icon: (props) => (
+
+
+
+ ),
+ },
+ {
+ name: "Twitter",
+ href: "#",
+ icon: (props) => (
+
+
+
+ ),
+ },
+ {
+ name: "GitHub",
+ href: "#",
+ icon: (props) => (
+
+
+
+ ),
+ },
+ {
+ name: "Dribbble",
+ href: "#",
+ icon: (props) => (
+
+
+
+ ),
+ },
+ ],
+};
+
+import {
+ AnnotationIcon,
+ GlobeAltIcon,
+ LightningBoltIcon,
+ MailIcon,
+ ScaleIcon,
+} from "@heroicons/react/outline";
+import { Router, useRouter } from "next/router";
+
+const transferFeatures = [
+ {
+ id: 1,
+ name: "Competitive exchange rates",
+ description:
+ "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Maiores impedit perferendis suscipit eaque, iste dolor cupiditate blanditiis ratione.",
+ icon: GlobeAltIcon,
+ },
+ {
+ id: 2,
+ name: "No hidden fees",
+ description:
+ "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Maiores impedit perferendis suscipit eaque, iste dolor cupiditate blanditiis ratione.",
+ icon: ScaleIcon,
+ },
+ {
+ id: 3,
+ name: "Transfers are instant",
+ description:
+ "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Maiores impedit perferendis suscipit eaque, iste dolor cupiditate blanditiis ratione.",
+ icon: LightningBoltIcon,
+ },
+];
+const communicationFeatures = [
+ {
+ id: 1,
+ name: "Mobile notifications",
+ description:
+ "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Maiores impedit perferendis suscipit eaque, iste dolor cupiditate blanditiis ratione.",
+ icon: AnnotationIcon,
+ },
+ {
+ id: 2,
+ name: "Reminder emails",
+ description:
+ "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Maiores impedit perferendis suscipit eaque, iste dolor cupiditate blanditiis ratione.",
+ icon: MailIcon,
+ },
+];
+
+export default function HomePage() {
+ const router = useRouter();
+
+ return (
+
+
+
+
+
+
+
+ router.push("/login")}
+ className="inline-flex items-center px-4 py-2 border border-transparent text-base font-medium rounded-md text-white bg-gray-600 hover:bg-gray-700"
+ >
+ Login
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Illustration taken from Lucid Illustrations: https://lucid.pixsellz.io/ */}
+
+
+
+
+
+
+
+ {/* Feature section with screenshot */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A better way to send money
+
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit.
+ Possimus magnam voluptatum cupiditate veritatis in, accusamus
+ quisquam.
+
+
+
+
+
+
+ Transfer funds world-wide
+
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit.
+ Pariatur minima sequi recusandae, porro maiores officia
+ assumenda aliquam laborum ab aliquid veritatis impedit odit
+ adipisci optio iste blanditiis facere. Totam, velit.
+
+
+
+ {transferFeatures.map((item) => (
+
+
+
+
+
+
+ {item.name}
+
+
+
+ {item.description}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always in the loop
+
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit.
+ Impedit ex obcaecati natus eligendi delectus, cum deleniti
+ sunt in labore nihil quod quibusdam expedita nemo.
+
+
+
+ {communicationFeatures.map((item) => (
+
+
+
+
+
+
+ {item.name}
+
+
+
+ {item.description}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Feature section with grid */}
+
+
+
+ Deploy faster
+
+
+ Everything you need to deploy your app
+
+
+ Phasellus lorem quam molestie id quisque diam aenean nulla in.
+ Accumsan in quis quis nunc, ullamcorper malesuada. Eleifend
+ condimentum id viverra nulla.
+
+
+
+ {features.map((feature) => (
+
+
+
+
+
+
+
+
+
+ {feature.name}
+
+
+ {feature.description}
+
+
+
+
+ ))}
+
+
+
+
+
+ {/* Testimonial section */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Get notified when we’re launching.
+
+
+ Sagittis scelerisque nulla cursus in enim consectetur
+ quam. Dictum urna sed consectetur neque tristique
+ pellentesque.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Making the world a better place through constructing elegant
+ hierarchies.
+
+
+
+
+
+
+
+ Solutions
+
+
+ {footerNavigation.solutions.map((item) => (
+
+
+ {item.name}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+ © 2022 StoreEye, Inc. All rights reserved.
+
+
+
+
+
+
+ );
+}
+
+HomePage.Layout = BaseLayout;
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index 3ea0be3..7f84e1c 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -1,859 +1,30 @@
-import BaseLayout from "@components/layouts/base";
+import { NextPageContext } from "next";
-import { Fragment } from "react";
-import { Popover, Transition } from "@headlessui/react";
-import {
- CloudUploadIcon,
- CogIcon,
- LockClosedIcon,
- MenuIcon,
- RefreshIcon,
- ServerIcon,
- ShieldCheckIcon,
- XIcon,
-} from "@heroicons/react/outline";
-import { ChevronRightIcon, ExternalLinkIcon } from "@heroicons/react/solid";
+import { getSession } from "@lib/auth";
+import DashboardLayout from "@components/layouts/dashboard";
-const navigation = [
- { name: "Products", href: "#" },
- { name: "Use cases", href: "#" },
- { name: "Developers", href: "#" },
- { name: "Pricing", href: "#" },
-];
-const features = [
- {
- name: "Push to Deploy",
- description:
- "Ac tincidunt sapien vehicula erat auctor pellentesque rhoncus. Et magna sit morbi vitae lobortis.",
- icon: CloudUploadIcon,
- },
- {
- name: "SSL Certificates",
- description:
- "Qui aut temporibus nesciunt vitae dicta repellat sit dolores pariatur. Temporibus qui illum aut.",
- icon: LockClosedIcon,
- },
- {
- name: "Simple Queues",
- description:
- "Rerum quas incidunt deleniti quaerat suscipit mollitia. Amet repellendus ut odit dolores qui.",
- icon: RefreshIcon,
- },
- {
- name: "Advanced Security",
- description:
- "Ullam laboriosam est voluptatem maxime ut mollitia commodi. Et dignissimos suscipit perspiciatis.",
- icon: ShieldCheckIcon,
- },
- {
- name: "Powerful API",
- description:
- "Ab a facere voluptatem in quia corrupti veritatis aliquam. Veritatis labore quaerat ipsum quaerat id.",
- icon: CogIcon,
- },
- {
- name: "Database Backups",
- description:
- "Quia qui et est officia cupiditate qui consectetur. Ratione similique et impedit ea ipsum et.",
- icon: ServerIcon,
- },
-];
+export default function AppPage() {
+ return
App
;
+}
-const footerNavigation = {
- solutions: [
- { name: "Marketing", href: "#" },
- { name: "Analytics", href: "#" },
- { name: "Commerce", href: "#" },
- { name: "Insights", href: "#" },
- ],
- support: [
- { name: "Pricing", href: "#" },
- { name: "Documentation", href: "#" },
- { name: "Guides", href: "#" },
- { name: "API Status", href: "#" },
- ],
- company: [
- { name: "About", href: "#" },
- { name: "Blog", href: "#" },
- { name: "Jobs", href: "#" },
- { name: "Press", href: "#" },
- { name: "Partners", href: "#" },
- ],
- legal: [
- { name: "Claim", href: "#" },
- { name: "Privacy", href: "#" },
- { name: "Terms", href: "#" },
- ],
- social: [
- {
- name: "Facebook",
- href: "#",
- icon: (props) => (
-
-
-
- ),
- },
- {
- name: "Instagram",
- href: "#",
- icon: (props) => (
-
-
-
- ),
- },
- {
- name: "Twitter",
- href: "#",
- icon: (props) => (
-
-
-
- ),
+export async function getServerSideProps(context: NextPageContext) {
+ const session = await getSession(context);
+
+ if (!session?.user?.id) {
+ return {
+ redirect: {
+ permanent: false,
+ destination: "/login",
+ },
+ };
+ }
+
+ return {
+ redirect: {
+ permanent: false,
+ destination: "/apps/stores",
},
- {
- name: "GitHub",
- href: "#",
- icon: (props) => (
-
-
-
- ),
- },
- {
- name: "Dribbble",
- href: "#",
- icon: (props) => (
-
-
-
- ),
- },
- ],
-};
-
-import {
- AnnotationIcon,
- GlobeAltIcon,
- LightningBoltIcon,
- MailIcon,
- ScaleIcon,
-} from "@heroicons/react/outline";
-import { Router, useRouter } from "next/router";
-
-const transferFeatures = [
- {
- id: 1,
- name: "Competitive exchange rates",
- description:
- "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Maiores impedit perferendis suscipit eaque, iste dolor cupiditate blanditiis ratione.",
- icon: GlobeAltIcon,
- },
- {
- id: 2,
- name: "No hidden fees",
- description:
- "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Maiores impedit perferendis suscipit eaque, iste dolor cupiditate blanditiis ratione.",
- icon: ScaleIcon,
- },
- {
- id: 3,
- name: "Transfers are instant",
- description:
- "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Maiores impedit perferendis suscipit eaque, iste dolor cupiditate blanditiis ratione.",
- icon: LightningBoltIcon,
- },
-];
-const communicationFeatures = [
- {
- id: 1,
- name: "Mobile notifications",
- description:
- "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Maiores impedit perferendis suscipit eaque, iste dolor cupiditate blanditiis ratione.",
- icon: AnnotationIcon,
- },
- {
- id: 2,
- name: "Reminder emails",
- description:
- "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Maiores impedit perferendis suscipit eaque, iste dolor cupiditate blanditiis ratione.",
- icon: MailIcon,
- },
-];
-
-export default function HomePage() {
- const router = useRouter();
-
- return (
-
-
-
-
-
-
-
- router.push("/login")}
- className="inline-flex items-center px-4 py-2 border border-transparent text-base font-medium rounded-md text-white bg-gray-600 hover:bg-gray-700"
- >
- Login
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* Illustration taken from Lucid Illustrations: https://lucid.pixsellz.io/ */}
-
-
-
-
-
-
-
- {/* Feature section with screenshot */}
-
-
-
-
-
-
-
-
-
-
-
-
-
- A better way to send money
-
-
- Lorem ipsum dolor sit amet consectetur adipisicing elit.
- Possimus magnam voluptatum cupiditate veritatis in, accusamus
- quisquam.
-
-
-
-
-
-
- Transfer funds world-wide
-
-
- Lorem ipsum dolor sit amet consectetur adipisicing elit.
- Pariatur minima sequi recusandae, porro maiores officia
- assumenda aliquam laborum ab aliquid veritatis impedit odit
- adipisci optio iste blanditiis facere. Totam, velit.
-
-
-
- {transferFeatures.map((item) => (
-
-
-
-
-
-
- {item.name}
-
-
-
- {item.description}
-
-
- ))}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Always in the loop
-
-
- Lorem ipsum dolor sit amet consectetur adipisicing elit.
- Impedit ex obcaecati natus eligendi delectus, cum deleniti
- sunt in labore nihil quod quibusdam expedita nemo.
-
-
-
- {communicationFeatures.map((item) => (
-
-
-
-
-
-
- {item.name}
-
-
-
- {item.description}
-
-
- ))}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* Feature section with grid */}
-
-
-
- Deploy faster
-
-
- Everything you need to deploy your app
-
-
- Phasellus lorem quam molestie id quisque diam aenean nulla in.
- Accumsan in quis quis nunc, ullamcorper malesuada. Eleifend
- condimentum id viverra nulla.
-
-
-
- {features.map((feature) => (
-
-
-
-
-
-
-
-
-
- {feature.name}
-
-
- {feature.description}
-
-
-
-
- ))}
-
-
-
-
-
- {/* Testimonial section */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Get notified when we’re launching.
-
-
- Sagittis scelerisque nulla cursus in enim consectetur
- quam. Dictum urna sed consectetur neque tristique
- pellentesque.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Making the world a better place through constructing elegant
- hierarchies.
-
-
-
-
-
-
-
- Solutions
-
-
- {footerNavigation.solutions.map((item) => (
-
-
- {item.name}
-
-
- ))}
-
-
-
-
-
-
-
-
-
- © 2022 StoreEye, Inc. All rights reserved.
-
-
-
-
-
-
- );
+ };
}
-HomePage.Layout = BaseLayout;
+AppPage.Layout = DashboardLayout;
diff --git a/src/pages/login.tsx b/src/pages/login.tsx
index 6e872fc..02e47c6 100644
--- a/src/pages/login.tsx
+++ b/src/pages/login.tsx
@@ -1,182 +1,139 @@
+import { signIn } from "next-auth/react";
+import Link from "next/link";
import { useRouter } from "next/router";
-import BaseLayout from "../components/layouts/base";
+import { useState } from "react";
+import { useForm } from "react-hook-form";
-export default function LoginPage() {
+import { Alert } from "@components/ui/alert";
+import Button from "@components/ui/button";
+import { EmailField, Form, PasswordField } from "@components/ui/form/fields";
+import { ErrorCode } from "@lib/auth";
+import BaseLayout from "@components/layouts/base";
+
+interface LoginValues {
+ email: string;
+ password: string;
+ csrfToken: string;
+}
+
+export default function LoginPage({ csrfToken }: any) {
const router = useRouter();
+ const form = useForm
();
- return (
- <>
-
-
-
-
- Sign in to your account
-
-
+ const [errorMessage, setErrorMessage] = useState
(null);
-
-
-
-
-
-
-
-
-
- Or continue with
-
-
-
+ if (!res) setErrorMessage(errorMessages[ErrorCode.InternalServerError]);
+ else if (!res.error) router.push(callbackUrl);
+ else setErrorMessage(errorMessages[res.error] || "Coś poszło nie tak");
+ } catch (err) {
+ setErrorMessage(errorMessages[ErrorCode.InternalServerError]);
+ }
+ };
-
-
+ return (
+
+
+
+
+ Sign in to your account
+
+
-
+
-
+ {/*
Don't have account?
router.push("/register")}
+ onClick={() => router.push("/signup")}
className="font-medium text-indigo-600 hover:text-indigo-500"
>
Sign up
@@ -184,10 +141,9 @@ export default function LoginPage() {
-
-
+
*/}
- >
+
);
}
diff --git a/src/pages/register.tsx b/src/pages/signup.tsx
similarity index 98%
rename from src/pages/register.tsx
rename to src/pages/signup.tsx
index 0358e97..f628def 100644
--- a/src/pages/register.tsx
+++ b/src/pages/signup.tsx
@@ -1,7 +1,7 @@
import { useRouter } from "next/router";
import BaseLayout from "../components/layouts/base";
-export default function RegisterPage() {
+export default function SignupPage() {
const router = useRouter();
return (
@@ -130,4 +130,4 @@ export default function RegisterPage() {
);
}
-RegisterPage.Layout = BaseLayout;
+SignupPage.Layout = BaseLayout;
diff --git a/tsconfig.json b/tsconfig.json
index a7ed24a..926ff61 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -42,6 +42,11 @@
},
"incremental": true
},
+ "ts-node": {
+ "compilerOptions": {
+ "module": "commonjs"
+ }
+ },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}