Compare commits
10 Commits
docs
...
@blitzjs/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2da4f1516 | ||
|
|
acc07ce943 | ||
|
|
ea7561b8ee | ||
|
|
13639c5d1d | ||
|
|
afdc810d0b | ||
|
|
37aeaa7fa2 | ||
|
|
cadefb88e4 | ||
|
|
6f18cbdc98 | ||
|
|
3480d90098 | ||
|
|
9529dbd6f4 |
@@ -3847,6 +3847,25 @@
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jeliasson",
|
||||
"name": "Johan Eliasson",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/865493?v=4",
|
||||
"profile": "https://github.com/jeliasson",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jafarlihi",
|
||||
"name": "Hikmat Jafarli",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/43515211?v=4",
|
||||
"profile": "https://github.com/jafarlihi",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
11
.changeset/cuddly-singers-perform.md
Normal file
11
.changeset/cuddly-singers-perform.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
"blitz": minor
|
||||
"@blitzjs/auth": minor
|
||||
"@blitzjs/next": minor
|
||||
"@blitzjs/rpc": minor
|
||||
---
|
||||
|
||||
- New Blitz Auth Function `getAppSession`, This function will use the cookies and headers provided by the server component and returns the current session.
|
||||
- New Blitz Auth Hook `useAuthenticatedAppSession`, This hook is implemented as the replacement of the BlitzPage seurity auth utilities provided for the pages directory to work with React Server Components in the Nextjs 13 app directory
|
||||
- New Blitz React Server Component Wrapper, `BlitzProvider` is to be imported from setupBlitzClient in src/blitz-client.ts and to used to ideally wrap the entire application in the `RootLayout` in the root layout.ts file of next app directory.
|
||||
- Fix failing tests due to the error `NextRouter is not mounted` in next 13 blitz apps
|
||||
6
.changeset/empty-spiders-lay.md
Normal file
6
.changeset/empty-spiders-lay.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@blitzjs/recipe-chakra-ui": patch
|
||||
"@blitzjs/recipe-next-ui": patch
|
||||
---
|
||||
|
||||
Fix LabeledTextField update on next-ui and chakra-ui recipes
|
||||
6
.changeset/fast-adults-guess.md
Normal file
6
.changeset/fast-adults-guess.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@blitzjs/auth": minor
|
||||
"blitz": minor
|
||||
---
|
||||
|
||||
feature: Next Auth Adapter
|
||||
7
.changeset/fast-pans-impress.md
Normal file
7
.changeset/fast-pans-impress.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@blitzjs/next": minor
|
||||
---
|
||||
|
||||
Fix Next 13.2 compatibility
|
||||
|
||||
This updates the suspense patch to work with Next.js 13.2+. Hopefully soon we can stop patching once Next.js catches up with all the other frameworks and properly [exposes the `onRecoverableError` react hook](https://github.com/vercel/next.js/discussions/36641).
|
||||
6
.changeset/fast-toys-wash.md
Normal file
6
.changeset/fast-toys-wash.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"blitz": patch
|
||||
"@blitzjs/generator": patch
|
||||
---
|
||||
|
||||
Consolidate mutations schema to new schema.{ts|js} file.
|
||||
20
.changeset/four-radios-tickle.md
Normal file
20
.changeset/four-radios-tickle.md
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
"@blitzjs/auth": major
|
||||
"@blitzjs/codemod": minor
|
||||
---
|
||||
|
||||
## ⚠️ Breaking Changes for Blitz Auth
|
||||
|
||||
Automatically upgrade using codemod
|
||||
(Make sure to git commit before running this command to avoid losing changes)
|
||||
|
||||
```bash
|
||||
npx @blitz/codemod secure-password
|
||||
```
|
||||
|
||||
Introduce a new import path for the Blitz wrapper `SecurePassword` to fully decouple the library from `@blitzjs/auth`
|
||||
|
||||
```diff
|
||||
- import {SecurePassword} from "@blitzjs/auth"
|
||||
+ import {SecurePassword} from "@blitzjs/auth/secure-password"
|
||||
```
|
||||
@@ -46,7 +46,9 @@
|
||||
"@blitzjs/recipe-theme-ui": "0.34.0-canary.0",
|
||||
"@blitzjs/recipe-vanilla-extract": "0.34.0-canary.0",
|
||||
"test-rpc-path-root": "0.0.0",
|
||||
"test-full-auth-with-rpc": "0.0.0"
|
||||
"test-full-auth-with-rpc": "0.0.0",
|
||||
"next-blitz-auth": "0.1.0",
|
||||
"test-app-dir": "0.0.0"
|
||||
},
|
||||
"changesets": [
|
||||
"afraid-dancers-juggle",
|
||||
@@ -67,6 +69,7 @@
|
||||
"calm-nails-wait",
|
||||
"calm-papayas-protect",
|
||||
"calm-tomatoes-drive",
|
||||
"chatty-fireants-leave",
|
||||
"chilled-carrots-own",
|
||||
"chilly-candles-care",
|
||||
"chilly-nails-nail",
|
||||
@@ -77,6 +80,7 @@
|
||||
"cool-doors-invent",
|
||||
"cool-horses-check",
|
||||
"cuddly-pugs-crash",
|
||||
"cuddly-singers-perform",
|
||||
"curly-rules-speak",
|
||||
"curly-seas-serve",
|
||||
"curvy-days-attend",
|
||||
@@ -91,13 +95,17 @@
|
||||
"eleven-lobsters-drop",
|
||||
"empty-berries-rule",
|
||||
"empty-pants-search",
|
||||
"empty-spiders-lay",
|
||||
"empty-turkeys-wave",
|
||||
"fair-carrots-guess",
|
||||
"fair-kangaroos-clean",
|
||||
"fair-wombats-sneeze",
|
||||
"famous-kings-explain",
|
||||
"fast-adults-guess",
|
||||
"fast-clocks-push",
|
||||
"fast-pans-impress",
|
||||
"fast-papayas-grow",
|
||||
"fast-toys-wash",
|
||||
"fast-trainers-kneel",
|
||||
"few-dogs-fetch",
|
||||
"few-hounds-worry",
|
||||
@@ -109,6 +117,7 @@
|
||||
"forty-timers-rhyme",
|
||||
"four-brooms-juggle",
|
||||
"four-meals-fry",
|
||||
"four-radios-tickle",
|
||||
"four-sheep-judge",
|
||||
"funny-cups-pay",
|
||||
"fuzzy-bees-warn",
|
||||
@@ -169,6 +178,7 @@
|
||||
"lucky-cows-try",
|
||||
"lucky-months-guess",
|
||||
"lucky-years-turn",
|
||||
"mean-ears-speak",
|
||||
"mean-gorillas-reply",
|
||||
"modern-cameras-pull",
|
||||
"modern-games-dream",
|
||||
@@ -234,6 +244,7 @@
|
||||
"silent-colts-reply",
|
||||
"silent-lies-run",
|
||||
"silly-apricots-share",
|
||||
"silly-peas-work",
|
||||
"silly-shoes-agree",
|
||||
"six-apricots-kick",
|
||||
"slimy-humans-impress",
|
||||
@@ -258,6 +269,7 @@
|
||||
"stale-jobs-drum",
|
||||
"stale-parents-yawn",
|
||||
"strong-apes-reply",
|
||||
"strong-chicken-study",
|
||||
"strong-keys-lie",
|
||||
"stupid-rabbits-jump",
|
||||
"stupid-walls-sell",
|
||||
@@ -266,6 +278,7 @@
|
||||
"swift-glasses-laugh",
|
||||
"swift-poets-travel",
|
||||
"tall-meals-learn",
|
||||
"tall-radios-clean",
|
||||
"tame-keys-reply",
|
||||
"tame-pumpkins-nail",
|
||||
"tasty-maps-fetch",
|
||||
|
||||
6
.changeset/silly-peas-work.md
Normal file
6
.changeset/silly-peas-work.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"blitz": patch
|
||||
"@blitzjs/generator": patch
|
||||
---
|
||||
|
||||
Multiple fields forms using templates during generation - TODO
|
||||
67
.changeset/tall-radios-clean.md
Normal file
67
.changeset/tall-radios-clean.md
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
"@blitzjs/auth": patch
|
||||
"@blitzjs/rpc": patch
|
||||
"blitz": patch
|
||||
---
|
||||
|
||||
feature: Nextjs 13 App Directory Utility Methods
|
||||
|
||||
### 🔧 New Blitz Auth Hook `useAuthenticatedBlitzContext`
|
||||
|
||||
This hook is implemented as the replacement of the [`BlitzPage` seurity auth utilities](https://blitzjs.com/docs/authorization#secure-your-pages) provided for the pages directory to work with React Server Components in the Nextjs 13 app directory
|
||||
It can be used in any asynchronous server component be it in `page.ts` or in the layouts in `layout.ts`
|
||||
It uses the new [`redirect` function](https://beta.nextjs.org/docs/api-reference/redirect) to provide the required authorization in server side
|
||||
|
||||
#### API
|
||||
|
||||
```ts
|
||||
useAuthenticatedBlitzContext({
|
||||
redirectTo,
|
||||
redirectAuthenticatedTo,
|
||||
role,
|
||||
}: {
|
||||
redirectTo?: string | RouteUrlObject
|
||||
redirectAuthenticatedTo?: string | RouteUrlObject | ((ctx: Ctx) => string | RouteUrlObject)
|
||||
role?: string | string[]
|
||||
}): Promise<void>
|
||||
```
|
||||
|
||||
#### Usage
|
||||
|
||||
**Example Usage in React Server Component in `app` directory in Next 13**
|
||||
|
||||
```ts
|
||||
import {getAppSession, useAuthenticatedBlitzContext} from "src/blitz-server"
|
||||
...
|
||||
await useAuthenticatedBlitzContext({
|
||||
redirectTo: "/auth/login",
|
||||
role: ["admin"],
|
||||
redirectAuthenticatedTo: "/dashboard",
|
||||
})
|
||||
```
|
||||
|
||||
### 🔧 New Blitz RPC Hook `invokeResolver`
|
||||
|
||||
#### API
|
||||
|
||||
```ts
|
||||
invokeResolver<T extends (...args: any) => any, TInput = FirstParam<T>>(
|
||||
queryFn: T,
|
||||
params: TInput,
|
||||
): Promise<PromiseReturnType<T>>
|
||||
```
|
||||
|
||||
#### Example Usage
|
||||
|
||||
```ts
|
||||
...
|
||||
import {invokeResolver, useAuthenticatedBlitzContext} from "../src/blitz-server"
|
||||
import getCurrentUser from "../src/users/queries/getCurrentUser"
|
||||
|
||||
export default async function Home() {
|
||||
await useAuthenticatedBlitzContext({
|
||||
redirectTo: "/auth/login",
|
||||
})
|
||||
const user = await invokeResolver(getCurrentUser, null)
|
||||
...
|
||||
```
|
||||
1
.github/workflows/pr-release.yml
vendored
1
.github/workflows/pr-release.yml
vendored
@@ -99,4 +99,3 @@ jobs:
|
||||
repo: context.repo.repo,
|
||||
body: '```\n' + process.env.MESSAGE + '\n```',
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# pnpm manypkg check
|
||||
# pnpm lint
|
||||
# pnpm pretty-quick --staged
|
||||
pnpm manypkg check
|
||||
pnpm lint
|
||||
pnpm pretty-quick --staged
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<img alt="" src="https://img.shields.io/badge/Join%20our%20community-6700EB.svg?style=for-the-badge&labelColor=000000&logoWidth=20&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAQ9SURBVHgB7d3dVdtAEIbhcSpICUoH0IEogQqSVBBSAU4FSSpIOoAORAfQgSghHXzZ1U/YcMD4R9rZmf2ec3y448LyiNf27iLiGIAmPLrweC9Un3DhrzG6EarLNP09nlwJ1SOZ/lQr5N80/S/p2QMVCBf5N17XCfm1Y/rBHqjAG9PPHvBsz+mf9WAP+HLA9M/YA14cOP2payH7jpj+VCtk1wnTP+vj7xCy6cTpn7EHLMLp059iD1iD8eveJbVCNsSLheX1YA/YgOWnf8YeKB3Wmf7Ud6Fy4f/FHmtpxbl3YlC4MJ/Cj0bWdwPnPbARg+L0S54XQHS32WwuxClzd4CM0z9rPfeAuTtA5ulPXYQ7wZ04Y+oOoDD9KZc9YOoOoDj9s4dwFzgXR6w1wIPoOvPWA9buAHEJ173o3gWiy3AnuBUHLEbgmYwvAk1/wuM8vAgexThzbwPDkx7/DHwVXfFOxP2GmsKd4Ab6zPeAyU8CI7AHFmH2BRCBPXAyk18GzUrqAXCTiR4ssyj0VFw/oCU8+e+RZ33AWz6KMaYbIIWxB+JSLs1bsbkeMN0AqakHvoku9oA2sAfqBvbAQdw0QArsgb25aYBUQT3QgT2gB+yBuqGcHij2UCqXDZACe2Anlw2QYg/QAOyBuoE98CL3DZDCuK4/rh/Q7oGL6U+TOvcNkJoijN8X1C48+T+g75eQDrAH/qmqAVJgDwyqaoAUe4AGYA/UDZX3QLUNkEIZPRCd5+6BahsgVUgPROwBTSijB7jpVAvGHriHvmw9wAZ4BpX1ABvgmakHtPcbRuwBTWAPULgAV9D/jKDY9YRvwvgEaurD44uQHvAol7qBW7WKluVtIHiUS7GyvA0s6CiXDnxrpQfsgbqBS7GKk/2jYHCrVlGyfxTMrVo0ALdq1Q3sgSKofh0M9oA61a+D2QM0AHugbmAPqClmSRjK2apVVQ8UsySsoK1aHdgDesCtWnUDeyCrIpeFg1u3sylyWTi3btMA7IG6gT2wuuK3hoE9sKrit4YVslWLPaAN7IG6ocKt2zmY2h4O9sDiTG0PZw/QANy6XTewBxZj9ogYVHy025LMHhEz9cBn0We6B0yfERReBLfhx0/R1YQHPx/QBPbA0VwcEwf2wNFcHBPHHjiem3MC2QPHcXdSaJjA+KfgTPQ8hhfjBzHC40mhlzJ+Xq9lK4a4PCs43AVaGTed5mZq+iOXZwWHi3AnOj2wFWNcnxYe7gTxLtBKHuamP/J+Wnh8a5irB7ZC5Yk9gPX1QuXC+usHWqGyhYvUYR0a7zboUOFCNVhnk0krZAOW7wFOvzXhom2xnEbIHizTA1wEYhWW6YFGyC6c1gOcfg9wfA80Qj7g8B7g9HuCww+haIR8wf49wOn3Cvv9k8tGyC/s7gFOv3fY3QONkH+v9MBWqB7PeqDn9FcIT//kcitUn6kHOu/T/xfWzlQy3dEHhwAAAABJRU5ErkJggg==">
|
||||
</a>
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
<a aria-label="All Contributors" href="#contributors-"><img alt="" src="https://img.shields.io/badge/all_contributors-406-17BB8A.svg?style=for-the-badge&labelColor=000000"></a>
|
||||
<a aria-label="All Contributors" href="#contributors-"><img alt="" src="https://img.shields.io/badge/all_contributors-408-17BB8A.svg?style=for-the-badge&labelColor=000000"></a>
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
<a aria-label="License" href="https://github.com/blitz-js/blitz/blob/main/LICENSE">
|
||||
<img alt="" src="https://img.shields.io/npm/l/blitz.svg?style=for-the-badge&labelColor=000000&color=blue">
|
||||
@@ -718,6 +718,10 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
|
||||
<td align="center"><a href="sweetliquid.me"><img src="https://avatars.githubusercontent.com/u/18693190?v=4?s=100" width="100px;" alt=""/><br /><sub><b>sweetliquid</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sweetliquid" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/exKAZUu"><img src="https://avatars.githubusercontent.com/u/436237?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sakamoto, Kazunori</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=exKAZUu" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=exKAZUu" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/jeliasson"><img src="https://avatars.githubusercontent.com/u/865493?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Johan Eliasson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jeliasson" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jafarlihi"><img src="https://avatars.githubusercontent.com/u/43515211?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Hikmat Jafarli</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jafarlihi" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=jafarlihi" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
|
||||
7
apps/next13/.env
Normal file
7
apps/next13/.env
Normal file
@@ -0,0 +1,7 @@
|
||||
# Environment variables declared in this file are automatically made available to Prisma.
|
||||
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
|
||||
|
||||
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
|
||||
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
|
||||
|
||||
DATABASE_URL="file:./dev.db"
|
||||
3
apps/next13/.eslintrc.json
Normal file
3
apps/next13/.eslintrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
||||
38
apps/next13/.gitignore
vendored
Normal file
38
apps/next13/.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
.vscode
|
||||
81
apps/next13/CHANGELOG.md
Normal file
81
apps/next13/CHANGELOG.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# next-blitz-auth
|
||||
|
||||
## 0.1.1-beta.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 37aeaa7fa: feature: Nextjs 13 App Directory Utility Methods
|
||||
|
||||
### 🔧 New Blitz Auth Hook `useAuthenticatedBlitzContext`
|
||||
|
||||
This hook is implemented as the replacement of the [`BlitzPage` seurity auth utilities](https://blitzjs.com/docs/authorization#secure-your-pages) provided for the pages directory to work with React Server Components in the Nextjs 13 app directory
|
||||
It can be used in any asynchronous server component be it in `page.ts` or in the layouts in `layout.ts`
|
||||
It uses the new [`redirect` function](https://beta.nextjs.org/docs/api-reference/redirect) to provide the required authorization in server side
|
||||
|
||||
#### API
|
||||
|
||||
```ts
|
||||
useAuthenticatedBlitzContext({
|
||||
redirectTo,
|
||||
redirectAuthenticatedTo,
|
||||
role,
|
||||
}: {
|
||||
redirectTo?: string | RouteUrlObject
|
||||
redirectAuthenticatedTo?: string | RouteUrlObject | ((ctx: Ctx) => string | RouteUrlObject)
|
||||
role?: string | string[]
|
||||
}): Promise<void>
|
||||
```
|
||||
|
||||
#### Usage
|
||||
|
||||
**Example Usage in React Server Component in `app` directory in Next 13**
|
||||
|
||||
```ts
|
||||
import {getAppSession, useAuthenticatedBlitzContext} from "src/blitz-server"
|
||||
...
|
||||
await useAuthenticatedBlitzContext({
|
||||
redirectTo: "/auth/login",
|
||||
role: ["admin"],
|
||||
redirectAuthenticatedTo: "/dashboard",
|
||||
})
|
||||
```
|
||||
|
||||
### 🔧 New Blitz RPC Hook `invokeResolver`
|
||||
|
||||
#### API
|
||||
|
||||
```ts
|
||||
invokeResolver<T extends (...args: any) => any, TInput = FirstParam<T>>(
|
||||
queryFn: T,
|
||||
params: TInput,
|
||||
): Promise<PromiseReturnType<T>>
|
||||
```
|
||||
|
||||
#### Example Usage
|
||||
|
||||
```ts
|
||||
...
|
||||
import {invokeResolver, useAuthenticatedBlitzContext} from "../src/blitz-server"
|
||||
import getCurrentUser from "../src/users/queries/getCurrentUser"
|
||||
|
||||
export default async function Home() {
|
||||
await useAuthenticatedBlitzContext({
|
||||
redirectTo: "/auth/login",
|
||||
})
|
||||
const user = await invokeResolver(getCurrentUser, null)
|
||||
...
|
||||
```
|
||||
|
||||
- Updated dependencies [cadefb88e]
|
||||
- Updated dependencies [6f18cbdc9]
|
||||
- Updated dependencies [acc07ce94]
|
||||
- Updated dependencies [ea7561b8e]
|
||||
- Updated dependencies [9529dbd6f]
|
||||
- Updated dependencies [ea7561b8e]
|
||||
- Updated dependencies [6e88a847f]
|
||||
- Updated dependencies [37aeaa7fa]
|
||||
- blitz@2.0.0-beta.24
|
||||
- @blitzjs/auth@2.0.0-beta.24
|
||||
- @blitzjs/next@2.0.0-beta.24
|
||||
- @blitzjs/rpc@2.0.0-beta.24
|
||||
- @blitzjs/config@2.0.0-beta.24
|
||||
26
apps/next13/README.md
Normal file
26
apps/next13/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Next.js 13 + Blitz Auth
|
||||
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) + [`Blitz Auth`](https://blitzjs.com/docs/auth).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can go to the `/signup` page and create a new account.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js and Blitz.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
- [Blitz.js Documentation](https://blitzjs.com/docs/) — learn about Blitz.js.
|
||||
- [Blitz Auth Documentation](https://blitzjs.com/docs/auth) — learn about Blitz Auth plugin.
|
||||
8
apps/next13/app/auth/layout.tsx
Normal file
8
apps/next13/app/auth/layout.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import {useAuthenticatedBlitzContext} from "@blitzjs/auth"
|
||||
|
||||
export default async function RootLayout({children}: {children: React.ReactNode}) {
|
||||
await useAuthenticatedBlitzContext({
|
||||
redirectAuthenticatedTo: "/",
|
||||
})
|
||||
return <>{children}</>
|
||||
}
|
||||
13
apps/next13/app/auth/login/page.tsx
Normal file
13
apps/next13/app/auth/login/page.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
"use client"
|
||||
|
||||
import {LoginForm} from "../../../src/auth/components/LoginForm"
|
||||
import {useRouter} from "next/navigation"
|
||||
import {useSearchParams} from "next/navigation"
|
||||
|
||||
const LoginPage = () => {
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
return <LoginForm onSuccess={(_user) => {}} />
|
||||
}
|
||||
|
||||
export default LoginPage
|
||||
11
apps/next13/app/auth/signup/page.tsx
Normal file
11
apps/next13/app/auth/signup/page.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
"use client"
|
||||
|
||||
import {useRouter} from "next/navigation"
|
||||
import SignupForm from "../../../src/auth/components/SignupForm"
|
||||
|
||||
const SignUp = () => {
|
||||
const router = useRouter()
|
||||
return <SignupForm onSuccess={() => router.push("/")} />
|
||||
}
|
||||
|
||||
export default SignUp
|
||||
24
apps/next13/app/error.tsx
Normal file
24
apps/next13/app/error.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
"use client" // Error components must be Client components
|
||||
|
||||
import React, {useEffect} from "react"
|
||||
|
||||
export default function Error({error, reset}: {error: Error; reset: () => void}) {
|
||||
useEffect(() => {
|
||||
// Log the error to an error reporting service
|
||||
console.error(error)
|
||||
}, [error])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Something went wrong!</h2>
|
||||
<button
|
||||
onClick={
|
||||
// Attempt to recover by trying to re-render the segment
|
||||
() => reset()
|
||||
}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
57
apps/next13/app/layout.tsx
Normal file
57
apps/next13/app/layout.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import "src/styles/globals.css"
|
||||
import {BlitzProvider} from "../src/blitz-client"
|
||||
import styles from "src/styles/Home.module.css"
|
||||
|
||||
export default function RootLayout({children}: {children: React.ReactNode}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Create Next App</title>
|
||||
<meta name="description" content="Generated by create next app" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<BlitzProvider>
|
||||
<div className={styles.globe} />
|
||||
|
||||
<div className={styles.container}>
|
||||
<div className={styles.toastContainer}>
|
||||
<p>
|
||||
<strong>Congrats!</strong> Your app is ready, including user sign-up and log-in.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<main className={styles.main}>
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.logo}>
|
||||
<svg viewBox="0 0 165 66">
|
||||
<path d="M104.292 56.033C104.292 56.408 104.206 56.6636 104.036 56.8C103.9 56.9363 103.627 57.0045 103.218 57.0045H99.7409C99.4001 57.0045 99.1615 56.9533 99.0251 56.8511C98.8888 56.7147 98.8206 56.4932 98.8206 56.1864L98.9229 19.8324C98.9229 19.3211 99.1444 19.0654 99.5876 19.0654H103.627C103.839 19.0654 104.292 19.0672 104.292 19.0672V19.8324V56.033ZM64.3531 57.0081C64.1145 57.0081 63.927 56.9399 63.7906 56.8035C63.6543 56.6672 63.5861 56.4968 63.5861 56.2922V19.9383C63.5861 19.3588 63.8588 19.069 64.4042 19.069H76.829C81.533 19.069 85.1463 19.9212 87.6687 21.6256C90.1912 23.2958 91.4524 25.7331 91.4524 28.9373C91.4524 30.9484 90.924 32.6528 89.8673 34.0504C88.8106 35.4138 87.1063 36.5217 84.7543 37.3739C84.6179 37.4079 84.5497 37.4932 84.5497 37.6295C84.5497 37.7318 84.6179 37.7999 84.7543 37.834C87.2767 38.5158 89.1686 39.5895 90.4298 41.0553C91.7251 42.521 92.3727 44.4469 92.3727 46.833C92.3727 50.2418 91.0945 52.7983 88.5379 54.5027C85.9814 56.1729 82.2318 57.0081 77.2892 57.0081H64.3531ZM77.5448 35.5843C79.6923 35.5843 81.516 35.1071 83.0158 34.1526C84.5157 33.1982 85.2656 31.6983 85.2656 29.6531C85.2656 27.6079 84.5157 26.0569 83.0158 25.0002C81.5501 23.9435 79.5219 23.4151 76.9313 23.4151H70.5399C70.0286 23.4151 69.7729 23.6367 69.7729 24.0798V34.8684C69.7729 35.3457 69.9604 35.5843 70.3354 35.5843H77.5448ZM77.0335 52.662C82.9647 52.662 85.9303 50.5997 85.9303 46.4751C85.9303 44.3276 85.1633 42.7255 83.6294 41.6688C82.0955 40.6121 80.0673 40.0838 77.5448 40.0838H70.591C70.2843 40.0838 70.0627 40.1349 69.9263 40.2372C69.8241 40.3394 69.7729 40.5099 69.7729 40.7485V51.895C69.7729 52.4063 69.9604 52.662 70.3354 52.662H77.0335ZM142.707 56.8624C142.81 56.9647 142.997 57.0158 143.27 57.0158H163.876C164.387 57.0158 164.643 56.7772 164.643 56.3V53.948V53.3344H163.978H149.866C149.593 53.3344 149.457 53.2492 149.457 53.0788C149.457 52.9765 149.508 52.8572 149.61 52.7208L163.876 33.8536C164.251 33.2741 164.438 32.7628 164.438 32.3197V30.479V29.9144C164.438 29.9144 164.051 29.9165 163.876 29.9165H144.241C143.866 29.9165 143.679 30.121 143.679 30.5301V32.831C143.679 33.1037 143.713 33.2911 143.781 33.3934C143.883 33.4957 144.071 33.5468 144.344 33.5468H157.075C157.382 33.5468 157.535 33.632 157.535 33.8025L157.382 34.1092L143.219 52.9765C142.946 53.3515 142.759 53.6412 142.656 53.8457C142.588 54.0502 142.554 54.3059 142.554 54.6127V56.3C142.554 56.5727 142.605 56.7602 142.707 56.8624ZM116.929 19.0676H111.51V27.7684C114.503 27.7684 116.929 25.3419 116.929 22.3486V19.0676ZM116.926 56.0308C116.926 56.4058 116.841 56.6614 116.67 56.7978C116.534 56.9341 116.278 57.0023 115.903 57.0023H112.427C112.086 57.0023 111.847 56.9512 111.711 56.8489C111.574 56.7126 111.506 56.491 111.506 56.1842V30.6699C111.506 30.3972 111.557 30.2098 111.66 30.1075C111.762 29.9712 111.949 29.903 112.222 29.903H117.028L116.926 56.0308ZM132.183 34.3137C132.183 33.9728 132.336 33.8024 132.643 33.8024H138.779C139.256 33.8024 139.495 33.5979 139.495 33.1888V30.4789V29.9165H138.881H132.745C132.439 29.9165 132.285 29.7631 132.285 29.4563V21.531V20.713L131.621 20.7129H128.093C127.752 20.7129 127.547 20.9515 127.479 21.4288L126.865 29.4563C126.865 29.7631 126.729 29.9165 126.456 29.9165H122.366C121.957 29.9165 121.752 30.1039 121.752 30.4789V33.1888C121.752 33.5979 121.974 33.8024 122.417 33.8024H126.252C126.593 33.8024 126.763 34.0069 126.763 34.416V50.6244C126.763 52.806 127.309 54.4252 128.399 55.4819C129.49 56.5045 131.16 57.0158 133.41 57.0158C135.796 57.0158 137.535 56.9306 138.625 56.7601C139.137 56.6579 139.392 56.3681 139.392 55.8909V53.6923V53.0787H138.779H135.507C134.348 53.0787 133.495 52.806 132.95 52.2606C132.439 51.7152 132.183 50.7267 132.183 49.295V34.3137Z"></path>
|
||||
<path d="M0.241243 33.2639H10.9742C15.0585 33.2639 18.9054 35.1835 21.3612 38.4471L31.9483 52.5165C32.1484 52.7824 32.1786 53.1393 32.026 53.435L25.9232 65.2592C25.6304 65.8265 24.8455 65.8932 24.4612 65.3835L0.241243 33.2639Z"></path>
|
||||
<path d="M42.4727 33.2822H31.7398C27.6555 33.2822 23.8086 31.3626 21.3528 28.0991L10.7656 14.0297C10.5656 13.7638 10.5354 13.4068 10.688 13.1111L16.7908 1.28696C17.0836 0.719654 17.8684 0.652924 18.2528 1.16266L42.4727 33.2822Z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div className={styles.buttonContainer}></div>
|
||||
</div>
|
||||
|
||||
<div className={styles.body}>{children} </div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer className={styles.footer}>
|
||||
<span>Powered by</span>
|
||||
<a
|
||||
href="https://blitzjs.com?utm_source=blitz-new&utm_medium=app-template&utm_campaign=blitz-new"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.textLink}
|
||||
>
|
||||
Blitz.js
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
</BlitzProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
47
apps/next13/app/page.tsx
Normal file
47
apps/next13/app/page.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import Link from "next/link"
|
||||
import styles from "src/styles/Home.module.css"
|
||||
import Test from "./react-query"
|
||||
import {invoke, useAuthenticatedBlitzContext} from "../src/blitz-server"
|
||||
import getCurrentUser from "../src/users/queries/getCurrentUser"
|
||||
|
||||
export default async function Home() {
|
||||
await useAuthenticatedBlitzContext({
|
||||
redirectTo: "/auth/login",
|
||||
})
|
||||
const user = await invoke(getCurrentUser, null)
|
||||
console.log("user", user)
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Link href={"/auth/signup"} className={styles.button}>
|
||||
<strong>Sign Up</strong>
|
||||
</Link>
|
||||
<Link href={"/auth/login"} className={styles.loginButton}>
|
||||
<strong>Login</strong>
|
||||
</Link>
|
||||
<div style={{height: 20}} />
|
||||
<p>Server Session</p>
|
||||
<p>UserId: {user?.id}</p>
|
||||
<p>Email: {user?.email}</p>
|
||||
<div style={{height: 20}} />
|
||||
<p>Client Session</p>
|
||||
<Test />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
34
apps/next13/app/react-query.tsx
Normal file
34
apps/next13/app/react-query.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
"use client"
|
||||
|
||||
import {useQuery, useMutation} from "@blitzjs/rpc"
|
||||
import logout from "../src/auth/mutations/logout"
|
||||
import getCurrentUser from "../src/users/queries/getCurrentUser"
|
||||
import {useTransition} from "react"
|
||||
import {useRouter} from "next/navigation"
|
||||
|
||||
export default function Test() {
|
||||
const router = useRouter()
|
||||
const [user] = useQuery(getCurrentUser, null)
|
||||
const [isPending, startTransition] = useTransition()
|
||||
const [logoutMutation] = useMutation(logout)
|
||||
console.log(user)
|
||||
return (
|
||||
<div>
|
||||
<h1>Test</h1>
|
||||
<p>{user?.email}</p>
|
||||
<button
|
||||
className="button small"
|
||||
onClick={async () => {
|
||||
await logoutMutation()
|
||||
startTransition(() => {
|
||||
// Refresh the current route and fetch new data from the server without
|
||||
// losing client-side browser or React state.
|
||||
router.refresh()
|
||||
})
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
11
apps/next13/next.config.js
Normal file
11
apps/next13/next.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const {withBlitz} = require("@blitzjs/next")
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
appDir: true,
|
||||
serverComponentsExternalPackages: ["@prisma/client", "prisma"],
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = withBlitz(nextConfig)
|
||||
38
apps/next13/package.json
Normal file
38
apps/next13/package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "next-blitz-auth",
|
||||
"version": "0.1.1-beta.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"blitz:dev": "next dev",
|
||||
"blitz:build": "next build",
|
||||
"blitz:start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/auth": "workspace:*",
|
||||
"@blitzjs/config": "workspace:*",
|
||||
"@blitzjs/next": "workspace:*",
|
||||
"@blitzjs/rpc": "workspace:*",
|
||||
"@hookform/error-message": "2.0.0",
|
||||
"@hookform/resolvers": "2.9.10",
|
||||
"@prisma/client": "^4.5.0",
|
||||
"@tanstack/react-query": "4.0.10",
|
||||
"blitz": "workspace:*",
|
||||
"flatted": "3.2.7",
|
||||
"next": "13.2.4",
|
||||
"prisma": "^4.5.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "7.39.1",
|
||||
"superjson": "1.11.0",
|
||||
"zod": "3.20.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.11.7",
|
||||
"@types/react": "18.0.23",
|
||||
"@types/react-dom": "18.0.7",
|
||||
"eslint": "8.26.0",
|
||||
"eslint-config-next": "13.0.0",
|
||||
"typescript": "4.8.4"
|
||||
}
|
||||
}
|
||||
BIN
apps/next13/prisma/dev.db
Normal file
BIN
apps/next13/prisma/dev.db
Normal file
Binary file not shown.
5
apps/next13/prisma/index.ts
Normal file
5
apps/next13/prisma/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import {PrismaClient} from "@prisma/client"
|
||||
|
||||
export * from "@prisma/client"
|
||||
const db = new PrismaClient()
|
||||
export default db
|
||||
@@ -0,0 +1,47 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"name" TEXT,
|
||||
"email" TEXT NOT NULL,
|
||||
"hashedPassword" TEXT,
|
||||
"role" TEXT NOT NULL DEFAULT 'USER'
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Session" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"expiresAt" DATETIME,
|
||||
"handle" TEXT NOT NULL,
|
||||
"hashedSessionToken" TEXT,
|
||||
"antiCSRFToken" TEXT,
|
||||
"publicData" TEXT,
|
||||
"privateData" TEXT,
|
||||
"userId" INTEGER,
|
||||
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Token" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"hashedToken" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"expiresAt" DATETIME NOT NULL,
|
||||
"sentTo" TEXT NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
CONSTRAINT "Token_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Session_handle_key" ON "Session"("handle");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Token_hashedToken_type_key" ON "Token"("hashedToken", "type");
|
||||
48
apps/next13/prisma/migrations/20230206052436_/migration.sql
Normal file
48
apps/next13/prisma/migrations/20230206052436_/migration.sql
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the `Token` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the column `createdAt` on the `User` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `role` on the `User` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `updatedAt` on the `User` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `createdAt` on the `Session` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `updatedAt` on the `Session` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- DropIndex
|
||||
DROP INDEX "Token_hashedToken_type_key";
|
||||
|
||||
-- DropTable
|
||||
PRAGMA foreign_keys=off;
|
||||
DROP TABLE "Token";
|
||||
PRAGMA foreign_keys=on;
|
||||
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_User" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"name" TEXT,
|
||||
"email" TEXT NOT NULL,
|
||||
"hashedPassword" TEXT
|
||||
);
|
||||
INSERT INTO "new_User" ("email", "hashedPassword", "id", "name") SELECT "email", "hashedPassword", "id", "name" FROM "User";
|
||||
DROP TABLE "User";
|
||||
ALTER TABLE "new_User" RENAME TO "User";
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
CREATE TABLE "new_Session" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"expiresAt" DATETIME,
|
||||
"handle" TEXT NOT NULL,
|
||||
"hashedSessionToken" TEXT,
|
||||
"antiCSRFToken" TEXT,
|
||||
"publicData" TEXT,
|
||||
"privateData" TEXT,
|
||||
"userId" INTEGER,
|
||||
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Session" ("antiCSRFToken", "expiresAt", "handle", "hashedSessionToken", "id", "privateData", "publicData", "userId") SELECT "antiCSRFToken", "expiresAt", "handle", "hashedSessionToken", "id", "privateData", "publicData", "userId" FROM "Session";
|
||||
DROP TABLE "Session";
|
||||
ALTER TABLE "new_Session" RENAME TO "Session";
|
||||
CREATE UNIQUE INDEX "Session_handle_key" ON "Session"("handle");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
3
apps/next13/prisma/migrations/migration_lock.toml
Normal file
3
apps/next13/prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "sqlite"
|
||||
33
apps/next13/prisma/schema.prisma
Normal file
33
apps/next13/prisma/schema.prisma
Normal file
@@ -0,0 +1,33 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
name String?
|
||||
email String @unique
|
||||
hashedPassword String?
|
||||
|
||||
sessions Session[]
|
||||
}
|
||||
|
||||
model Session {
|
||||
id Int @id @default(autoincrement())
|
||||
expiresAt DateTime?
|
||||
handle String @unique
|
||||
hashedSessionToken String?
|
||||
antiCSRFToken String?
|
||||
publicData String?
|
||||
privateData String?
|
||||
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
userId Int?
|
||||
}
|
||||
BIN
apps/next13/public/favicon.ico
Normal file
BIN
apps/next13/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
4
apps/next13/public/vercel.svg
Normal file
4
apps/next13/public/vercel.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
61
apps/next13/src/auth/components/LoginForm.tsx
Normal file
61
apps/next13/src/auth/components/LoginForm.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import {AuthenticationError, PromiseReturnType} from "blitz"
|
||||
import Link from "next/link"
|
||||
import {LabeledTextField} from "../../core/components/LabeledTextField"
|
||||
import {Form, FORM_ERROR} from "../../core/components/Form"
|
||||
import login from "../../auth/mutations/login"
|
||||
import {Login} from "../../auth/validations"
|
||||
import {useMutation} from "@blitzjs/rpc"
|
||||
import {startTransition} from "react"
|
||||
import {useRouter} from "next/navigation"
|
||||
|
||||
type LoginFormProps = {
|
||||
onSuccess?: (user: PromiseReturnType<typeof login>) => void
|
||||
}
|
||||
|
||||
export const LoginForm = (props: LoginFormProps) => {
|
||||
const [loginMutation] = useMutation(login)
|
||||
const router = useRouter()
|
||||
return (
|
||||
<div>
|
||||
<h1>Login</h1>
|
||||
|
||||
<Form
|
||||
submitText="Login"
|
||||
schema={Login}
|
||||
initialValues={{email: "", password: ""}}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
const user = await loginMutation(values)
|
||||
props.onSuccess?.(user)
|
||||
startTransition(() => {
|
||||
// Refresh the current route and fetch new data from the server without
|
||||
// losing client-side browser or React state.
|
||||
router.refresh()
|
||||
})
|
||||
} catch (error: any) {
|
||||
if (error instanceof AuthenticationError) {
|
||||
return {[FORM_ERROR]: "Sorry, those credentials are invalid"}
|
||||
} else {
|
||||
return {
|
||||
[FORM_ERROR]:
|
||||
"Sorry, we had an unexpected error. Please try again. - " + error.toString(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LabeledTextField name="email" label="Email" placeholder="Email" />
|
||||
<LabeledTextField name="password" label="Password" placeholder="Password" type="password" />
|
||||
<div>
|
||||
<a>Forgot your password?</a>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
<div style={{marginTop: "1rem"}}>
|
||||
Or <Link href={"/auth/signup"}>Sign Up</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginForm
|
||||
51
apps/next13/src/auth/components/SignupForm.tsx
Normal file
51
apps/next13/src/auth/components/SignupForm.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import {LabeledTextField} from "../../core/components/LabeledTextField"
|
||||
import {Form, FORM_ERROR} from "../../core/components/Form"
|
||||
import signup from "../../auth/mutations/signup"
|
||||
import {Signup} from "../../auth/validations"
|
||||
import {useMutation} from "@blitzjs/rpc"
|
||||
import {startTransition} from "react"
|
||||
import {useRouter} from "next/navigation"
|
||||
|
||||
type SignupFormProps = {
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
export const SignupForm = (props: SignupFormProps) => {
|
||||
const [signupMutation] = useMutation(signup)
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Create an Account</h1>
|
||||
|
||||
<Form
|
||||
submitText="Create Account"
|
||||
schema={Signup}
|
||||
initialValues={{email: "", password: ""}}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await signupMutation(values)
|
||||
props.onSuccess?.()
|
||||
startTransition(() => {
|
||||
// Refresh the current route and fetch new data from the server without
|
||||
// losing client-side browser or React state.
|
||||
router.refresh()
|
||||
})
|
||||
} catch (error: any) {
|
||||
if (error.code === "P2002" && error.meta?.target?.includes("email")) {
|
||||
// Error "P2002" comes from Prisma (https://www.prisma.io/docs/reference/api-reference/error-reference#p2002)
|
||||
return {email: "This email is already being used"}
|
||||
} else {
|
||||
return {[FORM_ERROR]: error.toString()}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LabeledTextField name="email" label="Email" placeholder="Email" />
|
||||
<LabeledTextField name="password" label="Password" placeholder="Password" type="password" />
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SignupForm
|
||||
26
apps/next13/src/auth/mutations/changePassword.ts
Normal file
26
apps/next13/src/auth/mutations/changePassword.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import {NotFoundError} from "blitz"
|
||||
import db from "../../../prisma"
|
||||
// import {authenticateUser} from "./login"
|
||||
import {ChangePassword} from "../validations"
|
||||
import {resolver} from "@blitzjs/rpc"
|
||||
// import {SecurePassword} from "@blitzjs/auth"
|
||||
|
||||
export default resolver.pipe(
|
||||
//@ts-ignore
|
||||
resolver.zod(ChangePassword),
|
||||
resolver.authorize(),
|
||||
async ({currentPassword, newPassword}, ctx) => {
|
||||
const user = await db.user.findFirst({where: {id: ctx.session.userId}})
|
||||
if (!user) throw new NotFoundError()
|
||||
|
||||
// await authenticateUser(user.email, currentPassword)
|
||||
|
||||
// const hashedPassword = await SecurePassword.hash(newPassword.trim())
|
||||
await db.user.update({
|
||||
where: {id: user.id},
|
||||
data: {hashedPassword: newPassword},
|
||||
})
|
||||
|
||||
return true
|
||||
},
|
||||
)
|
||||
64
apps/next13/src/auth/mutations/forgotPassword.test.ts
Normal file
64
apps/next13/src/auth/mutations/forgotPassword.test.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import {vi, describe, it, beforeEach} from "vitest"
|
||||
import db from "db"
|
||||
import {hash256} from "@blitzjs/auth"
|
||||
import forgotPassword from "./forgotPassword"
|
||||
import previewEmail from "preview-email"
|
||||
import {Ctx} from "@blitzjs/next"
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.$reset()
|
||||
})
|
||||
|
||||
const generatedToken = "plain-token"
|
||||
vi.mock("@blitzjs/auth", async () => {
|
||||
const auth = await vi.importActual<Record<string, unknown>>("@blitzjs/auth")!
|
||||
return {
|
||||
...auth,
|
||||
generateToken: () => generatedToken,
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock("preview-email", () => ({default: vi.fn()}))
|
||||
|
||||
describe("forgotPassword mutation", () => {
|
||||
it("does not throw error if user doesn't exist", async () => {
|
||||
await expect(forgotPassword({email: "no-user@email.com"}, {} as Ctx)).resolves.not.toThrow()
|
||||
})
|
||||
|
||||
it("works correctly", async () => {
|
||||
// Create test user
|
||||
const user = await db.user.create({
|
||||
data: {
|
||||
email: "user@example.com",
|
||||
tokens: {
|
||||
// Create old token to ensure it's deleted
|
||||
create: {
|
||||
type: "RESET_PASSWORD",
|
||||
hashedToken: "token",
|
||||
expiresAt: new Date(),
|
||||
sentTo: "user@example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {tokens: true},
|
||||
})
|
||||
|
||||
// Invoke the mutation
|
||||
await forgotPassword({email: user.email}, {} as Ctx)
|
||||
|
||||
const tokens = await db.token.findMany({where: {userId: user.id}})
|
||||
const token = tokens[0]
|
||||
if (!user.tokens[0]) throw new Error("Missing user token")
|
||||
if (!token) throw new Error("Missing token")
|
||||
|
||||
// delete's existing tokens
|
||||
expect(tokens.length).toBe(1)
|
||||
|
||||
expect(token.id).not.toBe(user.tokens[0].id)
|
||||
expect(token.type).toBe("RESET_PASSWORD")
|
||||
expect(token.sentTo).toBe(user.email)
|
||||
expect(token.hashedToken).toBe(hash256(generatedToken))
|
||||
expect(token.expiresAt > new Date()).toBe(true)
|
||||
expect(previewEmail).toBeCalled()
|
||||
})
|
||||
})
|
||||
40
apps/next13/src/auth/mutations/forgotPassword.ts
Normal file
40
apps/next13/src/auth/mutations/forgotPassword.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import {generateToken, hash256} from "@blitzjs/auth"
|
||||
import {resolver} from "@blitzjs/rpc"
|
||||
import db from "../../../prisma"
|
||||
import {ForgotPassword} from "../validations"
|
||||
|
||||
const RESET_PASSWORD_TOKEN_EXPIRATION_IN_HOURS = 4
|
||||
//@ts-ignore
|
||||
export default resolver.pipe(resolver.zod(ForgotPassword), async ({email}) => {
|
||||
// 1. Get the user
|
||||
const user = await db.user.findFirst({where: {email: email.toLowerCase()}})
|
||||
|
||||
// 2. Generate the token and expiration date.
|
||||
const token = generateToken()
|
||||
const hashedToken = hash256(token)
|
||||
const expiresAt = new Date()
|
||||
expiresAt.setHours(expiresAt.getHours() + RESET_PASSWORD_TOKEN_EXPIRATION_IN_HOURS)
|
||||
|
||||
// 3. If user with this email was found
|
||||
if (user) {
|
||||
// 4. Delete any existing password reset tokens
|
||||
await db.token.deleteMany({where: {type: "RESET_PASSWORD", userId: user.id}})
|
||||
// 5. Save this new token in the database.
|
||||
await db.token.create({
|
||||
data: {
|
||||
user: {connect: {id: user.id}},
|
||||
type: "RESET_PASSWORD",
|
||||
expiresAt,
|
||||
hashedToken,
|
||||
sentTo: user.email,
|
||||
},
|
||||
})
|
||||
// 6. Send the email
|
||||
} else {
|
||||
// 7. If no user found wait the same time so attackers can't tell the difference
|
||||
await new Promise((resolve) => setTimeout(resolve, 750))
|
||||
}
|
||||
|
||||
// 8. Return the same result whether a password reset email was sent or not
|
||||
return
|
||||
})
|
||||
31
apps/next13/src/auth/mutations/login.ts
Normal file
31
apps/next13/src/auth/mutations/login.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {resolver} from "@blitzjs/rpc"
|
||||
// import {AuthenticationError} from "blitz"
|
||||
import db from "../../../prisma"
|
||||
import {Login} from "../validations"
|
||||
|
||||
// export const authenticateUser = async (rawEmail: string, rawPassword: string) => {
|
||||
// const {email, password} = Login.parse({email: rawEmail, password: rawPassword})
|
||||
// const user = await db.user.findFirst({where: {email}})
|
||||
// if (!user) throw new AuthenticationError()
|
||||
|
||||
// const result = await SecurePassword.verify(user.hashedPassword, password)
|
||||
|
||||
// if (result === SecurePassword.VALID_NEEDS_REHASH) {
|
||||
// // Upgrade hashed password with a more secure hash
|
||||
// const improvedHash = await SecurePassword.hash(password)
|
||||
// await db.user.update({where: {id: user.id}, data: {hashedPassword: improvedHash}})
|
||||
// }
|
||||
|
||||
// const {hashedPassword, ...rest} = user
|
||||
// return rest
|
||||
// }
|
||||
//@ts-ignore
|
||||
export default resolver.pipe(resolver.zod(Login), async ({email, password}, ctx) => {
|
||||
// This throws an error if credentials are invalid
|
||||
// const user = await authenticateUser(email, password)
|
||||
const user = await db.user.findFirst({where: {email}})
|
||||
//@ts-ignore
|
||||
await ctx.session.$create({userId: user.id, role: user.role})
|
||||
console.log("user", user)
|
||||
return user
|
||||
})
|
||||
5
apps/next13/src/auth/mutations/logout.ts
Normal file
5
apps/next13/src/auth/mutations/logout.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import {Ctx} from "blitz"
|
||||
|
||||
export default async function logout(_: any, ctx: Ctx) {
|
||||
return await ctx.session.$revoke()
|
||||
}
|
||||
83
apps/next13/src/auth/mutations/resetPassword.test.ts
Normal file
83
apps/next13/src/auth/mutations/resetPassword.test.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import {vi, describe, it, beforeEach, expect} from "vitest"
|
||||
import resetPassword from "./resetPassword"
|
||||
import db from "db"
|
||||
import {SecurePassword, hash256} from "@blitzjs/auth"
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.$reset()
|
||||
})
|
||||
|
||||
const mockCtx: any = {
|
||||
session: {
|
||||
$create: vi.fn(),
|
||||
},
|
||||
}
|
||||
|
||||
describe("resetPassword mutation", () => {
|
||||
it("works correctly", async () => {
|
||||
expect(true).toBe(true)
|
||||
|
||||
// Create test user
|
||||
const goodToken = "randomPasswordResetToken"
|
||||
const expiredToken = "expiredRandomPasswordResetToken"
|
||||
const future = new Date()
|
||||
future.setHours(future.getHours() + 4)
|
||||
const past = new Date()
|
||||
past.setHours(past.getHours() - 4)
|
||||
|
||||
const user = await db.user.create({
|
||||
data: {
|
||||
email: "user@example.com",
|
||||
tokens: {
|
||||
// Create old token to ensure it's deleted
|
||||
create: [
|
||||
{
|
||||
type: "RESET_PASSWORD",
|
||||
hashedToken: hash256(expiredToken),
|
||||
expiresAt: past,
|
||||
sentTo: "user@example.com",
|
||||
},
|
||||
{
|
||||
type: "RESET_PASSWORD",
|
||||
hashedToken: hash256(goodToken),
|
||||
expiresAt: future,
|
||||
sentTo: "user@example.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
include: {tokens: true},
|
||||
})
|
||||
|
||||
const newPassword = "newPassword"
|
||||
|
||||
// Non-existent token
|
||||
await expect(
|
||||
resetPassword({token: "no-token", password: "", passwordConfirmation: ""}, mockCtx),
|
||||
).rejects.toThrowError()
|
||||
|
||||
// Expired token
|
||||
await expect(
|
||||
resetPassword(
|
||||
{token: expiredToken, password: newPassword, passwordConfirmation: newPassword},
|
||||
mockCtx,
|
||||
),
|
||||
).rejects.toThrowError()
|
||||
|
||||
// Good token
|
||||
await resetPassword(
|
||||
{token: goodToken, password: newPassword, passwordConfirmation: newPassword},
|
||||
mockCtx,
|
||||
)
|
||||
|
||||
// Delete's the token
|
||||
const numberOfTokens = await db.token.count({where: {userId: user.id}})
|
||||
expect(numberOfTokens).toBe(0)
|
||||
|
||||
// Updates user's password
|
||||
const updatedUser = await db.user.findFirst({where: {id: user.id}})
|
||||
expect(await SecurePassword.verify(updatedUser!.hashedPassword, newPassword)).toBe(
|
||||
SecurePassword.VALID,
|
||||
)
|
||||
})
|
||||
})
|
||||
50
apps/next13/src/auth/mutations/resetPassword.ts
Normal file
50
apps/next13/src/auth/mutations/resetPassword.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import {hash256} from "@blitzjs/auth"
|
||||
import db from "../../../prisma"
|
||||
import {ResetPassword} from "../validations"
|
||||
import login from "./login"
|
||||
|
||||
export class ResetPasswordError extends Error {
|
||||
name = "ResetPasswordError"
|
||||
message = "Reset password link is invalid or it has expired."
|
||||
}
|
||||
|
||||
export default async function resetPassword(input: any, ctx: any) {
|
||||
ResetPassword.parse(input)
|
||||
// 1. Try to find this token in the database
|
||||
const hashedToken = hash256(input.token)
|
||||
const possibleToken = await db.token.findFirst({
|
||||
where: {hashedToken, type: "RESET_PASSWORD"},
|
||||
include: {user: true},
|
||||
})
|
||||
|
||||
// 2. If token not found, error
|
||||
if (!possibleToken) {
|
||||
throw new ResetPasswordError()
|
||||
}
|
||||
const savedToken = possibleToken
|
||||
|
||||
// 3. Delete token so it can't be used again
|
||||
await db.token.delete({where: {id: savedToken.id}})
|
||||
|
||||
// 4. If token has expired, error
|
||||
if (savedToken.expiresAt < new Date()) {
|
||||
throw new ResetPasswordError()
|
||||
}
|
||||
|
||||
// 5. Since token is valid, now we can update the user's password
|
||||
// const hashedPassword = await SecurePassword.hash(input.password.trim())
|
||||
const user = await db.user.update({
|
||||
where: {id: savedToken.userId},
|
||||
data: {
|
||||
hashedPassword: input.password,
|
||||
},
|
||||
})
|
||||
|
||||
// 6. Revoke all existing login sessions for this user
|
||||
await db.session.deleteMany({where: {userId: user.id}})
|
||||
|
||||
// 7. Now log the user in with the new credentials
|
||||
await login({email: user.email, password: input.password}, ctx)
|
||||
|
||||
return true
|
||||
}
|
||||
19
apps/next13/src/auth/mutations/signup.ts
Normal file
19
apps/next13/src/auth/mutations/signup.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import db from "../../../prisma"
|
||||
// import {SecurePassword} from "@blitzjs/auth"
|
||||
|
||||
export default async function signup(input: {password: string; email: string}, ctx: any) {
|
||||
const blitzContext = ctx
|
||||
// const hashedPassword = await SecurePassword.hash((input.password as string) || "test-password")
|
||||
const hashedPassword = (input.password as string) || "test-password"
|
||||
const email = (input.email as string) || "test" + Math.random() + "@test.com"
|
||||
const user = await db.user.create({
|
||||
data: {email, hashedPassword},
|
||||
})
|
||||
|
||||
await blitzContext.session.$create({
|
||||
userId: user.id,
|
||||
role: "user",
|
||||
})
|
||||
|
||||
return {userId: blitzContext.session.userId, ...user, email: input.email}
|
||||
}
|
||||
42
apps/next13/src/auth/validations.ts
Normal file
42
apps/next13/src/auth/validations.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import {z} from "zod"
|
||||
|
||||
export const email = z
|
||||
.string()
|
||||
.email()
|
||||
.transform((str) => str.toLowerCase().trim())
|
||||
|
||||
export const password = z
|
||||
.string()
|
||||
.min(10)
|
||||
.max(100)
|
||||
.transform((str) => str.trim())
|
||||
|
||||
export const Signup = z.object({
|
||||
email,
|
||||
password,
|
||||
})
|
||||
|
||||
export const Login = z.object({
|
||||
email,
|
||||
password: z.string(),
|
||||
})
|
||||
|
||||
export const ForgotPassword = z.object({
|
||||
email,
|
||||
})
|
||||
|
||||
export const ResetPassword = z
|
||||
.object({
|
||||
password: password,
|
||||
passwordConfirmation: password,
|
||||
token: z.string(),
|
||||
})
|
||||
.refine((data) => data.password === data.passwordConfirmation, {
|
||||
message: "Passwords don't match",
|
||||
path: ["passwordConfirmation"], // set the path of the error
|
||||
})
|
||||
|
||||
export const ChangePassword = z.object({
|
||||
currentPassword: z.string(),
|
||||
newPassword: password,
|
||||
})
|
||||
13
apps/next13/src/blitz-client.ts
Normal file
13
apps/next13/src/blitz-client.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
"use client"
|
||||
import {AuthClientPlugin} from "@blitzjs/auth"
|
||||
import {setupBlitzClient} from "@blitzjs/next"
|
||||
import {BlitzRpcPlugin} from "@blitzjs/rpc"
|
||||
|
||||
export const {withBlitz, useSession, queryClient, BlitzProvider} = setupBlitzClient({
|
||||
plugins: [
|
||||
AuthClientPlugin({
|
||||
cookiePrefix: "web-cookie-prefix",
|
||||
}),
|
||||
BlitzRpcPlugin({}),
|
||||
],
|
||||
})
|
||||
25
apps/next13/src/blitz-server.ts
Normal file
25
apps/next13/src/blitz-server.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type {BlitzCliConfig} from "blitz"
|
||||
import {setupBlitzServer} from "@blitzjs/next"
|
||||
import {AuthServerPlugin, PrismaStorage} from "@blitzjs/auth"
|
||||
import db from "../prisma"
|
||||
import {simpleRolesIsAuthorized} from "@blitzjs/auth"
|
||||
import {BlitzLogger} from "blitz"
|
||||
import {RpcServerPlugin} from "@blitzjs/rpc"
|
||||
|
||||
const {api, getBlitzContext, useAuthenticatedBlitzContext, invoke} = setupBlitzServer({
|
||||
plugins: [
|
||||
AuthServerPlugin({
|
||||
cookiePrefix: "web-cookie-prefix",
|
||||
storage: PrismaStorage(db),
|
||||
isAuthorized: simpleRolesIsAuthorized,
|
||||
}),
|
||||
RpcServerPlugin({}),
|
||||
],
|
||||
logger: BlitzLogger({}),
|
||||
})
|
||||
|
||||
export {api, getBlitzContext, useAuthenticatedBlitzContext, invoke}
|
||||
|
||||
export const cliConfig: BlitzCliConfig = {
|
||||
customTemplates: "src/templates",
|
||||
}
|
||||
83
apps/next13/src/core/components/Form.tsx
Normal file
83
apps/next13/src/core/components/Form.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import {useState, ReactNode, PropsWithoutRef} from "react"
|
||||
import {FormProvider, useForm, UseFormProps} from "react-hook-form"
|
||||
import {zodResolver} from "@hookform/resolvers/zod"
|
||||
import {z} from "zod"
|
||||
|
||||
export interface FormProps<S extends z.ZodType<any, any>>
|
||||
extends Omit<PropsWithoutRef<JSX.IntrinsicElements["form"]>, "onSubmit"> {
|
||||
/** All your form fields */
|
||||
children?: ReactNode
|
||||
/** Text to display in the submit button */
|
||||
submitText?: string
|
||||
schema?: S
|
||||
onSubmit: (values: z.infer<S>) => Promise<void | OnSubmitResult>
|
||||
initialValues?: UseFormProps<z.infer<S>>["defaultValues"]
|
||||
}
|
||||
|
||||
interface OnSubmitResult {
|
||||
FORM_ERROR?: string
|
||||
[prop: string]: any
|
||||
}
|
||||
|
||||
export const FORM_ERROR = "FORM_ERROR"
|
||||
|
||||
export function Form<S extends z.ZodType<any, any>>({
|
||||
children,
|
||||
submitText,
|
||||
schema,
|
||||
initialValues,
|
||||
onSubmit,
|
||||
...props
|
||||
}: FormProps<S>) {
|
||||
const ctx = useForm<z.infer<S>>({
|
||||
mode: "onBlur",
|
||||
resolver: schema ? zodResolver(schema) : undefined,
|
||||
defaultValues: initialValues,
|
||||
})
|
||||
const [formError, setFormError] = useState<string | null>(null)
|
||||
|
||||
return (
|
||||
<FormProvider {...ctx}>
|
||||
<form
|
||||
onSubmit={ctx.handleSubmit(async (values) => {
|
||||
const result = (await onSubmit(values)) || {}
|
||||
for (const [key, value] of Object.entries(result)) {
|
||||
if (key === FORM_ERROR) {
|
||||
setFormError(value)
|
||||
} else {
|
||||
ctx.setError(key as any, {
|
||||
type: "submit",
|
||||
message: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
})}
|
||||
className="form"
|
||||
{...props}
|
||||
>
|
||||
{/* Form fields supplied as children are rendered here */}
|
||||
{children}
|
||||
|
||||
{formError && (
|
||||
<div role="alert" style={{color: "red"}}>
|
||||
{formError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{submitText && (
|
||||
<button type="submit" disabled={ctx.formState.isSubmitting}>
|
||||
{submitText}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<style global jsx>{`
|
||||
.form > * + * {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
`}</style>
|
||||
</form>
|
||||
</FormProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default Form
|
||||
63
apps/next13/src/core/components/LabeledTextField.tsx
Normal file
63
apps/next13/src/core/components/LabeledTextField.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import {forwardRef, PropsWithoutRef, ComponentPropsWithoutRef} from "react"
|
||||
import {useFormContext} from "react-hook-form"
|
||||
import {ErrorMessage} from "@hookform/error-message"
|
||||
|
||||
export interface LabeledTextFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["input"]> {
|
||||
/** Field name. */
|
||||
name: string
|
||||
/** Field label. */
|
||||
label: string
|
||||
/** Field type. Doesn't include radio buttons and checkboxes */
|
||||
type?: "text" | "password" | "email" | "number"
|
||||
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
|
||||
labelProps?: ComponentPropsWithoutRef<"label">
|
||||
}
|
||||
|
||||
export const LabeledTextField = forwardRef<HTMLInputElement, LabeledTextFieldProps>(
|
||||
({label, outerProps, labelProps, name, ...props}, ref) => {
|
||||
const {
|
||||
register,
|
||||
formState: {isSubmitting, errors},
|
||||
} = useFormContext()
|
||||
|
||||
return (
|
||||
<div {...outerProps}>
|
||||
<label {...labelProps}>
|
||||
{label}
|
||||
<input disabled={isSubmitting} {...register(name)} {...props} />
|
||||
</label>
|
||||
|
||||
<ErrorMessage
|
||||
render={({message}) => (
|
||||
<div role="alert" style={{color: "red"}}>
|
||||
{message}
|
||||
</div>
|
||||
)}
|
||||
errors={errors}
|
||||
name={name}
|
||||
/>
|
||||
|
||||
<style jsx>{`
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
font-size: 1rem;
|
||||
}
|
||||
input {
|
||||
font-size: 1rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
border: 1px solid purple;
|
||||
appearance: none;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
LabeledTextField.displayName = "LabeledTextField"
|
||||
|
||||
export default LabeledTextField
|
||||
8
apps/next13/src/pages/api/auth/session.ts
Normal file
8
apps/next13/src/pages/api/auth/session.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import {api} from "../../../blitz-server"
|
||||
|
||||
export default api((req, res, ctx) => {
|
||||
// console.log("session", ctx.session)
|
||||
//get cookie
|
||||
console.dir("cookie", req.headers.cookie)
|
||||
res.json({session: ctx.session.userId})
|
||||
})
|
||||
4
apps/next13/src/pages/api/rpc/[[...blitz]].ts
Normal file
4
apps/next13/src/pages/api/rpc/[[...blitz]].ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import {rpcHandler} from "@blitzjs/rpc"
|
||||
import {api} from "../../../blitz-server"
|
||||
|
||||
export default api(rpcHandler({onError: console.log}))
|
||||
303
apps/next13/src/styles/Home.module.css
Normal file
303
apps/next13/src/styles/Home.module.css
Normal file
@@ -0,0 +1,303 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
padding: 0rem 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.frost {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(5px);
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.31);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 4rem 0rem 2rem 0rem;
|
||||
text-align: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
max-width: 620px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.body {
|
||||
composes: frost;
|
||||
border-radius: var(--border-radius);
|
||||
display: flex;
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
align-self: center;
|
||||
max-width: var(--screen-width);
|
||||
}
|
||||
|
||||
.instructions {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.globe {
|
||||
position: fixed;
|
||||
width: 350vmin;
|
||||
height: 75vmin;
|
||||
left: 20%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, calc(-50% + 40px));
|
||||
z-index: -1;
|
||||
border-radius: 100%;
|
||||
background-image: radial-gradient(
|
||||
95.63% 95.63% at 95.92% 0%,
|
||||
rgba(255, 255, 255, 0.62) 0%,
|
||||
#8155ff38 60.42%,
|
||||
#002fff5c 169%
|
||||
);
|
||||
filter: blur(8vmin);
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
padding: 2rem 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer span {
|
||||
margin-right: 0.2rem;
|
||||
}
|
||||
|
||||
.code {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.code span {
|
||||
background: rgba(124, 58, 237, 50%);
|
||||
border-radius: 50rem;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
padding: 17px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgb(57, 33, 97);
|
||||
}
|
||||
|
||||
.code pre {
|
||||
background: rgba(124, 58, 237, 12%);
|
||||
border-radius: 4px;
|
||||
padding: 0.7em 1.4em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.code code {
|
||||
font-size: 0.86em;
|
||||
font-weight: bold;
|
||||
color: rgb(124, 58, 237);
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
|
||||
.toastContainer {
|
||||
border: 1px solid #edff;
|
||||
padding: 0 1rem;
|
||||
background: #eeff;
|
||||
color: #62af;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.toastContainer strong {
|
||||
color: rgb(124, 58, 237);
|
||||
}
|
||||
|
||||
.textLink {
|
||||
color: rgb(124, 58, 237);
|
||||
background: linear-gradient(to right, rgba(231, 216, 246, 1), rgba(231, 216, 246, 1)),
|
||||
linear-gradient(to right, rgba(99, 1, 235, 1), rgba(124, 58, 237, 1), rgba(231, 216, 246, 1));
|
||||
background-size: 100% 1px, 0 1px;
|
||||
background-position: 100% 100%, 0 100%;
|
||||
background-repeat: no-repeat;
|
||||
transition: background-size 400ms;
|
||||
}
|
||||
|
||||
.textLink:hover,
|
||||
.textLink:focus,
|
||||
.textLink:active {
|
||||
background-size: 0 1px, 100% 1px;
|
||||
}
|
||||
|
||||
.arrowIcon {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-top: 2px solid;
|
||||
transform: scale(var(--ggs, 1));
|
||||
border-right: 2px solid;
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
top: 6px;
|
||||
color: #b1a5c4;
|
||||
}
|
||||
|
||||
.arrowIcon::after {
|
||||
content: "";
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 2px;
|
||||
background: currentColor;
|
||||
transform: rotate(-45deg);
|
||||
top: 2px;
|
||||
right: -1px;
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.button {
|
||||
background: linear-gradient(to top, rgb(124, 58, 237), rgb(117, 81, 236));
|
||||
border: 1px solid rgb(231, 216, 246);
|
||||
color: white;
|
||||
text-shadow: rgba(0, 0, 0, 0.25) 0px 3px 8px;
|
||||
padding: 0 24px;
|
||||
height: 48px;
|
||||
width: 200px;
|
||||
max-width: 300px;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
border-radius: 0.75rem;
|
||||
border-bottom-left-radius: 0px;
|
||||
font-size: 15px;
|
||||
transition: all 0.3s ease 0s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
color: white;
|
||||
text-shadow: rgb(0 0 0 / 56%) 0px 3px 12px;
|
||||
box-shadow: rgb(80 63 205 / 50%) 0px 1px 40px;
|
||||
}
|
||||
|
||||
.loginButton {
|
||||
composes: button;
|
||||
background: rgb(248 250 252);
|
||||
border: 1px solid rgb(231, 216, 246);
|
||||
color: rgb(30 41 59);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.loginButton:hover {
|
||||
color: rgb(30 41 59);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.card:hover .arrowIcon {
|
||||
color: #7450ec;
|
||||
}
|
||||
|
||||
.linkGrid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
composes: frost;
|
||||
padding: 1rem 0rem;
|
||||
text-align: center;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
border-radius: 10px;
|
||||
border-bottom-left-radius: 0px;
|
||||
transition: color 0.15s ease, border-color 0.15s ease;
|
||||
max-width: 200px;
|
||||
min-width: 200px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.card:hover,
|
||||
.card:focus,
|
||||
.card:active {
|
||||
color: #7450ec;
|
||||
border-color: #7450ec;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.card p {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
flex: 1;
|
||||
padding: 1rem 2rem;
|
||||
}
|
||||
|
||||
.logo svg {
|
||||
height: 100%;
|
||||
width: 200px;
|
||||
fill: #7450ec;
|
||||
}
|
||||
|
||||
/* MOBILE */
|
||||
@media (max-width: 800px) {
|
||||
.linkGrid {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.body {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
25
apps/next13/src/styles/globals.css
Normal file
25
apps/next13/src/styles/globals.css
Normal file
@@ -0,0 +1,25 @@
|
||||
:root {
|
||||
--border-radius: 0.75rem;
|
||||
--screen-width: 90vmin;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell,
|
||||
Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
7
apps/next13/src/users/hooks/useCurrentUser.ts
Normal file
7
apps/next13/src/users/hooks/useCurrentUser.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import {useQuery} from "@blitzjs/rpc"
|
||||
import getCurrentUser from "../../../src/users/queries/getCurrentUser"
|
||||
|
||||
export const useCurrentUser = () => {
|
||||
const [user] = useQuery(getCurrentUser, null)
|
||||
return user
|
||||
}
|
||||
16
apps/next13/src/users/queries/getCurrentUser.ts
Normal file
16
apps/next13/src/users/queries/getCurrentUser.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {Ctx} from "blitz"
|
||||
import db from "../../../prisma"
|
||||
|
||||
export default async function getCurrentUser(input: null, ctx: Ctx) {
|
||||
if (!ctx.session.userId) return null
|
||||
const user = await db.user.findFirst({
|
||||
where: {id: ctx.session.userId},
|
||||
select: {id: true, name: true, email: true},
|
||||
})
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
export const config = {
|
||||
httpMethod: "GET",
|
||||
}
|
||||
@@ -4,17 +4,22 @@
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"incremental": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve"
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
12
apps/next13/types.ts
Normal file
12
apps/next13/types.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import {SimpleRolesIsAuthorized} from "@blitzjs/auth"
|
||||
import {User} from "./prisma"
|
||||
|
||||
declare module "@blitzjs/auth" {
|
||||
export interface Session {
|
||||
isAuthorized: SimpleRolesIsAuthorized
|
||||
PublicData: {
|
||||
userId: User["id"]
|
||||
email: User["email"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,8 @@
|
||||
"@hookform/error-message": "2.0.0",
|
||||
"@hookform/resolvers": "2.9.10",
|
||||
"@prisma/client": "4.6.1",
|
||||
"blitz": "workspace:2.0.0-beta.23",
|
||||
"next": "12.2.5",
|
||||
"blitz": "workspace:2.0.0-beta.24",
|
||||
"next": "13.2.4",
|
||||
"openid-client": "5.2.1",
|
||||
"prisma": "4.6.1",
|
||||
"react": "18.2.0",
|
||||
|
||||
@@ -40,17 +40,12 @@ export const LoginForm = (props: LoginFormProps) => {
|
||||
<LabeledTextField name="email" label="Email" placeholder="Email" />
|
||||
<LabeledTextField name="password" label="Password" placeholder="Password" type="password" />
|
||||
<div>
|
||||
<Link href={Routes.ForgotPasswordPage()}>
|
||||
Forgot your password?
|
||||
</Link>
|
||||
<Link href={Routes.ForgotPasswordPage()}>Forgot your password?</Link>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
<div style={{ marginTop: "1rem" }}>
|
||||
Or{" "}
|
||||
<Link href={Routes.SignupPage()}>
|
||||
Sign Up
|
||||
</Link>
|
||||
Or <Link href={Routes.SignupPage()}>Sign Up</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ import db from "db"
|
||||
import { authenticateUser } from "./login"
|
||||
import { ChangePassword } from "../validations"
|
||||
import { resolver } from "@blitzjs/rpc"
|
||||
import { SecurePassword } from "@blitzjs/auth"
|
||||
import { SecurePassword } from "@blitzjs/auth/secure-password"
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.zod(ChangePassword),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SecurePassword } from "@blitzjs/auth"
|
||||
import { SecurePassword } from "@blitzjs/auth/secure-password"
|
||||
import { resolver } from "@blitzjs/rpc"
|
||||
import { AuthenticationError } from "blitz"
|
||||
import db from "db"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { SecurePassword, hash256 } from "@blitzjs/auth"
|
||||
import { hash256 } from "@blitzjs/auth"
|
||||
import db from "db"
|
||||
import { ResetPassword } from "../validations"
|
||||
import { SecurePassword } from "@blitzjs/auth/secure-password"
|
||||
import login from "./login"
|
||||
|
||||
export class ResetPasswordError extends Error {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import db from "db"
|
||||
import { SecurePassword } from "@blitzjs/auth"
|
||||
import { SecurePassword } from "@blitzjs/auth/secure-password"
|
||||
import { Role } from "types"
|
||||
|
||||
export default async function signup(input, ctx) {
|
||||
|
||||
@@ -38,7 +38,7 @@ const UserInfo = () => {
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<Link href={Routes.SignupPage()}className="button small">
|
||||
<Link href={Routes.SignupPage()} className="button small">
|
||||
<strong>Sign Up</strong>
|
||||
</Link>
|
||||
<Link href={Routes.LoginPage()} className="button small">
|
||||
@@ -55,7 +55,7 @@ const Home: BlitzPage = () => {
|
||||
<div className="container">
|
||||
<main>
|
||||
<div className="logo">
|
||||
<Image src={`${logo.src}`} alt="blitzjs" width="256px" height="118px" layout="fixed" />
|
||||
<Image src={`${logo.src}`} alt="blitzjs" width={256} height={118} layout="fixed" />
|
||||
</div>
|
||||
<p>
|
||||
<strong>Congrats!</strong> Your app is ready, including user sign-up and log-in.
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
// @ts-check
|
||||
|
||||
const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
||||
enabled: process.env.ANALYZE === "true",
|
||||
})
|
||||
const { withNextAuthAdapter } = require("@blitzjs/auth/next-auth")
|
||||
const { withBlitz } = require("@blitzjs/next")
|
||||
|
||||
/**
|
||||
@@ -12,4 +8,4 @@ const config = {
|
||||
reactStrictMode: true,
|
||||
}
|
||||
|
||||
module.exports = withBlitz(withBundleAnalyzer(config))
|
||||
module.exports = withBlitz(withNextAuthAdapter(config))
|
||||
|
||||
@@ -31,8 +31,9 @@
|
||||
"@hookform/error-message": "2.0.0",
|
||||
"@hookform/resolvers": "2.9.10",
|
||||
"@prisma/client": "4.6.1",
|
||||
"blitz": "workspace:2.0.0-beta.23",
|
||||
"next": "12.2.5",
|
||||
"blitz": "workspace:2.0.0-beta.24",
|
||||
"next": "13.2.4",
|
||||
"next-auth": "4.18.7",
|
||||
"prisma": "4.6.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
|
||||
@@ -40,17 +40,12 @@ export const LoginForm = (props: LoginFormProps) => {
|
||||
<LabeledTextField name="email" label="Email" placeholder="Email" />
|
||||
<LabeledTextField name="password" label="Password" placeholder="Password" type="password" />
|
||||
<div>
|
||||
<Link href={Routes.ForgotPasswordPage()}>
|
||||
Forgot your password?
|
||||
</Link>
|
||||
<Link href={Routes.ForgotPasswordPage()}>Forgot your password?</Link>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
<div style={{ marginTop: "1rem" }}>
|
||||
Or{" "}
|
||||
<Link href={Routes.SignupPage()}>
|
||||
Sign Up
|
||||
</Link>
|
||||
Or <Link href={Routes.SignupPage()}>Sign Up</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ import db from "db"
|
||||
import { authenticateUser } from "./login"
|
||||
import { ChangePassword } from "../validations"
|
||||
import { resolver } from "@blitzjs/rpc"
|
||||
import { SecurePassword } from "@blitzjs/auth"
|
||||
import { SecurePassword } from "@blitzjs/auth/secure-password"
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.zod(ChangePassword),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SecurePassword } from "@blitzjs/auth"
|
||||
import { SecurePassword } from "@blitzjs/auth/secure-password"
|
||||
import { resolver } from "@blitzjs/rpc"
|
||||
import { AuthenticationError } from "blitz"
|
||||
import db from "db"
|
||||
@@ -24,9 +24,10 @@ export const authenticateUser = async (rawEmail: string, rawPassword: string) =>
|
||||
|
||||
export default resolver.pipe(resolver.zod(Login), async ({ email, password }, ctx) => {
|
||||
// This throws an error if credentials are invalid
|
||||
const user = await authenticateUser(email, password)
|
||||
// const user = await authenticateUser(email, password)
|
||||
const user = await db.user.findFirst({ where: { email } })
|
||||
|
||||
await ctx.session.$create({ userId: user.id, role: user.role as Role })
|
||||
await ctx.session.$create({ userId: user!.id, role: user!.role as Role })
|
||||
|
||||
return user
|
||||
})
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { vi, describe, it, beforeEach, expect } from "vitest"
|
||||
import resetPassword from "./resetPassword"
|
||||
import db from "db"
|
||||
import { SecurePassword, hash256 } from "@blitzjs/auth"
|
||||
import { hash256 } from "@blitzjs/auth"
|
||||
import { SecurePassword } from "@blitzjs/auth/secure-password"
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.$reset()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { SecurePassword, hash256 } from "@blitzjs/auth"
|
||||
import { hash256 } from "@blitzjs/auth"
|
||||
import { SecurePassword } from "@blitzjs/auth/secure-password"
|
||||
import db from "db"
|
||||
import { ResetPassword } from "../validations"
|
||||
import login from "./login"
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import db from "db"
|
||||
import { SecurePassword } from "@blitzjs/auth"
|
||||
import { SecurePassword } from "@blitzjs/auth/secure-password"
|
||||
import { Role } from "types"
|
||||
|
||||
export default async function signup(input, ctx) {
|
||||
const blitzContext = ctx
|
||||
|
||||
const hashedPassword = await SecurePassword.hash((input.password as string) || "test-password")
|
||||
// const hashedPassword = await SecurePassword.hash((input.password as string) || "test-password")
|
||||
const hashedPassword = (input.password as string) || "test-password"
|
||||
const email = (input.email as string) || "test" + Math.random() + "@test.com"
|
||||
const user = await db.user.create({
|
||||
data: { email, hashedPassword, role: "user" },
|
||||
|
||||
@@ -5,6 +5,20 @@ import db from "db"
|
||||
import { simpleRolesIsAuthorized } from "@blitzjs/auth"
|
||||
import { BlitzLogger } from "blitz"
|
||||
|
||||
export const cliConfig: BlitzCliConfig = {
|
||||
customTemplates: "src/templates",
|
||||
codegen: {
|
||||
fieldTypeMap: {
|
||||
string: {
|
||||
component: "LabeledTextField",
|
||||
inputType: "text",
|
||||
zodType: "date",
|
||||
prismaType: "String",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const { gSSP, gSP, api } = setupBlitzServer({
|
||||
plugins: [
|
||||
AuthServerPlugin({
|
||||
@@ -17,7 +31,3 @@ const { gSSP, gSP, api } = setupBlitzServer({
|
||||
})
|
||||
|
||||
export { gSSP, gSP, api }
|
||||
|
||||
export const cliConfig: BlitzCliConfig = {
|
||||
customTemplates: "src/templates",
|
||||
}
|
||||
|
||||
63
apps/toolkit-app/src/core/components/LabelSelectField.tsx
Normal file
63
apps/toolkit-app/src/core/components/LabelSelectField.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { ComponentPropsWithoutRef, forwardRef, PropsWithoutRef } from "react"
|
||||
import { useFormContext } from "react-hook-form"
|
||||
import { ErrorMessage } from "@hookform/error-message"
|
||||
|
||||
export interface LabeledSelectFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["select"]> {
|
||||
/** Field name. */
|
||||
name: string
|
||||
/** Field label. */
|
||||
label: string
|
||||
/** Field type. Doesn't include radio buttons and checkboxes */
|
||||
options: any[]
|
||||
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
|
||||
labelProps?: ComponentPropsWithoutRef<"label">
|
||||
}
|
||||
|
||||
export const LabeledSelectField = forwardRef<HTMLSelectElement, LabeledSelectFieldProps>(
|
||||
({ label, outerProps, labelProps, name, options, ...props }, ref) => {
|
||||
const {
|
||||
register,
|
||||
formState: { isSubmitting, errors },
|
||||
} = useFormContext()
|
||||
return (
|
||||
<div {...outerProps}>
|
||||
<label {...labelProps}>
|
||||
{label}
|
||||
<select {...register(name)} disabled={isSubmitting} {...props}>
|
||||
{options &&
|
||||
options.map((value) => (
|
||||
<option value={value.id} key={value.id}>
|
||||
{value[name]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<ErrorMessage
|
||||
render={({ message }) => (
|
||||
<div role="alert" style={{ color: "red" }}>
|
||||
{message}
|
||||
</div>
|
||||
)}
|
||||
errors={errors}
|
||||
name={name}
|
||||
/>
|
||||
<style jsx>{`
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
font-size: 1rem;
|
||||
}
|
||||
select {
|
||||
font-size: 1rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
border: 1px solid purple;
|
||||
appearance: none;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -22,7 +22,6 @@ export const BlitzCustomPlugin = createClientPlugin<CustomPluginOptions, {}>(
|
||||
middleware: {
|
||||
beforeHttpRequest: (req) => {
|
||||
//make changes to the request options before RPC call
|
||||
req.headers = { ...req.headers, ...{ customHeader: "customHeaderValue" } }
|
||||
return req
|
||||
},
|
||||
beforeHttpResponse: (res) => {
|
||||
|
||||
43
apps/toolkit-app/src/pages/api/auth/[...nextauth].ts
Normal file
43
apps/toolkit-app/src/pages/api/auth/[...nextauth].ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { api } from "src/blitz-server"
|
||||
import GithubProvider from "next-auth/providers/github"
|
||||
import { NextAuthAdapter, BlitzNextAuthOptions } from "@blitzjs/auth/next-auth"
|
||||
import db, { User } from "db"
|
||||
import { Role } from "types"
|
||||
|
||||
// Has to be defined separately for `profile` to be correctly typed below
|
||||
const providers = [
|
||||
GithubProvider({
|
||||
clientId: process.env.GITHUB_CLIENT_ID as string,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
|
||||
}),
|
||||
]
|
||||
|
||||
export default api(
|
||||
NextAuthAdapter({
|
||||
successRedirectUrl: "/",
|
||||
errorRedirectUrl: "/error",
|
||||
providers,
|
||||
callback: async (user, account, profile, session) => {
|
||||
console.log("USER SIDE PROFILE_DATA", { user, account, profile })
|
||||
let newUser: User
|
||||
try {
|
||||
newUser = await db.user.findFirstOrThrow({ where: { name: { equals: user.name } } })
|
||||
} catch (e) {
|
||||
newUser = await db.user.create({
|
||||
data: {
|
||||
email: user.email as string,
|
||||
name: user.name as string,
|
||||
role: "USER",
|
||||
},
|
||||
})
|
||||
}
|
||||
const publicData = {
|
||||
userId: newUser.id,
|
||||
role: newUser.role as Role,
|
||||
source: "github",
|
||||
}
|
||||
await session.$create(publicData)
|
||||
return { redirectUrl: "/" }
|
||||
},
|
||||
})
|
||||
)
|
||||
@@ -11,11 +11,15 @@ const LoginPage: BlitzPage = () => {
|
||||
<LoginForm
|
||||
onSuccess={(_user) => {
|
||||
const next = router.query.next ? decodeURIComponent(router.query.next as string) : "/"
|
||||
return router.push(next)
|
||||
// return router.push(next)
|
||||
}}
|
||||
/>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
LoginPage.authenticate = {
|
||||
redirectTo: "/",
|
||||
}
|
||||
|
||||
export default LoginPage
|
||||
|
||||
@@ -4,7 +4,8 @@ import Layout from "src/core/layouts/Layout"
|
||||
import { useCurrentUser } from "src/users/hooks/useCurrentUser"
|
||||
import logout from "src/auth/mutations/logout"
|
||||
import { useMutation } from "@blitzjs/rpc"
|
||||
import { Routes, BlitzPage } from "@blitzjs/next"
|
||||
import { BlitzPage } from "@blitzjs/next"
|
||||
import { Routes } from ".blitz"
|
||||
import styles from "src/styles/Home.module.css"
|
||||
|
||||
/*
|
||||
@@ -43,6 +44,11 @@ const UserInfo = () => {
|
||||
<Link href={Routes.LoginPage()} className={styles.loginButton}>
|
||||
<strong>Login</strong>
|
||||
</Link>
|
||||
<Link href="/api/auth/github/login" passHref>
|
||||
<a className="button small">
|
||||
<strong>Sign in with GitHub</strong>
|
||||
</a>
|
||||
</Link>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
31
apps/toolkit-app/src/users/queries/getUsers.ts
Normal file
31
apps/toolkit-app/src/users/queries/getUsers.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { paginate } from "blitz"
|
||||
import { resolver } from "@blitzjs/rpc"
|
||||
import db, { Prisma } from "db"
|
||||
|
||||
interface GetProjectsInput
|
||||
extends Pick<Prisma.UserFindManyArgs, "where" | "orderBy" | "skip" | "take"> {}
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.authorize(),
|
||||
async ({ where, orderBy, skip = 0, take = 100 }: GetProjectsInput) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const {
|
||||
items: users,
|
||||
hasMore,
|
||||
nextPage,
|
||||
count,
|
||||
} = await paginate({
|
||||
skip,
|
||||
take,
|
||||
count: () => db.user.count({ where }),
|
||||
query: (paginateArgs) => db.user.findMany({ ...paginateArgs, where, orderBy }),
|
||||
})
|
||||
|
||||
return {
|
||||
users,
|
||||
nextPage,
|
||||
hasMore,
|
||||
count,
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -82,12 +82,13 @@ export const mockRouter: NextRouter = {
|
||||
query: {},
|
||||
isReady: true,
|
||||
isLocaleDomain: false,
|
||||
forward: vi.fn(),
|
||||
prefetch: vi.fn(),
|
||||
isPreview: false,
|
||||
push: vi.fn(),
|
||||
replace: vi.fn(),
|
||||
reload: vi.fn(),
|
||||
back: vi.fn(),
|
||||
prefetch: vi.fn(),
|
||||
beforePopState: vi.fn(),
|
||||
events: {
|
||||
on: vi.fn(),
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"blitz": "workspace:*",
|
||||
"jest": "29.3.0",
|
||||
"jest-environment-jsdom": "29.3.0",
|
||||
"next": "12.2.5",
|
||||
"next": "13.2.4",
|
||||
"passport-mock-strategy": "2.0.0",
|
||||
"passport-twitter": "1.0.4",
|
||||
"prisma": "4.6.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {api} from "src/blitz-server"
|
||||
import db from "db"
|
||||
import {SecurePassword} from "@blitzjs/auth"
|
||||
import {SecurePassword} from "@blitzjs/auth/secure-password"
|
||||
|
||||
export const authenticateUser = async (email: string, password: string) => {
|
||||
const user = await db.user.findFirst({where: {email}})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {api} from "src/blitz-server"
|
||||
import db from "db"
|
||||
import {SecurePassword} from "@blitzjs/auth"
|
||||
import {SecurePassword} from "@blitzjs/auth/secure-password"
|
||||
|
||||
export default api(async (req, res, ctx) => {
|
||||
const blitzContext = ctx
|
||||
|
||||
BIN
docs/.github/screenshot.png
vendored
BIN
docs/.github/screenshot.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 868 KiB |
2
docs/.gitignore
vendored
2
docs/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
.next
|
||||
node_modules
|
||||
21
docs/LICENSE
21
docs/LICENSE
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Shu Ding
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,23 +0,0 @@
|
||||
# Nextra Docs Template
|
||||
|
||||
This is a template for creating documentation with [Nextra](https://nextra.site).
|
||||
|
||||
[**Live Demo →**](https://nextra-docs-template.vercel.app)
|
||||
|
||||
[](https://nextra-docs-template.vercel.app)
|
||||
|
||||
## Quick Start
|
||||
|
||||
Click the button to clone this repository and deploy it on Vercel:
|
||||
|
||||
[](https://vercel.com/new/clone?s=https%3A%2F%2Fgithub.com%2Fshuding%2Fnextra-docs-template&showOptionalTeamCreation=false)
|
||||
|
||||
## Local Development
|
||||
|
||||
First, run `pnpm i` to install the dependencies.
|
||||
|
||||
Then, run `pnpm dev` to start the development server and visit localhost:3000.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License.
|
||||
@@ -1,14 +0,0 @@
|
||||
const remarkShikiTwoslash = require('remark-shiki-twoslash')
|
||||
|
||||
const withNextra = require('nextra')({
|
||||
theme: 'nextra-theme-docs',
|
||||
themeConfig: './theme.config.tsx',
|
||||
// Not working yet
|
||||
remarkPlugins: [
|
||||
[
|
||||
remarkShikiTwoslash
|
||||
]
|
||||
]
|
||||
})
|
||||
|
||||
module.exports = withNextra()
|
||||
@@ -1,43 +0,0 @@
|
||||
{
|
||||
"name": "nextra-docs-template",
|
||||
"version": "0.0.1",
|
||||
"description": "Nextra docs template",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/shuding/nextra-docs-template.git"
|
||||
},
|
||||
"author": "Shu Ding <g@shud.in>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/shuding/nextra-docs-template/issues"
|
||||
},
|
||||
"homepage": "https://github.com/shuding/nextra-docs-template#readme",
|
||||
"dependencies": {
|
||||
"@blitzjs/auth": "workspace:2.0.0-beta.23",
|
||||
"@blitzjs/next": "workspace:2.0.0-beta.23",
|
||||
"@blitzjs/rpc": "workspace:2.0.0-beta.23",
|
||||
"@mdx-js/loader": "2.3.0",
|
||||
"@mdx-js/react": "2.3.0",
|
||||
"@next/mdx": "13.2.3",
|
||||
"@octokit/rest": "19.0.7",
|
||||
"blitz": "workspace:2.0.0-beta.23",
|
||||
"next": "^13.0.6",
|
||||
"nextra": "latest",
|
||||
"nextra-theme-docs": "latest",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"remark-shiki-twoslash": "3.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.11.10",
|
||||
"autoprefixer": "10.4.13",
|
||||
"postcss": "8.4.21",
|
||||
"tailwindcss": "3.2.7",
|
||||
"typescript": "^4.9.3"
|
||||
}
|
||||
}
|
||||
2172
docs/pnpm-lock.yaml
generated
2172
docs/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
.counter {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 2px 6px;
|
||||
margin: 12px 0 0;
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// Example from https://beta.reactjs.org/learn
|
||||
|
||||
import { useState } from 'react'
|
||||
import styles from './counters.module.css'
|
||||
|
||||
function MyButton() {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
function handleClick() {
|
||||
setCount(count + 1)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={handleClick} className={styles.counter}>
|
||||
Clicked {count} times
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function MyApp() {
|
||||
return <MyButton />
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import "../styles/nextra.css";
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"index": {
|
||||
"title": "Home",
|
||||
"theme": {
|
||||
"layout": "raw"
|
||||
}
|
||||
},
|
||||
"docs": {
|
||||
"title": "Documentation",
|
||||
"type": "page"
|
||||
},
|
||||
"tutorial": {
|
||||
"title": "Tutorial",
|
||||
"type": "page"
|
||||
},
|
||||
"showcase": {
|
||||
"title": "Showcase",
|
||||
"type": "page"
|
||||
},
|
||||
"releases": {
|
||||
"title": "Releases ↗",
|
||||
"type": "page",
|
||||
"href": "https://github.com/blitz-js/blitz/releases",
|
||||
"newWindow": true
|
||||
},
|
||||
"swag": {
|
||||
"title": "Swag ↗",
|
||||
"type": "page",
|
||||
"href": "https://store.blitzjs.com/",
|
||||
"newWindow": true
|
||||
},
|
||||
"deploy": {
|
||||
"title": "Deploy With Flightcontrol ↗",
|
||||
"type": "page",
|
||||
"href": "https://flightcontrol.dev/?ref=blitzjs",
|
||||
"newWindow": true
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"introduction": "Introduction",
|
||||
"community": "Community",
|
||||
"basics": "Basics",
|
||||
"framweworks": "Framework Adapters",
|
||||
"blitz-auth": "Blitz Auth",
|
||||
"blitz-rpc": "Blitz RPC",
|
||||
"blitz-recipies": "Blitz Recipies",
|
||||
"blitz-generator": "Blitz Generator",
|
||||
"backend-architecture": "Backend Architecture",
|
||||
"databse": "Database",
|
||||
"cli" : "CLI",
|
||||
"migration":"Migration Guides",
|
||||
"docs": {
|
||||
"title": "Documentation",
|
||||
"type": "page"
|
||||
},
|
||||
"tutorial": {
|
||||
"title": "Tutorial",
|
||||
"type": "page"
|
||||
},
|
||||
"showcase": {
|
||||
"title": "Showcase",
|
||||
"type": "page"
|
||||
},
|
||||
"releases": {
|
||||
"title": "Releases ↗",
|
||||
"type": "page",
|
||||
"href" : "https://github.com/blitz-js/blitz/releases",
|
||||
"newWindow": true
|
||||
},
|
||||
"swag": {
|
||||
"title": "Swag ↗",
|
||||
"type": "page",
|
||||
"href" : "https://store.blitzjs.com/",
|
||||
"newWindow": true
|
||||
},
|
||||
"deploy": {
|
||||
"title": "Deploy With Flightcontrol ↗",
|
||||
"type": "page",
|
||||
"href": "https://flightcontrol.dev/?ref=blitzjs",
|
||||
"newWindow": true
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"how-the-community-operates": "How the Community Operates",
|
||||
"manifesto": "Manifesto",
|
||||
"history": "History",
|
||||
"contributing": "How to Contribute",
|
||||
"maintainers": "Being a Maintainers",
|
||||
"code-of-conduct": "Code of Conduct",
|
||||
"translations": "Doc Translations"
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
# The Blitz Community Code of Conduct
|
||||
|
||||
The Blitz core members take this CoC very serious. All members,
|
||||
contributors and volunteers in this community are required to act
|
||||
according to the following Code of Conduct to keep Blitz a positive,
|
||||
growing project and community and help us provide and ensure a safe
|
||||
environment for everyone.
|
||||
|
||||
## When Something Happens
|
||||
|
||||
If you see a Code of Conduct violation, follow these steps:
|
||||
|
||||
1. Let the person know that what they did is not appropriate and ask them
|
||||
to stop and/or edit their message(s).
|
||||
2. That person should immediately stop the behavior and correct the issue.
|
||||
3. If this doesn’t happen, or if you’re uncomfortable speaking up, contact
|
||||
Brandon Bayer ([Twitter](https://twitter.com/flybayer) |
|
||||
[Email](mailto:b@bayer.ws)).
|
||||
|
||||
When reporting, please include any relevant details, links, screenshots,
|
||||
context, or other information that may be used to better understand and
|
||||
resolve the situation.
|
||||
|
||||
The core members will prioritize the well-being and comfort of the
|
||||
recipients of the violation over the comfort of the violator.
|
||||
|
||||
## What We Believe and How We Act
|
||||
|
||||
- We are committed to providing a friendly, safe and welcoming environment
|
||||
for everyone, regardless of age, body size, culture, ethnicity, gender
|
||||
expression, gender identity, level of experience, nationality, personal
|
||||
ability or disability, physical appearance, physical or mental
|
||||
difference, race, religion, set of skills, sexual orientation,
|
||||
socio-economic status, and subculture. We welcome people regardless of
|
||||
these or other attributes.
|
||||
- We are better together. We are more alike than different.
|
||||
- Our community is based on mutual respect, tolerance, and encouragement.
|
||||
- We believe that a diverse community where people treat each other with
|
||||
respect is stronger, more vibrant and has more potential contributors
|
||||
and more sources for ideas. We aim for more diversity.
|
||||
- We are kind, welcoming and courteous to everyone.
|
||||
- We’re respectful of others, their positions, their skills, their
|
||||
commitments and their efforts.
|
||||
- We’re attentive in our communications, whether in person or online, and
|
||||
we’re tactful when approaching differing views.
|
||||
- We are aware that language shapes reality. Thus, we use inclusive,
|
||||
gender-neutral language in the documents we provide and when we talk to
|
||||
people. When referring to a group of people, we aim to use
|
||||
gender-neutral terms like “team”, “folks”, “everyone”. (For details, we
|
||||
recommend
|
||||
[this post](https://modelviewculture.com/pieces/gendered-language-feature-or-bug-in-software-documentation)).
|
||||
- We respect that people have differences of opinion and criticize
|
||||
constructively.
|
||||
- We value people over code.
|
||||
|
||||
## Don'ts
|
||||
|
||||
- Don’t discriminate against anyone.
|
||||
- Sexism and racism of any kind (including sexist and racist “jokes”),
|
||||
demeaning or insulting behaviour and harassment are seen as direct
|
||||
violations to this Code of Conduct. Harassment includes offensive verbal
|
||||
comments related to age, body size, culture, ethnicity, gender
|
||||
expression, gender identity, level of experience, nationality, personal
|
||||
ability or disability, physical appearance, physical or mental
|
||||
difference, race, religion, set of skills, sexual orientation,
|
||||
socio-economic status, and subculture. Harassment also includes sexual
|
||||
images in public spaces, deliberate intimidation, stalking, following,
|
||||
harassing photography or recording, inappropriate physical contact, and
|
||||
unwelcome sexual attention.
|
||||
- On Discord and other online or offline communications channels, don't
|
||||
use overtly sexual nicknames or other nicknames that might detract from
|
||||
a friendly, safe and welcoming environment for all.
|
||||
- Don’t be mean or rude.
|
||||
- Respect that some individuals and cultures consider the casual use of
|
||||
profanity offensive and off-putting.
|
||||
- Unwelcome / non-consensual sexual advances over Discord or any other
|
||||
channels related with this community are not okay.
|
||||
- Derailing, tone arguments and otherwise playing on people’s desires to
|
||||
be nice are not welcome, especially in discussions about violations to
|
||||
this Code of Conduct.
|
||||
- Please avoid unstructured critique.
|
||||
- Likewise any spamming, trolling, flaming, baiting or other
|
||||
attention-stealing behavior is not welcome.
|
||||
- Sponsors of Blitz are also subject to this Code of Conduct. In
|
||||
particular, sponsors are required to not use sexualized images,
|
||||
activities, or other material which is not according to this Code of
|
||||
Conduct.
|
||||
|
||||
## Consequences for Violations to this Code of Conduct
|
||||
|
||||
If a participant engages in any behavior violating this Code of Conduct,
|
||||
the core members of this community will take any action they deem
|
||||
appropriate, starting with a gentle warning and then escalating as needed
|
||||
to expulsion from the community, exclusion from any interaction and loss
|
||||
of all rights in this community.
|
||||
|
||||
## Decisions About Consequences of Violations
|
||||
|
||||
Decisions about consequences of violations of this Code of Conduct are
|
||||
made by this community’s core members and may not be discussed with the
|
||||
person responsible for the violation.
|
||||
|
||||
## For Questions or Feedback
|
||||
|
||||
If you have any questions or feedback on this Code of Conduct, we’re happy
|
||||
to hear from you.
|
||||
|
||||
## Thanks for Inspiration
|
||||
|
||||
- [Hoodie](https://github.com/hoodiehq/hoodie)
|
||||
- [WeAllJS](https://wealljs.org/code-of-conduct)
|
||||
@@ -1,268 +0,0 @@
|
||||
# How to Contribute
|
||||
|
||||
import { Callout, Tab, Tabs, Steps } from 'nextra-theme-docs'
|
||||
|
||||
👋 We're so excited you're interested in helping with Blitz! We happy to
|
||||
help you get started, even if you don't have any previous open-source
|
||||
experience
|
||||
|
||||
## First Things First
|
||||
|
||||
1. New to open source? take a look at
|
||||
[How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github)
|
||||
2. Familiarize yourself with the
|
||||
[Blitz Code of Conduct](./code-of-conduct)
|
||||
3. Learn [how the community operates](./how-the-community-operates)
|
||||
|
||||
## What to Work On?
|
||||
|
||||
Issues with the label
|
||||
[`status/ready-to-work-on`](https://github.com/blitz-js/blitz/labels/status%2Fready-to-work-on)
|
||||
are the best place to start.
|
||||
|
||||
We also label issues as `good first issue` and `good second issue` when
|
||||
appropriate.
|
||||
|
||||
If you find one that looks interesting and no one else is already working
|
||||
on it, comment in the issue that you are going to work on it. But only
|
||||
claim an issue if you can start work on it within a couple days.
|
||||
|
||||
Please ask as many questions as you need, either directly in the issue or
|
||||
in Discord. We're happy to help!
|
||||
|
||||
The Blitzjs.com website and documentation repo also has issues with
|
||||
[`ready to work on | help wanted`](https://github.com/blitz-js/blitzjs.com/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22ready+to+work+on+%7C+help+wanted%22).
|
||||
|
||||
### Things that are ALWAYS welcome
|
||||
|
||||
- Adding tests
|
||||
- Improved documentation
|
||||
- Improved error messages
|
||||
- Improved logging (i.e. more clear, more beautiful)
|
||||
- Performance or security improvements
|
||||
- Educational content like blogs, videos, courses
|
||||
|
||||
If there's some other way you'd like to contribute, ask us about it in
|
||||
Discord!
|
||||
|
||||
After you contribute in any way, please add yourself as a contributor via
|
||||
the
|
||||
[@all-contributors bot](https://allcontributors.org/docs/en/bot/usage)!
|
||||
|
||||
## Our Codebase is a Garden
|
||||
|
||||
The Blitz codebase is like a community garden. There's a lot of beautiful
|
||||
plants and vegetables, but it won't take long until you find some weeds!
|
||||
When you find weeds, please remove them. Minor refactoring is always
|
||||
encouraged. If you'd like to do some major refactoring, it's best to first
|
||||
either open an issue or check with us in Discord. Most likely we'll agree
|
||||
with you.
|
||||
|
||||
## What to Expect After Submitting a PR
|
||||
|
||||
A Blitz maintainer will review your PR, usually within a couple days.
|
||||
|
||||
If your PR changes user facing code, make sure you also made a PR to the
|
||||
docs repo, otherwise this will block your PR from being merged.
|
||||
|
||||
You also need to add tests to cover the changes you made.
|
||||
|
||||
Once all the requirements are met and the maintainer is happy with your
|
||||
code, they will merge it to the canary branch. It will then be included in
|
||||
the next Blitz release.
|
||||
|
||||
Lastly, you will be added the all-contributors list as an official Blitz
|
||||
contributor. **Congratulations!!**
|
||||
|
||||
## Project Management
|
||||
|
||||
We use a
|
||||
[GitHub Project Board](https://github.com/blitz-js/blitz/projects/4) to
|
||||
track all issues and PRs.
|
||||
|
||||
## Commit Access
|
||||
|
||||
We give liberal commit access to the Blitz repo to anyone who is a
|
||||
half-way regular contributor. This allows you to push branches directly to
|
||||
the Blitz repo without using a fork.
|
||||
|
||||
We'll often give someone access if we notice they are regularly
|
||||
contributing. But you're also welcome to ask for access if you are
|
||||
regularly helping but haven't been given access yet.
|
||||
|
||||
In the main Blitz repo, code reviews by code owners are required to merge
|
||||
PRs.
|
||||
|
||||
But in the docs repo, anyone can merge PRs once someone else has approved
|
||||
the PR.
|
||||
|
||||
## Development Setup
|
||||
|
||||
Make sure you're using Node 14 — Next uses `node-sass` which does not work
|
||||
with newer Node versions.
|
||||
|
||||
<Steps>
|
||||
|
||||
### Fork [the blitz repo](https://github.com/blitz-js/blitz)
|
||||
|
||||
### Clone your forked repo
|
||||
|
||||
```sh
|
||||
# replace USERNAME below with your GitHub username
|
||||
git clone git@github.com:USERNAME/blitz.git
|
||||
cd blitz
|
||||
```
|
||||
|
||||
### (Optional, macOS only) Install required packages for
|
||||
`sodium-native`
|
||||
|
||||
If you spot any issues related to `sodium-native`, run the following
|
||||
command to fix it:
|
||||
|
||||
```sh
|
||||
brew install autoconf automake
|
||||
```
|
||||
|
||||
### Install dependencies
|
||||
|
||||
```sh
|
||||
pnpm i
|
||||
```
|
||||
|
||||
### Start the package server. This **must be running** for any package
|
||||
development or example development
|
||||
|
||||
```sh
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Blitz.js Tests
|
||||
|
||||
#### Unit Tests
|
||||
|
||||
Most Blitz packages in `packages/` have vitest unit tests.
|
||||
|
||||
- Run tests in a single package by running `pnpm test` inside that package
|
||||
folder (like `packages/blitz-next`)
|
||||
- Run all unit tests from the repo root by running `pnpm test`. (Make sure
|
||||
you have ran `pnpm build` or `pnpm dev` prior to running this). Note:
|
||||
this will run all tests, including integration tests.
|
||||
|
||||
#### Integration Tests
|
||||
|
||||
Blitz integration tests are inside the root `integration-tests/` folder.
|
||||
|
||||
Make sure you have `chromedriver` installed for your Chrome version. You
|
||||
can install it with
|
||||
|
||||
- `brew install --cask chromedriver` on Mac OS X
|
||||
- `chocolatey install chromedriver` on Windows
|
||||
- Or manually download the version that matches your installed chrome
|
||||
version (if there's no match, download a version under it, but not
|
||||
above) from the
|
||||
[chromedriver repo](https://chromedriver.storage.googleapis.com/index.html)
|
||||
and add the binary to `<blitz-repo>/node_modules/.bin`
|
||||
|
||||
You can run all tests (both unit tests and integration tests) by running
|
||||
`pnpm test` from the repo root.
|
||||
|
||||
You can manually run the integration app by running `blitz dev` inside the
|
||||
integration test folder, like `integration-tests/auth/`.
|
||||
|
||||
### Testing Development Version of Blitz Inside an App
|
||||
|
||||
<Callout>
|
||||
|
||||
Currently, to test the local dev version of Blitz, you can test an app
|
||||
inside the `blitz/apps/` folder. In there, the blitz dependency will
|
||||
automatically use the local dev version. We use it for development
|
||||
testing. You must also make sure you are running `pnpm dev` in the `blitz`
|
||||
folder at the same time.
|
||||
|
||||
</Callout>
|
||||
|
||||
#### Link the Blitz CLI (Optional)
|
||||
|
||||
The following will link the development CLI as a local binary so you can
|
||||
use it anywhere for testing.
|
||||
|
||||
```sh
|
||||
pnpm link ./packages/blitz
|
||||
```
|
||||
|
||||
## Doing A Releas
|
||||
|
||||
We use the [changesets](https://github.com/changesets/changesets) package
|
||||
& a forked version of their GitHub action which can be found here:
|
||||
[GitHub - blitz-js/changesets-action](https://github.com/blitz-js/changesets-action).
|
||||
Every user facing PR should have a changeset. When that PR gets approved
|
||||
and merged to the `main` branch the "release" PR with the name
|
||||
`Version packages (beta)` is automatically created or updated. This PR
|
||||
updates each internal package's changelog & each blitz package's version
|
||||
by incrementing it in the `package.json` files.
|
||||
|
||||
Once the auto generated PR accumulates enough changesets, It's time to do
|
||||
a release by merging this PR into the `main` branch. To trigger the
|
||||
release action, you must squash and merge the PR without waiting for the
|
||||
requirements to be met, bypassing the branch protections rule. This is
|
||||
because the CI doesn't run on PR's generated by a github action. Once you
|
||||
merge this PR, it will generate the release action and publish on npm &
|
||||
create a release on GitHub.
|
||||
|
||||
The auto generated release on GitHub, still requires some manual cleaning.
|
||||
It's recommended to remove some "fluff" like:
|
||||
|
||||
```
|
||||
Updated dependencies [3b213a3]
|
||||
@blitzjs/rpc@2.0.0-beta.18
|
||||
```
|
||||
|
||||
It's also important to note, that since we haven't done a stable release
|
||||
yet that we're in a pre-release mode set with the `changesets` package. So
|
||||
with that being said, you will need to uncheck the `Set as a pre-release`
|
||||
checkbox while editing the release on Github, and selecting
|
||||
`Set as the latest release` checkbox.
|
||||
|
||||
While on the category of being in prerelease mode with the `changesets`
|
||||
package, when published to npm we also want to ensure that each package
|
||||
gets tagged with `latest`. This requires manual work and the correct npm
|
||||
permissions. _This is mostly for our internal team._ For each package run:
|
||||
|
||||
```shell
|
||||
npm dist-tag add blitz@2.0.0-beta.18 latest
|
||||
npm dist-tag add @blitzs/auth@2.0.0-beta.18 latest
|
||||
npm dist-tag add @blitzs/rpc@2.0.0-beta.18 latest
|
||||
npm dist-tag add @blitzs/next@2.0.0-beta.18 latest
|
||||
```
|
||||
|
||||
For more information about `changesets` pre-release mode, you can read
|
||||
their documentation here:
|
||||
[changesets/prereleases.md at main · changesets/changesets · GitHub](https://github.com/changesets/changesets/blob/main/docs/prereleases.md)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you run into issues that should be documented here, please submit a PR!
|
||||
❤️
|
||||
|
||||
#### Git errors
|
||||
|
||||
Are you unable to commit? Do you get errors like `command not found` or
|
||||
`stdin is not a tty`? That's probably a Husky error! Try checking their
|
||||
[troubleshoot guide](https://typicode.github.io/husky/#/?id=troubleshoot).
|
||||
|
||||
#### Preconstruct errors
|
||||
|
||||
If you run into symlink and EPERM errors when trying to run Preconstruct
|
||||
on Windows, you may need to enable
|
||||
[Windows Developer Mode](https://www.howtogeek.com/292914/what-is-developer-mode-in-windows-10/)
|
||||
so that Preconstruct can create symlinks.
|
||||
|
||||
#### Missing files in Windows
|
||||
|
||||
If you have errors about missing files even after you run `pnpm build`,
|
||||
try cloning again your repository with the configuration `core.symlinks`
|
||||
set to `true` like this: `git clone -c core.symlinks=true <URL>`.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user