Merge pull request #981 from wasp-lang/vince-improve-auth-integrations

Big Auth / Integrations Docs Update
This commit is contained in:
vincanger 2023-02-06 14:41:41 -05:00 committed by GitHub
commit 05ae9dde30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 345 additions and 173 deletions

View File

@ -3,7 +3,7 @@ Waspello
**Waspello** is a trello clone app made with Wasp.
This app is deployed at [https://waspello.netlify.app/](https://waspello.netlify.app/).
This app is deployed at [https://waspello-demo.netlify.app/login](https://waspello-demo.netlify.app/login).
The backend is hosted on Heroku - [@matijaSos](https://github.com/matijaSos)

View File

@ -2,6 +2,8 @@
Welcome to the Waspleau example! This is a small Wasp project that will allow you to setup an easy Dashboard that pulls in data via [Jobs](https://wasp-lang.dev/docs/language/features#jobs) and stores them in the database.
The deployed version of this example can be found at https://waspleau.netlify.app/
## Step 1
Clone this repo

View File

@ -2,9 +2,30 @@
title: Examples
---
Todo App:
- source code: https://github.com/wasp-lang/wasp/tree/release/examples/tutorials/TodoApp .
import useBaseUrl from '@docusaurus/useBaseUrl';
Real World App (clone of Medium):
- source code: https://github.com/wasp-lang/wasp/tree/release/examples/realworld .
- hosted at: https://wasp-rwa.netlify.app/ .
We have a constantly growing collection of fully-functioning example apps, which you can use to learn more about Wasp's features.
The full list of examples can be found [here](https://github.com/wasp-lang/wasp/tree/release/examples/). Here is a few of them:
## Todo App
- **Features**: Auth ([username/password](language/features#authentication--authorization)), [Queries & Actions](language/features#queries-and-actions-aka-operations), [Entities](language/features#entity), [Routes](language/features#route)
- JS source code: [GitHub](https://github.com/wasp-lang/wasp/tree/release/examples/tutorials/TodoApp)
- TS source code: [GitHub](https://github.com/wasp-lang/wasp/tree/release/examples/todo-typescript)
- in-browser dev environment: [GitPod](https://gitpod.io/#https://github.com/wasp-lang/gitpod-template)
## Waspello (Trello Clone)
- **Features**: Auth ([Google](language/features#social-login-providers-oauth-20), [username/password](language/features#authentication--authorization)), [Optimistic Updates](language/features#the-useaction-hook), [Tailwind CSS integration](integrations/css-frameworks)
- Source code: [GitHub](https://github.com/wasp-lang/wasp/tree/main/examples/waspello)
- Hosted at [https://waspello-demo.netlify.app](https://waspello-demo.netlify.app/login)
<p align='center'>
<img src={useBaseUrl('img/wespello-new.png')} width='75%'/>
</p>
## Waspleau (Realtime Statistics Dashboard)
- **Features**: Cron [Jobs](language/features#jobs), [Server Setup](language/features#server-configuration)
- Source code: [GitHub](https://github.com/wasp-lang/wasp/tree/main/examples/waspleau)
- Hosted at [https://waspleau.netlify.app/](https://waspleau.netlify.app/)
<p align='center'>
<img src={useBaseUrl('img/waspleau.png')} width='75%'/>
</p>

View File

@ -1,14 +1,65 @@
---
title: GitHub Integrations
title: GitHub
---
import useBaseUrl from '@docusaurus/useBaseUrl';
# GitHub Integrations
# GitHub
To implement GitHub Auth, you'll need to add the Auth object with the following configuration to your `main.wasp` file:
```c title="main.wasp"
app Example {
wasp: {
version: "^0.8.0"
},
title: "Example",
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
gitHub: {}
},
onAuthFailedRedirectTo: "/login"
},
}
//...
entity User {=psl
id Int @id @default(autoincrement())
username String @unique
password String
externalAuthAssociations SocialLogin[]
psl=}
entity SocialLogin {=psl
id Int @id @default(autoincrement())
provider String
providerId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
createdAt DateTime @default(now())
@@unique([provider, providerId, userId])
psl=}
```
For more info on the specific fields, check out this [Auth](../language/features#social-login-providers-oauth-20) section of the docs.
You'll also need to add these environment variables to your `.env.server` file at the root of your project:
```bash title=".env.server"
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
```
We will cover how to get these values in the next section.
## GitHub Auth
To use GitHub as an authentication method (covered [here](/docs/language/features#github)), you'll first need to create a GitHub OAuth App and provide Wasp with your client key and secret. Here is how to do so:
To use GitHub as an authentication method (covered [here](/docs/language/features#social-login-providers-oauth-20)), you'll first need to create a GitHub OAuth App and provide Wasp with your client key and secret. Here is how to do so:
1. Log into your GitHub account and navigate to: https://github.com/settings/developers
2. Select "New OAuth App"
@ -23,4 +74,7 @@ To use GitHub as an authentication method (covered [here](/docs/language/feature
- Once you know on which URL your API server will be deployed, you can create a new app with that URL instead.
- For example: `https://someotherhost.com/auth/login/github`
4. Hit "Register application"
5. Copy your Client ID and Client secret, and expose them as environment variables named `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` wherever your app is running
5. Copy your Client ID and Client secret, and paste them into your environment variables named `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET`in your `.env.server` file.
6. Now when youre user logs in with GitHub, you can access the logged in user on the client via the `useAuth()` hook, and on the server via the `context.user` object as described [here](/docs/language/features#accessing-the-currently-logged-in-user)!

View File

@ -1,14 +1,64 @@
---
title: Google Integrations
title: Google
---
import useBaseUrl from '@docusaurus/useBaseUrl';
# Google Integrations
# Google
To implement Google Auth, you'll need to add the Auth object with the following configuration to your `main.wasp` file:
```c title="main.wasp"
app Example {
wasp: {
version: "^0.8.0"
},
title: "Example",
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
google: {}
},
onAuthFailedRedirectTo: "/login"
},
}
//...
entity User {=psl
id Int @id @default(autoincrement())
username String @unique
password String
externalAuthAssociations SocialLogin[]
psl=}
entity SocialLogin {=psl
id Int @id @default(autoincrement())
provider String
providerId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
createdAt DateTime @default(now())
@@unique([provider, providerId, userId])
psl=}
```
For more info on the specific fields, check out this [Auth](../language/features#social-login-providers-oauth-20) section of the docs.
You'll also need to add these environment variables to your `.env.server` file at the root of your project:
```bash title=".env.server"
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
```
We will cover how to get these values in the next section.
## Google Auth
To use Google as an authentication method (covered [here](/docs/language/features#google)), you'll first need to create a Google project and provide Wasp with your client key and secret. Here is how to do so:
To use Google as an authentication method (covered [here](/docs/language/features#social-login-providers-oauth-20)), you'll first need to create a Google project and provide Wasp with your client key and secret. Here is how to do so:
1. Create a Google Cloud Platform account if you do not already have one: https://cloud.google.com/
2. Create and configure a new Google project here: https://console.cloud.google.com/home/dashboard
@ -64,4 +114,5 @@ To use Google as an authentication method (covered [here](/docs/language/feature
![Google Console Screenshot 14](../../static/img/integrations-google-14.jpg)
5. Copy your Client ID and Client secret, and expose them as environment variables named `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` wherever your app is running
5. Copy your Client ID and Client secret, and expose them as environment variables named `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` in your `.env.server` file.
6. Now when your user logs in with Google, you can access the logged in user on the client via the `useAuth()` hook, and on the server via the `context.user` object as described [here](/docs/language/features#accessing-the-currently-logged-in-user)!

View File

@ -2,6 +2,9 @@
title: Features
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
## App
There can be only one declaration of `app` type per Wasp project.
@ -809,7 +812,7 @@ In the future, we will add support for picking any version you like, but we have
## Authentication & Authorization
Wasp provides authentication and authorization support out-of-the-box. Enabling it for your app is optional and can be done by configuring `auth` field of the `app` declaration:
Wasp provides authentication and authorization support out-of-the-box. Enabling it for your app is optional and can be done by configuring the `auth` field of the `app` declaration:
```c
app MyApp {
@ -836,13 +839,13 @@ app MyApp {
Entity which represents the user (sometimes also referred to as *Principal*).
#### `externalAuthEntity: entity` (optional)
Entity which associates a user with some external authentication provider. We currently offer support for Google and GitHub. See the sections on [Social Login Providers](#social-login-providers-oauth-20---google-github) for more info.
Entity which associates a user with some external authentication provider. We currently offer support for Google and GitHub. See the sections on [Social Login Providers](#social-login-providers-oauth-20) for more info.
#### `methods: dict` (required)
List of authentication methods that Wasp app supports. Currently supported methods are:
* `usernameAndPassword`: Provides support for authentication with a username and password. See [here](#username-and-password) for more.
* `google`: Provides support for login via Google accounts. See [here](#google) for more.
* `gitHub`: Provides support for login via GitHub accounts. See [here](#github) for more.
* `google`: Provides support for login via Google accounts. See [here](#social-login-providers-oauth-20) for more.
* `gitHub`: Provides support for login via GitHub accounts. See [here](#social-login-providers-oauth-20) for more.
#### `onAuthFailedRedirectTo: String` (required)
Path where an unauthenticated user will be redirected to if they try to access a private page (which is declared by setting `authRequired: true` for a specific page).
@ -1129,30 +1132,82 @@ import AuthError from '@wasp/core/AuthError.js'
}
```
### Social Login Providers (OAuth 2.0) - Google, GitHub
Wasp currently supports the following Social Login providers (with more to come) :
- [GitHub](features#github)
- [Google](features#google)
## Social Login Providers (OAuth 2.0)
Wasp allows you to easily add social login providers to your app.
The following is a quick example of how your `.wasp` file might look like when implementing social login. Make sure to read the specific sections for furter requirements (env variables) and override options:
The following is a list of links to guides that will help you get started with the currently supported providers:
- [GitHub](/docs/integrations/github)
- [Google](/docs/integrations/google)
```c title="main.wasp"
app MyApp {
title: "My app",
//...
When using Social Login Providers, Wasp gives you the following options:
- Default settings to get you started quickly
- UI Helpers to make it easy to add social login buttons and actions
- Override settings to customize the behavior of the providers
### Default Settings
<Tabs>
<TabItem value="google" label="Google" default>
```c
auth: {
// both userEntity and externalAuthEntity are required
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
google: {},
gitHub: {}
},
}
}
```
<p>Add <code>google: &#123;&#125;</code> to your <code>auth.methods</code> dictionary to use it with default settings</p>
<p>By default, Wasp expects you to set two environment variables in order to use Google authentication:</p>
<ul>
<li><code>GOOGLE_CLIENT_ID</code></li>
<li><code>GOOGLE_CLIENT_SECRET</code></li>
</ul>
<p>These can be obtained in your Google Cloud Console project dashboard. See <a href="/docs/integrations/google">here</a> for a detailed guide.</p>
</TabItem>
<TabItem value="gitHub" label="GitHub">
```c
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
gitHub: {},
},
}
```
<p>Add <code>gitHub: &#123;&#125;</code> to your <code>auth.methods</code> dictionary to use it with default settings</p>
<p>By default, Wasp expects you to set two environment variables in order to use GitHub authentication:</p>
<ul>
<li><code>GITHUB_CLIENT_ID</code></li>
<li><code>GITHUB_CLIENT_SECRET</code></li>
</ul>
<p>These can be obtained in your GitHub project dashboard. See <a href="/docs/integrations/github">here</a> for a detailed guide.</p>
</TabItem>
</Tabs>
When a user signs in for the first time, Wasp assigns generated values to the `username` and `password` fields of the `userEntity` by default (e.g. `username: nice-blue-horse-14357`), so make sure to include these in your `userEntity` declaration even if you're only using a Social Login provider. If you'd like to change this behavior, these values can be overridden as described below.
:::tip Overriding Defaults
It is also posslbe to [override the default](features#overrides-for-social-login-providers) login behaviors that Wasp provides for you. This allows you to create custom setups, such as allowing Users to define a username rather than the default random username assigned by Wasp on initial Login.
:::
#### `externalAuthEntity`
Anytime an authentication method is used that relies on an external authorization provider, for example, Google, we require an `externalAuthEntity` specified in `auth`, in addition to the `userEntity`, that contains the following configuration:
```c {4,14}
...
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
...
// Wasp requires the userEntity to have at least the following fields
entity User {=psl
id Int @id @default(autoincrement())
username String @unique
@ -1160,7 +1215,6 @@ entity User {=psl
externalAuthAssociations SocialLogin[]
psl=}
// Different social login providers can use the same externalAuthEntity
entity SocialLogin {=psl
id Int @id @default(autoincrement())
provider String
@ -1171,50 +1225,54 @@ entity SocialLogin {=psl
@@unique([provider, providerId, userId])
psl=}
```
Be sure to include an `externalAuthEntity` in your `auth` declaration, as [described here](features#externalauthentity). Note that the same `externalAuthEntity` can be used across different social login providers (e.g., both GitHub and Google can use the same entity).
:::info
Wasp assigns generated values to the `username` and `password` fields of the `userEntity` by default, so make sure to include it them your `userEntity` declaration even if you're only using a Social Login provider.
If you require custom configuration setup or user entity field assignment, you can [override the defaults](features#google-overrides).
:::note
the same `externalAuthEntity` can be used across different social login providers (e.g., both GitHub and Google can use the same entity).
:::
### UI helpers
Wasp provides sign-in buttons, logos and URLs for your login page:
#### Google
```jsx
...
import { SignInButton as GoogleSignInButton, signInUrl as googleSignInUrl, logoUrl as googleLogoUrl } from '@wasp/auth/helpers/Google'
import { SignInButton as GitHubSignInButton, signInUrl as gitHubSignInUrl, logoUrl as gitHubLogoUrl } from '@wasp/auth/helpers/GitHub'
`google` authentication makes it possible to use Google's OAuth 2.0 service to sign Google users into your app. To enable it, add `google: {}` to your `auth.methods` dictionary to use it with default settings:
const Login = () => {
return (
<>
...
```c {6}
auth: {
// both userEntity and externalAuthEntity are required
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
google: {},
},
}
<GoogleSignInButton/>
<GitHubSignInButton/>
{/* or */}
<a href={googleSignInUrl}>Sign in with Google</a>
<a href={gitHubSignInUrl}>Sign in with GitHub</a>
</>
)
}
export default Login
```
##### Google Default settings
- Configuration:
- By default, Wasp expects you to set two environment variables in order to use Google authentication:
- `GOOGLE_CLIENT_ID`
- `GOOGLE_CLIENT_SECRET`
- These can be obtained in your Google Cloud Console project dashboard. See [here](/docs/integrations/google#google-auth) for more.
- Sign in:
- When a user signs in for the first time, Wasp will create a new User account and link it to their Google account for future logins. The `username` will default to a random dictionary phrase that does not exist in the database, like "nice-blue-horse-27160".
:::note Changing Random Username
If you would like to allow the user to select their own username, or some other sign up flow, you could add a boolean property to your User entity which indicates if the account setup is complete. You can then check this user's property on the client with the [`useAuth()`](#useauth) hook and redirect them when appropriate -- e.g. check on homepage if `user.isAuthSetup === false`, redirect them to `EditUserDetailsPage`.
If you need more customization than what the buttons provide, you can create your own custom components using the `signInUrl`s.
Alternatively, you could add a `displayName` property to your User entity and assign it using the details of their Google account, as described in **Overrides** below
:::
- Here is a link to the default implementations: https://github.com/wasp-lang/wasp/blob/release/waspc/data/Generator/templates/server/src/routes/auth/passport/google/defaults.js . These can be overriden as explained below.
### Overrides
##### Google Overrides
If you require modifications to the above, you can add one or more of the following to your `auth.methods.google` dictionary:
When a user signs in for the first time, Wasp will create a new User account and link it to the chosen Auth Provider account for future logins. The `username` will default to a random dictionary phrase that does not exist in the database, such as `nice-blue-horse-27160`.
If you would like to allow the user to select their own username, or some other sign up flow, you could add a boolean property to your `User` entity indicating the account setup is incomplete. You can then check this user's property on the client with the [`useAuth()`](#useauth) hook and redirect them when appropriate
- e.g. check on homepage if `user.isAuthSetup === false`, redirect them to `EditUserDetailsPage` where they can edit the `username` property.
Alternatively, you could add a `displayName` property to your User entity and assign it using the details of their provider account. Below is an example of how to do this by using:
- the `getUserFieldsFn` function to configure the user's `username` or `displayName` from their provider account
We also show you how to customize the configuration of the Provider's settings using:
- the `configFn` function
```c title=main.wasp {9,10,13,14,26}
app Example {
//...
```js
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
@ -1222,26 +1280,73 @@ If you require modifications to the above, you can add one or more of the follow
google: {
configFn: import { config } from "@server/auth/google.js",
getUserFieldsFn: import { getUserFields } from "@server/auth/google.js"
},
gitHub: {
configFn: import { config } from "@server/auth/github.js",
getUserFieldsFn: import { getUserFields } from "@server/auth/github.js"
}
},
...
//...
}
}
entity User {=psl
id Int @id @default(autoincrement())
username String @unique
password String
displayName String?
externalAuthAssociations SocialLogin[]
psl=}
//...
```
- `configFn`: This function should return an object with the following shape:
```js title=src/server/auth/google.js
export function config() {
// ...
return {
clientID, // look up from env or elsewhere,
clientSecret, // look up from env or elsewhere,
scope: ['profile'] // must include at least 'profile' for Google
}
}
#### `configFn`
This function should return an object with the following shape:
<Tabs>
<TabItem value="google" label="Google" default>
```js title=src/server/auth/google.js
export function config() {
// ...
```
- `getUserFieldsFn`: This function should return the user fields to use when creating a new user upon their first Google login. The context contains a User entity for DB access, and the args are what the OAuth provider responds with. Here is how you could generate a username based on the Google display name. In your model, you could choose to add more attributes and set additional information.
return {
clientID, // look up from env or elsewhere,
clientSecret, // look up from env or elsewhere,
scope: ['profile'] // must include at least 'profile' for Google
}
}
// ...
```
<p>Here is a link to the <a href="https://github.com/wasp-lang/wasp/blob/release/waspc/data/Generator/templates/server/src/routes/auth/passport/google/defaults.js">default implementations</a> as a reference</p>
</TabItem>
<TabItem value="github" label="GitHub">
```js title=src/server/auth/github.js
export function config() {
// ...
return {
clientID, // look up from env or elsewhere,
clientSecret, // look up from env or elsewhere,
scope: [] // default is an empty array for GitHub
}
}
// ...
```
<p>Here is a link to the <a href="https://github.com/wasp-lang/wasp/blob/release/waspc/data/Generator/templates/server/src/routes/auth/passport/github/defaults.js">default implementations</a> as a reference</p>
</TabItem>
</Tabs>
#### `getUserFieldsFn`
This function should return the user fields to use when creating a new user upon their first time logging in with a Social Auth Provider. The context contains a User entity for DB access, and the args are what the OAuth provider responds with. Here is how you could generate a username based on the Google display name. In your model, you could choose to add more attributes and set additional information.
```js title=src/server/auth/google.js
import { generateAvailableUsername } from '@wasp/core/auth.js'
@ -1252,92 +1357,22 @@ If you require modifications to the above, you can add one or more of the follow
return { username }
}
```
- `generateAvailableUsername` takes an array of Strings and an optional separator and generates a string ending with a random number that is not yet in the database. For example, the above could produce something like "Jim.Smith.3984" for a Google user Jim Smith.
##### Google UI helpers
Or you could set the optional `displayName` property on the `User` entity instead:
```js title=src/server/auth/google.js
import { generateAvailableDictionaryUsername, generateAvailableUsername } from '@wasp/core/auth.js'
To use the Google sign-in button, logo or URL on your login page, do either of the following:
// ...
```js
...
import { SignInButton as GoogleSignInButton, signInUrl as googleSignInUrl, logoUrl as googleLogoUrl } from '@wasp/auth/helpers/Google'
const Login = () => {
return (
<>
...
<GoogleSignInButton/>
{/* or */}
<a href={googleSignInUrl}>Sign in with Google</a>
</>
)
}
export default Login
```
If you need more customization than what the buttons provide, you can create your own custom component using the `googleSignInUrl`.
#### GitHub
`gitHub` authentication makes it possible to use GitHub's OAuth 2.0 service to sign GitHub users into your app. To enable it, add `gitHub: {}` to your `auth.methods` dictionary to use it with default settings:
```c {7}
//...
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
gitHub: {}
},
//...
export async function getUserFields(_context, args) {
const username = await generateAvailableDictionaryUsername()
const displayName = await generateAvailableUsername(args.profile.displayName.split(' '), { separator: '.' })
return { username, displayName }
}
```
```
- `generateAvailableUsername` takes an array of Strings and an optional separator and generates a string ending with a random number that is not yet in the database. For example, the above could produce something like "Jim.Smith.3984" for a Google user Jim Smith.
- `generateAvailableDictionaryUsername` generates a random dictionary phrase that is not yet in the database. For example, `nice-blue-horse-27160`.
This method requires also requires that `externalAuthEntity` be specified in `auth` as [described here](features#externalauthentity). NOTE: The same `externalAuthEntity` can be used across different social login providers (e.g., both GitHub and Google can use the same entity).
If you require custom configuration setup or user entity field assignment, you can override the defaults. Please check out that section for [Google overrides](features#google-overrides), as the information is the same.
##### GitHub Default settings
- Configuration:
- By default, Wasp expects you to set two environment variables in order to use GitHub authentication:
- `GITHUB_CLIENT_ID`
- `GITHUB_CLIENT_SECRET`
- These can be obtained in your GitHub project dashboard. See [here](/docs/integrations/github#github-auth) for more.
- The same sign-in logic applies as for Google. Please see [that section](features#google-default-settings) for more.
- Here is a link to the default implementations: https://github.com/wasp-lang/wasp/blob/release/waspc/data/Generator/templates/server/src/routes/auth/passport/github/defaults.js
NOTE: The same UI helpers apply as for Google. Please [see here](features#google-ui-helpers) for more.
#### `externalAuthEntity`
Anytime an authentication method is used that relies on an external authorization provider, for example, Google, we require an `externalAuthEntity` specified in `auth`, in addition to the `userEntity`, that contains at least the following highlighted fields:
```c {4,11,16-19,21}
...
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
...
entity User {=psl
id Int @id @default(autoincrement())
username String @unique
password String
externalAuthAssociations SocialLogin[]
psl=}
entity SocialLogin {=psl
id Int @id @default(autoincrement())
provider String
providerId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
createdAt DateTime @default(now())
@@unique([provider, providerId, userId])
psl=}
```
## Client configuration

View File

@ -40,6 +40,27 @@ module.exports = {
},
]
},
'examples',
{
type: 'category',
label: 'Guides',
collapsed: false,
items: [
{
type: 'category',
label: 'Auth Providers',
collapsed: false,
items: [
'integrations/github',
'integrations/google',
]
},
'integrations/css-frameworks',
'deploying',
],
},
{
type: 'category',
label: 'Language',
@ -51,22 +72,10 @@ module.exports = {
]
},
'cli',
'deploying',
'examples',
{
type: 'category',
label: 'Integrations',
collapsed: false,
items: [
'integrations/github',
'integrations/google',
'integrations/css-frameworks'
]
},
{
type: 'category',
label: 'Other',
collapsed: false,
collapsed: true,
items: [
'contributing',
'vision',

BIN
web/static/img/waspleau.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB