Merge branch 'main' into wasp-ai
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
import logout from '@wasp/auth/logout.js'
|
||||
import logout from '@wasp/auth/logout'
|
||||
|
||||
import './TopNavbar.css'
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import './Main.css';
|
||||
import React, { useEffect, FormEventHandler, FormEvent } from 'react';
|
||||
import logout from '@wasp/auth/logout.js';
|
||||
import useAuth from '@wasp/auth/useAuth.js';
|
||||
import logout from '@wasp/auth/logout';
|
||||
import useAuth from '@wasp/auth/useAuth';
|
||||
import { useQuery } from '@wasp/queries'; // Wasp uses a thin wrapper around react-query
|
||||
import getTasks from '@wasp/queries/getTasks';
|
||||
import createTask from '@wasp/actions/createTask';
|
||||
|
@ -12,6 +12,11 @@ The backend is hosted on Fly.io at https://waspello.fly.dev.
|
||||
### Database
|
||||
Wasp needs the Postgres database running. Check out the docs for details on [how to setup PostgreSQL](https://wasp-lang.dev/docs/language/features#postgresql)
|
||||
|
||||
You can use `wasp start db` to start a PostgreSQL locally using Docker.
|
||||
|
||||
### Env variables
|
||||
Copy `env.server` to `.env.server` and fill in the values.
|
||||
|
||||
### Running
|
||||
|
||||
`wasp start`
|
||||
|
2
examples/waspello/env.server
Normal file
@ -0,0 +1,2 @@
|
||||
GOOGLE_CLIENT_ID=
|
||||
GOOGLE_CLIENT_SECRET=
|
@ -1,6 +1,6 @@
|
||||
app waspello {
|
||||
wasp: {
|
||||
version: "^0.8.0"
|
||||
version: "^0.11.0"
|
||||
},
|
||||
|
||||
title: "Waspello",
|
||||
@ -20,10 +20,10 @@ app waspello {
|
||||
},
|
||||
|
||||
dependencies: [
|
||||
("react-feather", "2.0.9"),
|
||||
("react-feather", "2.0.10"),
|
||||
("classnames", "2.3.1"),
|
||||
("react-tiny-popover", "7.1.0"),
|
||||
("react-beautiful-dnd", "13.1.0")
|
||||
("react-beautiful-dnd", "13.1.1")
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Link, useHistory } from 'react-router-dom'
|
||||
|
||||
import login from '@wasp/auth/login.js'
|
||||
import login from '@wasp/auth/login'
|
||||
|
||||
import EmailAndPassForm from './Auth/EmailAndPassForm'
|
||||
import GoogleAuthButton from './Auth/GoogleAuthButton'
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
import logout from '@wasp/auth/logout.js'
|
||||
import logout from '@wasp/auth/logout'
|
||||
|
||||
import logo from './waspello-logo-navbar.svg'
|
||||
import './Navbar.css'
|
||||
|
@ -1,9 +1,8 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Link, useHistory } from 'react-router-dom'
|
||||
|
||||
import SignupForm from '@wasp/auth/forms/Signup'
|
||||
import signup from '@wasp/auth/signup.js'
|
||||
import login from '@wasp/auth/login.js'
|
||||
import signup from '@wasp/auth/signup'
|
||||
import login from '@wasp/auth/login'
|
||||
|
||||
import EmailAndPassForm from './Auth/EmailAndPassForm'
|
||||
import GoogleAuthButton from './Auth/GoogleAuthButton'
|
||||
|
@ -1,27 +1,24 @@
|
||||
# Waspleau
|
||||
|
||||
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.
|
||||
Welcome to the Waspleau example! This is a small Wasp project that tracks status of wasp-lang/wasp repo via a nice looking dashboard.
|
||||
It 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/
|
||||
This example project can serve as a good starting point for building your own dashboard with Wasp, that regularly pulls in external data by using Jobs Wasp feature.
|
||||
|
||||
## Step 1
|
||||
The deployed version of this example can be found at https://waspleau.netlify.app/ .
|
||||
|
||||
Clone this repo
|
||||
## Running in development
|
||||
|
||||
## Step 2
|
||||
|
||||
Update ext/workers with whatever you want to track, add them to main.wasp as a `job`, and optionally import and use them in `serverSetup.js` (or other server-side code).
|
||||
|
||||
## Step 3 (with PostgreSQL running)
|
||||
|
||||
`NODE_ENV=development DATABASE_URL="postgresql://postgres@localhost/waspleau-dev" wasp db migrate-dev`
|
||||
|
||||
## Step 4
|
||||
|
||||
`NODE_ENV=development DATABASE_URL="postgresql://postgres@localhost/waspleau-dev" wasp start`
|
||||
1. `wasp start db` to run the development database.
|
||||
2. `wasp start` to run the app.
|
||||
3. You might need to do `wasp db migrate-dev` if running the app for the first time or after db changes -> keep an eye out for warning from Wasp.
|
||||
|
||||
This will start your background workers as Wasp Jobs and present a dashboard UI that will auto-refresh every minute.
|
||||
|
||||
## Modifying the example to track the data of your choice
|
||||
|
||||
Update ext/workers with whatever you want to track, add them to main.wasp as a `job`, and optionally import and use them in `serverSetup.js` (or other server-side code).
|
||||
|
||||
Note: As you develop your own workers, keep in mind each time you save a file in the project it will automatically reload everything, including restarting your server, which may re-submit or terminate running `job`s.
|
||||
|
||||
## Heroku Deployment Note
|
||||
|
@ -12,7 +12,7 @@ app waspleau {
|
||||
db: { system: PostgreSQL },
|
||||
|
||||
dependencies: [
|
||||
("axios", "^0.27.2")
|
||||
("axios", "^1.4.0")
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ We now offer an interactive way to create a new project. You can run `wasp new`
|
||||
- We changed `LoginForm` and `SignupForm` to use a named export instead of a default export, this means you must use them like this:
|
||||
- `import { LoginForm } from '@wasp/auth/forms/Login'`
|
||||
- `import { SignupForm } from '@wasp/auth/Signup'`
|
||||
- We renamed `useAuth.js` to `useAuth.ts` and you should import it like this: `import useAuth from '@wasp/auth/useAuth'` (without the `.js` extension)
|
||||
- We changed some of the extensions on Wasp-provided imports from `.js` to `.ts`. For example `useAuth.js` is now `useAuth.ts`. Therefore, you should import them like this: `import useAuth from '@wasp/auth/useAuth'` (without the `.js` extension). Some other affected imports are `@wasp/auth/login.js`, `@wasp/auth/logout.js`, and similar.
|
||||
- We changed the type arguments for `useQuery` and `useAction` hooks. They now take two arguments (the `Error` type argument was removed):
|
||||
- `Input` - This type argument specifies the type for the **request's payload**.
|
||||
- `Output` - This type argument specifies the type for the **resposne's payload**.
|
||||
@ -115,7 +115,7 @@ export const TaskInfo = () => {
|
||||
```
|
||||
The same feature is available for Actions.
|
||||
|
||||
### Payloads compatible with Superjson
|
||||
### Payloads compatible with Superjson
|
||||
Client and the server can now communicate with richer payloads.
|
||||
|
||||
Return a Superjson-compatible object from your Operation:
|
||||
|
@ -9,10 +9,12 @@ import {
|
||||
type ErrorMessage,
|
||||
} from './types'
|
||||
import { LoginSignupForm } from './internal/common/LoginSignupForm'
|
||||
import { MessageError, MessageSuccess } from './internal/Message'
|
||||
{=# isEmailAuthEnabled =}
|
||||
import { ForgotPasswordForm } from './internal/email/ForgotPasswordForm'
|
||||
import { ResetPasswordForm } from './internal/email/ResetPasswordForm'
|
||||
import { VerifyEmailForm } from './internal/email/VerifyEmailForm'
|
||||
import { MessageError, MessageSuccess } from './internal/Message'
|
||||
{=/ isEmailAuthEnabled =}
|
||||
|
||||
const logoStyle = {
|
||||
height: '3rem'
|
||||
|
@ -1,8 +1,8 @@
|
||||
{{={= =}=}}
|
||||
|
||||
import config from '../../config.js'
|
||||
import { SocialButton } from '../forms/SocialButton'
|
||||
import * as SocialIcons from '../forms/SocialIcons'
|
||||
import { SocialButton } from '../forms/internal/social/SocialButton'
|
||||
import * as SocialIcons from '../forms/internal/social/SocialIcons'
|
||||
|
||||
export const signInUrl = `${config.apiUrl}{= signInPath =}`
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
{{={= =}=}}
|
||||
import React, { useEffect } from 'react'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
|
||||
import config from '../../config.js'
|
||||
@ -14,12 +14,25 @@ import { initSession } from '../helpers/user'
|
||||
export default function OAuthCodeExchange({ pathToApiServerRouteHandlingOauthRedirect }) {
|
||||
const history = useHistory()
|
||||
|
||||
// We are using a ref to prevent sending the OAuth token twice in development.
|
||||
// Since React 18 and using their StrictMode, useEffect is called twice in development.
|
||||
|
||||
// Fixing it this way is not recommended by the docs, but they don't offer any alternatives
|
||||
// for this particular use case (oauth redirect page):
|
||||
// https://react.dev/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development
|
||||
const firstRender = useRef(true)
|
||||
useEffect(() => {
|
||||
if (!firstRender.current) {
|
||||
return
|
||||
}
|
||||
// NOTE: Different auth methods will have different Wasp API server validation paths.
|
||||
// This helps us reuse one component for various methods (e.g., Google, Facebook, etc.).
|
||||
const apiServerUrlHandlingOauthRedirect = constructOauthRedirectApiServerUrl(pathToApiServerRouteHandlingOauthRedirect)
|
||||
|
||||
exchangeCodeForJwtAndRedirect(history, apiServerUrlHandlingOauthRedirect)
|
||||
return () => {
|
||||
firstRender.current = false
|
||||
}
|
||||
}, [history, pathToApiServerRouteHandlingOauthRedirect])
|
||||
|
||||
return (
|
||||
|
@ -28,9 +28,13 @@ export function useSocketListener<Event extends keyof ServerToClientEvents>(
|
||||
// Casting to `keyof ServerToClientEvents` is necessary because TypeScript
|
||||
// reports the handler function as incompatible with the event type.
|
||||
// See https://github.com/wasp-lang/wasp/pull/1203#discussion_r1232068898
|
||||
socket.on(event as keyof ServerToClientEvents, handler)
|
||||
|
||||
// We are wrapping it in `Extract<...>` due to Typescript infering string | number
|
||||
// in the case of default events being used.
|
||||
type AllowedEvents = Extract<keyof ServerToClientEvents, string>;
|
||||
socket.on(event as AllowedEvents, handler)
|
||||
return () => {
|
||||
socket.off(event as keyof ServerToClientEvents, handler)
|
||||
socket.off(event as AllowedEvents, handler)
|
||||
}
|
||||
}, [event, handler])
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ export async function init(server: http.Server): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
await {= userWebSocketFn.importIdentifier =}(io, context)
|
||||
await ({= userWebSocketFn.importIdentifier =} as any)(io, context)
|
||||
}
|
||||
|
||||
{=# isAuthEnabled =}
|
||||
|
@ -655,7 +655,7 @@
|
||||
"file",
|
||||
"web-app/src/auth/forms/Auth.tsx"
|
||||
],
|
||||
"9e342bdf8c17d9c36f042b272cf7d5e432bb27adc10ba218b206ca54638f6b4e"
|
||||
"d40cf940a499fdd4b137dcf9f3cd4fbe0bbab4b7c44eb7819b41daeaa861050b"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -718,7 +718,7 @@
|
||||
"file",
|
||||
"web-app/src/auth/helpers/Google.jsx"
|
||||
],
|
||||
"c6677ed5052cf7dc9aca312935b48dd59eaf22420d581ac1b79c01070d3c109e"
|
||||
"dd4daa37a618852db5da6ba5a718541f99ce57ceaaca3c397ba4e4c3739fdde8"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -739,7 +739,7 @@
|
||||
"file",
|
||||
"web-app/src/auth/pages/OAuthCodeExchange.jsx"
|
||||
],
|
||||
"180e07f82c8290f9cae71e0aef97b40cf1b72a5a5fb58317feaf4cf66e629e42"
|
||||
"7dbcc288201aafbb50b5f5319a28283546c81d006fe61c2a8a3c5f55c6833fb2"
|
||||
],
|
||||
[
|
||||
[
|
||||
|
@ -8,9 +8,6 @@ import {
|
||||
type ErrorMessage,
|
||||
} from './types'
|
||||
import { LoginSignupForm } from './internal/common/LoginSignupForm'
|
||||
import { ForgotPasswordForm } from './internal/email/ForgotPasswordForm'
|
||||
import { ResetPasswordForm } from './internal/email/ResetPasswordForm'
|
||||
import { VerifyEmailForm } from './internal/email/VerifyEmailForm'
|
||||
import { MessageError, MessageSuccess } from './internal/Message'
|
||||
|
||||
const logoStyle = {
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
import config from '../../config.js'
|
||||
import { SocialButton } from '../forms/SocialButton'
|
||||
import * as SocialIcons from '../forms/SocialIcons'
|
||||
import { SocialButton } from '../forms/internal/social/SocialButton'
|
||||
import * as SocialIcons from '../forms/internal/social/SocialIcons'
|
||||
|
||||
export const signInUrl = `${config.apiUrl}/auth/google/login`
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
|
||||
import config from '../../config.js'
|
||||
@ -13,12 +13,25 @@ import { initSession } from '../helpers/user'
|
||||
export default function OAuthCodeExchange({ pathToApiServerRouteHandlingOauthRedirect }) {
|
||||
const history = useHistory()
|
||||
|
||||
// We are using a ref to prevent sending the OAuth token twice in development.
|
||||
// Since React 18 and using their StrictMode, useEffect is called twice in development.
|
||||
|
||||
// Fixing it this way is not recommended by the docs, but they don't offer any alternatives
|
||||
// for this particular use case (oauth redirect page):
|
||||
// https://react.dev/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development
|
||||
const firstRender = useRef(true)
|
||||
useEffect(() => {
|
||||
if (!firstRender.current) {
|
||||
return
|
||||
}
|
||||
// NOTE: Different auth methods will have different Wasp API server validation paths.
|
||||
// This helps us reuse one component for various methods (e.g., Google, Facebook, etc.).
|
||||
const apiServerUrlHandlingOauthRedirect = constructOauthRedirectApiServerUrl(pathToApiServerRouteHandlingOauthRedirect)
|
||||
|
||||
exchangeCodeForJwtAndRedirect(history, apiServerUrlHandlingOauthRedirect)
|
||||
return () => {
|
||||
firstRender.current = false
|
||||
}
|
||||
}, [history, pathToApiServerRouteHandlingOauthRedirect])
|
||||
|
||||
return (
|
||||
|
164
web/blog/2023-06-07-wasp-beta-update-may-23.md
Normal file
@ -0,0 +1,164 @@
|
||||
---
|
||||
title: 'Wasp Beta - May 2023'
|
||||
authors: [matijasos]
|
||||
image: /img/update-may-23/banner.png
|
||||
tags: [update]
|
||||
---
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
import InBlogCta from './components/InBlogCta';
|
||||
import WaspIntro from './_wasp-intro.md';
|
||||
import ImgWithCaption from './components/ImgWithCaption'
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Wasp Update May 23"
|
||||
source="img/update-may-23/banner.png"
|
||||
/>
|
||||
|
||||
*Want to stay in the loop? → [Join our newsletter!](/#signup)*
|
||||
|
||||
Hola Waspeteers 🐝,
|
||||
|
||||
What did one plant say to the other? Aloe! Long thyme no see. 🌱
|
||||
|
||||
Now that we've set the tone, let me guide you through what's new in Waspworld (that would be a cool theme park, right?):
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
## Wasp Hackathon 2.0 is over - congrats to the winners! 🐝 🏆 🐝
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Congrats to the hackathon winners!"
|
||||
source="img/update-may-23/typergotchi.png"
|
||||
caption="Shoutout to the winning team - Typergotchi! They even made a cool illustration with our mascot, Da Boi 🐝 😎"
|
||||
/>
|
||||
|
||||
We had more submissions than ever, and the quality and creativity of your apps were really at the next level. We had everything from admin dashboards and GPT-powered story-telling apps to the actual games.
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Hackathon testimonial"
|
||||
source="img/update-may-23/testimonial-hackathon.png"
|
||||
/>
|
||||
|
||||
See all the winners and read a full Hackathon 2.0 review [👉 here 👈](/blog/2023/05/19/hackathon-2-review).
|
||||
|
||||
## Wasp Launch Week #3 is in the making - get ready for the Magic 🔮 🧙
|
||||
|
||||
As it always happens in the wilderness, after one launch week, there comes another one. And who are we to defy the laws of nature - thus, get ready for Launch Week #3!
|
||||
|
||||
We are aiming for the end of June, but we'll announce the exact date soon. Make sure to [follow us on Twitter](https://twitter.com/WaspLang) or/and [join our Discord](https://discord.gg/rzdnErX) to stay in the loop.
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Beautiful"
|
||||
source="img/update-may-23/beautiful.gif"
|
||||
caption="When you see it ✨"
|
||||
/>
|
||||
|
||||
### After Pizzazz 🍕 ...
|
||||
As you might remember, the motto/topic of our [last launch](/blog/2023/04/11/wasp-launch-week-two) was *Pizzaz*, which referred to improving the developer experience in Wasp - full-stack auth, one-line deployment, type safety, db tooling, ...
|
||||
|
||||
### ... Comes Magic! 🔮
|
||||
While DX will always be our top priority, we're now shifting gears a bit - the keyword we chose to represent our next launch is ✨ *Magic* ✨. The reason is that now that we have a majority of the features you'd expect in a web framework in place, **we can start utilizing Wasp's unique compiler-driven approach to offer next-level features no other framework can!**
|
||||
|
||||
### LW3 Sneak Peek 🤫 👀
|
||||
More details coming soon, but in the meanwhile, here are some of the features we're most excited about:
|
||||
|
||||
#### 🚧 Wasp AI 🤖 ✨
|
||||
There is no mAgIc without AI! We cannot share many details on this yet, but it is something we've been exploring a lot lately. Our previous experiments have shown that, due to its declarative and human-readable nature, Wasp is naturally a very good fit for LLMs.
|
||||
|
||||
We'll take this to the next level for our next launch - stay tuned!
|
||||
|
||||
#### 🚧 Auto CRUD
|
||||
Although Wasp helps a lot with bootstrapping your app, one repetitive thing that you have to do every time is implement "standard" CRUD operations for your data models.
|
||||
|
||||
We decided to put a stop to it - welcome our new (incoming) feature, Auto CRUD!
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Auto CRUD"
|
||||
source="img/update-may-23/auto-crud.png"
|
||||
caption="Syntax proposal for the new Auto CRUD feature"
|
||||
/>
|
||||
|
||||
All you have to do is specify in your .wasp file which CRUD operations you want, and they will be auto-generated for you to use in your JS/TS code. **The best part is when you update your data model, these will get updated as well! 🤯**
|
||||
|
||||
This feature is also a really good showcase of Wasp's compiler muscles - the best you could get with a traditional framework approach is scaffolding, which means spitting out code that will quickly get outdated and that you have to maintain.
|
||||
|
||||
[See a 2-min demo of Wasp Auto CRUD in action](https://www.youtube.com/watch?v=IzBxpqV5USE&ab_channel=Wasp) - by our founding engineer Miho
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Showing off compiler muscles"
|
||||
source="img/update-may-23/muscles.gif"
|
||||
caption="Our compiler right now"
|
||||
/>
|
||||
|
||||
#### 🚧 Advanced syntax completion for .wasp files (LSP)
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Improved LSP"
|
||||
source="img/update-may-23/new-lsp.png"
|
||||
/>
|
||||
|
||||
We're making our [VS Code extension](https://marketplace.visualstudio.com/items?itemName=wasp-lang.wasp) even better! So far it has provided highlighting and auto-completion for top-level declarations (e.g., *route, entity, query*, ...), but now it's going even deeper. Every property will display its full type as you are typing it out + you'll get a context-aware auto-completion.
|
||||
|
||||
|
||||
#### 🚧 Support for web sockets 🔌 🧦!
|
||||
Wasp will soon support Web Sockets! This will allow you to have a persistent, real-time connection between your client and server, which is great for chat apps, games, and more.
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Web sockets in Wasp"
|
||||
source="img/update-may-23/web-sockets.png"
|
||||
caption="Defining a new web socket in Wasp config file"
|
||||
/>
|
||||
|
||||
For now it is a stand-alone feature, but it opens some really interesting possibilities - e.g. combining this with Wasp's query/action system and letting you declare a particular query to be "live". Just an idea for now but something to keep in mind as we test and receive more feedback on this feature.
|
||||
|
||||
## From the blog 📖
|
||||
- [How I Built CoverLetterGPT - SaaS app with the PERN stack, GPT, Stripe, & Chakra UI](/blog/2023/04/17/How-I-Built-CoverLetterGPT) - our one and only Vince shares the story behind CoverLetterGPT; how he built it and got thousands of users in just a few weeks
|
||||
- [Hackathon #2: Results & Review](/blog/2023/05/19/hackathon-2-review) - see how our hackathon went and what the contestants think about Wasp!
|
||||
|
||||
## The community buzz 🐝 💬
|
||||
Last month was super buzzy! We got several awesome reviews, and Wasp also got picked up by a couple of YouTube dev influencers:
|
||||
|
||||
<ImgWithCaption
|
||||
alt="Wasp testimonial"
|
||||
source="img/update-may-23/testimonial-jon.png"
|
||||
/>
|
||||
|
||||
- 🎬 [Build Full-Stack Notes App w/ Wasp! (ReactJS, Prisma, Node)](https://www.youtube.com/watch?v=AA4ckj1P5QY&t=12s&ab_channel=webdecoded) - an amazing 30-min overview of how to build a classic notes app with Wasp
|
||||
- 🎬 [Live Coding Serverlesspresso with WaspLang!](https://www.youtube.com/watch?v=c3-bbzrdC8E&ab_channel=DevAgrawal) - 4-hour session of live coding a coffee ordering app. Only for the most hardcore folks plus you'll need a lot of coffee :D
|
||||
- ✒️ [Wasp Configuration Language: Simplify Full Stack App Creation](https://blog.oleggulevskyy.dev/wasp-configuration-language-simplify-full-stack-app-creation) - a lightweight overview of Wasp and why the author prefers it to Next/T3
|
||||
- ✒️ [Wasp(athon) impressions & some proposals](https://medium.com/@umbrien/wasp-athon-impressions-some-proposals-8f3726890009) - from one of our hackathon contestants - first impressions and spot-on ideas for the next features
|
||||
|
||||
## Wasp GitHub Star Growth - 2,825 ⭐
|
||||
Getting close to the big 3,000! Huge thanks to all our contributors and stargazers - you are amazing!
|
||||
|
||||
<ImgWithCaption
|
||||
alt="GitHub stars - almost 3,000!"
|
||||
source="img/update-may-23/github-stars.png"
|
||||
caption="Almost 3,000 stars! 🐝 🚀"
|
||||
/>
|
||||
|
||||
And if you haven't yet, please [star us on Github](https://wasp-lang.dev/docs)! Yes, we are shameless star beggars, but if you believe in the project and want to support it that's one of the best ways to do it (next to actually building something with Wasp - [go do that too](https://wasp-lang.dev/docs)! :D)
|
||||
|
||||
## That's a wrap! 🌯
|
||||
|
||||
<ImgWithCaption
|
||||
alt="A dramatic goodbye gif"
|
||||
source="img/update-may-23/goodbye-dramatic.gif"
|
||||
caption="A dramatic goodbye - don't ever let go"
|
||||
/>
|
||||
|
||||
That's it for this month and thanks for reading! Since you've come this far, you deserve one final treat - a Wasp-themed joke generated by ChatGPT:
|
||||
|
||||
<ImgWithCaption
|
||||
alt="GPT Wasp joke"
|
||||
source="img/update-may-23/gpt-wasp-joke.png"
|
||||
caption="Good one, dad."
|
||||
/>
|
||||
|
||||
Fly high, and we'll see you soon 🐝 🐝,
|
||||
Matija, Martin and the Wasp team
|
||||
|
||||
|
181
web/docs/guides/username-password.md
Normal file
@ -0,0 +1,181 @@
|
||||
---
|
||||
title: Username & Password
|
||||
---
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
# Username & Password
|
||||
|
||||
### Configuration
|
||||
|
||||
To get started with a simple Username & Password Auth strategy, 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.11.0"
|
||||
},
|
||||
|
||||
title: "Example",
|
||||
|
||||
auth: {
|
||||
userEntity: User,
|
||||
methods: {
|
||||
usernameAndPassword: {},
|
||||
},
|
||||
onAuthFailedRedirectTo: "/login"
|
||||
}
|
||||
}
|
||||
|
||||
// Wasp requires the userEntity to have at least the following fields
|
||||
entity User {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
username String @unique
|
||||
password String
|
||||
psl=}
|
||||
|
||||
// ...
|
||||
```
|
||||
For more info on the specific fields, check out this [Auth](/docs/language/features#authentication--authorization) section of the docs.
|
||||
|
||||
If you're adding a new entity to your `.wasp` file for the first time, make sure to create the table for it in your database:
|
||||
```shell
|
||||
wasp db migrate-dev
|
||||
```
|
||||
|
||||
You'll also need to add these environment variables to your `.env.server` file at the root of your project:
|
||||
|
||||
```bash title=".env.server"
|
||||
JWT_SECRET=random-string-at-least-32-characters-long.
|
||||
```
|
||||
|
||||
With `auth` now defined, Wasp offers a number of handy features out of the box:
|
||||
- ["AuthUI" Login and Signup forms](/docs/guides/auth-ui) located at `@wasp/auth/forms/Login` and `@wasp/auth/forms/Signup` paths, ready to be styled and used.
|
||||
- The `logout()` action.
|
||||
- The `useAuth()` hook to access the logged-in user client-side.
|
||||
- The `context.user` object as an argument server-side within [Operations](/docs/language/features#queries-and-actions-aka-operations).
|
||||
|
||||
:::tip Customizing Auth
|
||||
This is a very high-level API for auth which makes it very easy to get started quickly, but is
|
||||
not very flexible. If you require more control (e.g. want to execute some custom code on the server
|
||||
during signup, check out the [lower-level auth API](/docs/language/features#lower-level-api).
|
||||
:::
|
||||
|
||||
### Client-side
|
||||
|
||||
To access the logged-in user client-side, you have two options:
|
||||
|
||||
1. You can use the `user` object that Wasp passes to all pages by default:
|
||||
|
||||
```jsx
|
||||
const Page = ({ user }) => {
|
||||
const username = user.username
|
||||
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
2. Use the `useAuth()` hook:
|
||||
|
||||
```jsx
|
||||
import useAuth from '@wasp/auth/useAuth.js'
|
||||
|
||||
const Page = () => {
|
||||
const { data: user, isLoading, isError } = useAuth();
|
||||
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
You can then do things like displaying some of the user information on the page. Here's an example:
|
||||
|
||||
|
||||
```jsx
|
||||
import useAuth from '@wasp/auth/useAuth.js'
|
||||
import logout from '@wasp/auth/logout.js'
|
||||
import Todo from '../Todo.js'
|
||||
|
||||
const Page = () => {
|
||||
const { data: user } = useAuth()
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Welcome {user.username}!</h1>
|
||||
<Todo />
|
||||
<button onClick={logout}>Logout</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Page
|
||||
```
|
||||
|
||||
You don't need to use the `useAuth()` hook if you're trying to protect a page from unauthorized users. Wasp takes care of that for you with its [higher-level API](/docs/language/features#authentication--authorization):
|
||||
|
||||
```c title="main.wasp" {28}
|
||||
app Example {
|
||||
wasp: {
|
||||
version: "^0.8.0"
|
||||
},
|
||||
|
||||
title: "Example",
|
||||
|
||||
auth: {
|
||||
userEntity: User,
|
||||
methods: {
|
||||
usernameAndPassword: {},
|
||||
},
|
||||
onAuthFailedRedirectTo: "/login"
|
||||
}
|
||||
}
|
||||
|
||||
// Wasp requires the userEntity to have at least the following fields
|
||||
entity User {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
username String @unique
|
||||
password String
|
||||
psl=}
|
||||
|
||||
// By adding `authRequired: true` to a page, Wasp will automatically
|
||||
// redirect unauthenticated users to the `onAuthFailedRedirectTo` route
|
||||
route MainRoute { path: "/", to: MainPage }
|
||||
page MainPage {
|
||||
authRequired: true,
|
||||
component: import Main from "@client/MainPage"
|
||||
}
|
||||
|
||||
route LoginRoute { path: "/login", to: LoginPage }
|
||||
page LoginPage {
|
||||
component: import Login from "@client/LoginPage"
|
||||
}
|
||||
|
||||
route SignupRoute { path: "/signup", to: SignupPage }
|
||||
page SignupPage {
|
||||
component: import Signup from "@client/SignupPage"
|
||||
}
|
||||
```
|
||||
|
||||
### Server-side
|
||||
|
||||
To access the logged-in user server-side, you can use the `context.user` object within [Operations (i.e. *queries and actions*)](/docs/language/features#queries-and-actions-aka-operations):
|
||||
|
||||
|
||||
```js title="src/server/actions.js" {4}
|
||||
import HttpError from '@wasp/core/HttpError.js'
|
||||
|
||||
export const createTask = async (task, context) => {
|
||||
if (!context.user) {
|
||||
throw new HttpError(401, 'You need to be logged in to create a task.')
|
||||
}
|
||||
|
||||
return context.entities.Task.create({
|
||||
data: {
|
||||
description: task.description,
|
||||
user: {
|
||||
connect: { id: context.user.id }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
@ -46,12 +46,18 @@ psl=}
|
||||
|
||||
For more info on the specific fields, check out this [Auth](../language/features#social-login-providers-oauth-20) section of the docs.
|
||||
|
||||
If you're adding a new entity to your `.wasp` file, make sure you migrate your database schema:
|
||||
```shell
|
||||
wasp db migrate-dev
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
JWT_SECRET=random-string-at-least-32-characters-long.
|
||||
```
|
||||
We will cover how to get these values in the next section.
|
||||
|
||||
|
@ -46,12 +46,18 @@ psl=}
|
||||
|
||||
For more info on the specific fields, check out this [Auth](../language/features#social-login-providers-oauth-20) section of the docs.
|
||||
|
||||
If you're adding a new entity to your `.wasp` file, make sure you migrate your database schema:
|
||||
```shell
|
||||
wasp db migrate-dev
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
JWT_SECRET=random-string-at-least-32-characters-long.
|
||||
```
|
||||
We will cover how to get these values in the next section.
|
||||
|
||||
|
@ -1241,7 +1241,7 @@ If you require more control in your authentication flow, you can achieve that in
|
||||
- If you want to execute custom code on the server during sign up, create your own sign up action which invokes Prisma client as `context.entities.[USER_ENTITY].create()` function, along with your custom code.
|
||||
|
||||
The code of your custom sign-up action would look like this (your user entity being `User` in this instance):
|
||||
```js
|
||||
```js title="src/server/auth/signup.js"
|
||||
export const signUp = async (args, context) => {
|
||||
// Your custom code before sign-up.
|
||||
// ...
|
||||
@ -1306,7 +1306,7 @@ Password of the user logging in.
|
||||
|
||||
#### `import statement`:
|
||||
```js
|
||||
import login from '@wasp/auth/login.js'
|
||||
import login from '@wasp/auth/login'
|
||||
```
|
||||
Login is a regular action and can be used directly from the frontend.
|
||||
|
||||
@ -1328,7 +1328,7 @@ If you need to add extra fields to the user entity, we suggest doing it in a sep
|
||||
|
||||
#### `import statement`:
|
||||
```js
|
||||
import signup from '@wasp/auth/signup.js'
|
||||
import signup from '@wasp/auth/signup'
|
||||
```
|
||||
Signup is a regular action and can be used directly from the frontend.
|
||||
|
||||
@ -1340,12 +1340,12 @@ logout()
|
||||
|
||||
#### `import statement`:
|
||||
```js
|
||||
import logout from '@wasp/auth/logout.js'
|
||||
import logout from '@wasp/auth/logout'
|
||||
```
|
||||
|
||||
##### Example of usage:
|
||||
```jsx
|
||||
import logout from '@wasp/auth/logout.js'
|
||||
import logout from '@wasp/auth/logout'
|
||||
|
||||
const SignOut = () => {
|
||||
return (
|
||||
@ -1840,8 +1840,8 @@ import React from 'react'
|
||||
|
||||
import { Link } from 'react-router-dom'
|
||||
import useAuth from '@wasp/auth/useAuth'
|
||||
import logout from '@wasp/auth/logout.js'
|
||||
import Todo from '../Todo.js'
|
||||
import logout from '@wasp/auth/logout'
|
||||
import Todo from '../Todo'
|
||||
import '../Main.css'
|
||||
|
||||
const Main = () => {
|
||||
|
@ -282,7 +282,7 @@ You will see that each user has their own tasks, just as we specified in our cod
|
||||
Last, but not least, let's add the logout functionality:
|
||||
```jsx {2,10} title="src/client/MainPage.jsx"
|
||||
// ...
|
||||
import logout from '@wasp/auth/logout.js'
|
||||
import logout from '@wasp/auth/logout'
|
||||
//...
|
||||
|
||||
const MainPage = () => {
|
||||
|
@ -292,7 +292,7 @@ export const TaskInfo = () => {
|
||||
|
||||
Assuming the following action definition in your `.wasp` file
|
||||
|
||||
```typescript title=main.wasp
|
||||
```wasp title=main.wasp
|
||||
action addTask {
|
||||
fn: import { addTask } from "@server/actions.js"
|
||||
entities: [Task]
|
||||
|
@ -50,11 +50,8 @@ module.exports = {
|
||||
type: "category",
|
||||
label: "Auth Providers",
|
||||
collapsed: false,
|
||||
items: [
|
||||
"integrations/github",
|
||||
"integrations/google",
|
||||
"guides/email-auth",
|
||||
],
|
||||
items: ["integrations/github", "integrations/google",
|
||||
"guides/email-auth", "guides/username-password"],
|
||||
},
|
||||
"integrations/css-frameworks",
|
||||
"deploying",
|
||||
|
@ -1,44 +1,77 @@
|
||||
import React from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
const SubscribeForm = ({ className, inputBgColor }) => (
|
||||
<form
|
||||
className={classNames('sm:flex', className)}
|
||||
action="https://gmail.us4.list-manage.com/subscribe/post?u=8139c7de74df98aa17054b235&id=f0c6ba5f1d"
|
||||
method="post"
|
||||
>
|
||||
<input
|
||||
aria-label="Email address"
|
||||
type="email"
|
||||
name="EMAIL"
|
||||
id="email-address"
|
||||
required autoComplete='email'
|
||||
placeholder='you@areawesomeforsubscribing.com'
|
||||
className={`
|
||||
text-sm w-full
|
||||
appearance-none
|
||||
placeholder:text-neutral-400
|
||||
border border-yellow-500
|
||||
px-4 py-2 rounded-md
|
||||
focus:outline-none focus:ring-2 focus:ring-yellow-400
|
||||
` + ` ${inputBgColor}`}
|
||||
/>
|
||||
<div className='rounded-md mt-3 sm:mt-0 sm:ml-3'>
|
||||
<button
|
||||
type='submit'
|
||||
className={`
|
||||
w-full
|
||||
text-sm border border-transparent rounded-md
|
||||
bg-yellow-500 text-white
|
||||
px-4 py-2
|
||||
hover:bg-yellow-400
|
||||
transition ease-out duration-200
|
||||
`}
|
||||
>
|
||||
Subscribe
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
const createNewEmailSubscriberApiEndpoint = "https://app.loops.so/api/newsletter-form/clg0zndc9000ajn0f8a1bhgmu"
|
||||
|
||||
const SubscribeForm = ({ className, inputBgColor }) => {
|
||||
const [email, setEmail] = useState('')
|
||||
const [message, setMessage] = useState('')
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
// NOTE(matija): without this, the whole page reloads on form submission.
|
||||
event.preventDefault()
|
||||
|
||||
try {
|
||||
const res = await fetch(createNewEmailSubscriberApiEndpoint, {
|
||||
method: "POST",
|
||||
body: 'userGroup=&email=' + email,
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
})
|
||||
setMessage('Thank you for subscribing! 🙏')
|
||||
|
||||
} catch (error) {
|
||||
setMessage('🛑 Oops! Something went wrong. Please try again.')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{ message ?
|
||||
<p className='text-lg text-neutral-500'>{message}</p>
|
||||
:
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className={classNames('sm:flex', className)}
|
||||
>
|
||||
<input
|
||||
aria-label="Email address"
|
||||
type="email"
|
||||
name="email"
|
||||
value={email}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
id="email-address"
|
||||
required autoComplete='email'
|
||||
placeholder='you@awesomedev.com'
|
||||
className={`
|
||||
text-sm w-full
|
||||
appearance-none
|
||||
placeholder:text-neutral-400
|
||||
border border-yellow-500
|
||||
px-4 py-2 rounded-md
|
||||
focus:outline-none focus:ring-2 focus:ring-yellow-400
|
||||
` + ` ${inputBgColor}`}
|
||||
/>
|
||||
<div className='rounded-md mt-3 sm:mt-0 sm:ml-3'>
|
||||
<button
|
||||
type='submit'
|
||||
className={`
|
||||
w-full
|
||||
text-sm border border-transparent rounded-md
|
||||
bg-yellow-500 text-white
|
||||
px-4 py-2
|
||||
hover:bg-yellow-400
|
||||
transition ease-out duration-200
|
||||
`}
|
||||
>
|
||||
Subscribe
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default SubscribeForm
|
||||
|
BIN
web/static/img/update-may-23/auto-crud.png
Normal file
After Width: | Height: | Size: 292 KiB |
BIN
web/static/img/update-may-23/banner.png
Normal file
After Width: | Height: | Size: 150 KiB |
BIN
web/static/img/update-may-23/beautiful.gif
Normal file
After Width: | Height: | Size: 1.9 MiB |
BIN
web/static/img/update-may-23/github-stars.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
web/static/img/update-may-23/goodbye-dramatic.gif
Normal file
After Width: | Height: | Size: 2.2 MiB |
BIN
web/static/img/update-may-23/gpt-wasp-joke.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
web/static/img/update-may-23/muscles.gif
Normal file
After Width: | Height: | Size: 998 KiB |
BIN
web/static/img/update-may-23/new-lsp.png
Normal file
After Width: | Height: | Size: 155 KiB |
BIN
web/static/img/update-may-23/testimonial-hackathon.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
web/static/img/update-may-23/testimonial-jon.png
Normal file
After Width: | Height: | Size: 114 KiB |
BIN
web/static/img/update-may-23/typergotchi.png
Normal file
After Width: | Height: | Size: 644 KiB |
BIN
web/static/img/update-may-23/web-sockets.png
Normal file
After Width: | Height: | Size: 106 KiB |