Merge branch 'release'

This commit is contained in:
vincanger 2023-01-17 10:41:43 +01:00
commit b0c20a1f6d
33 changed files with 690 additions and 11 deletions

3
examples/todo-typescript/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/.wasp/
/.env.server
/.env.client

View File

@ -0,0 +1 @@
File marking the root of Wasp project.

View File

@ -0,0 +1,68 @@
app TodoTypescript {
wasp: {
version: "^0.7.3"
},
title: "ToDo TypeScript",
auth: {
userEntity: User,
methods: {
usernameAndPassword: {},
},
onAuthFailedRedirectTo: "/login",
}
}
// Use Prisma Schema Language (PSL) to define our entities: https://www.prisma.io/docs/concepts/components/prisma-schema
// Run `wasp db migrate-dev` in the CLI to create the database tables
// Then run `wasp db studio` to open Prisma Studio and view your db models
entity User {=psl
id Int @id @default(autoincrement())
username String @unique
password String
tasks Task[]
psl=}
entity Task {=psl
id Int @id @default(autoincrement())
description String
isDone Boolean @default(false)
user User? @relation(fields: [userId], references: [id])
userId Int?
psl=}
route RootRoute { path: "/", to: MainPage }
page MainPage {
authRequired: true,
component: import { MainPage } from "@client/MainPage"
}
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { LoginPage } from "@client/LoginPage"
}
route SignupRoute { path: "/signup", to: SignupPage }
page SignupPage {
component: import { SignupPage } from "@client/SignupPage"
}
query getTasks {
// We specify the JS implementation of our query (which is an async JS function)
// Even if you use TS and have a queries.ts file, you will still need to import it using the .js extension.
// see here for more info: https://wasp-lang.dev/docs/tutorials/todo-app/03-listing-tasks#wasp-declaration
fn: import { getTasks } from "@server/queries.js",
// We tell Wasp that this query is doing something with the `Task` entity. With that, Wasp will
// automatically refresh the results of this query when tasks change.
entities: [Task]
}
action createTask {
fn: import { createTask } from "@server/actions.js",
entities: [Task]
}
action updateTask {
fn: import { updateTask } from "@server/actions.js",
entities: [Task]
}

View File

@ -0,0 +1,18 @@
-- CreateTable
CREATE TABLE "User" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"username" TEXT NOT NULL,
"password" TEXT NOT NULL
);
-- CreateTable
CREATE TABLE "Task" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"description" TEXT NOT NULL,
"isDone" BOOLEAN NOT NULL DEFAULT false,
"userId" INTEGER,
CONSTRAINT "Task_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");

View 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"

View File

@ -0,0 +1,3 @@
# Ignore editor tmp files
**/*~
**/#*#

View File

@ -0,0 +1,16 @@
import { Link } from 'react-router-dom';
import LoginForm from '@wasp/auth/forms/Login';
export function LoginPage() {
return (
<main>
<h1>Login</h1>
{/** Wasp has built-in auth forms & flows, which you can also opt-out of, if you wish :) */}
<LoginForm />
<br />
<span>
I don't have an account yet (<Link to='/signup'>go to signup</Link>).
</span>
</main>
);
};

View File

@ -0,0 +1,53 @@
* {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
box-sizing: border-box;
}
main {
padding: 1rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
h1 {
padding: 0;
margin: 1rem 0;
}
main p {
font-size: 1rem;
}
img {
max-height: 100px;
}
button {
margin-top: 1rem;
}
code {
border-radius: 5px;
padding: 0.2rem;
background: #efefef;
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
Bitstream Vera Sans Mono, Courier New, monospace;
}
.auth-form h2 {
margin-top: 0.5rem;
font-size: 1.2rem;
}
.tasklist {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
width: 300px;
margin-top: 1rem;
}

View File

@ -0,0 +1,88 @@
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 { useQuery } from '@wasp/queries'; // Wasp uses a thin wrapper around react-query
import getTasks from '@wasp/queries/getTasks';
import createTask from '@wasp/actions/createTask';
import updateTask from '@wasp/actions/updateTask';
import waspLogo from './waspLogo.png';
import { Task } from './types'
export function MainPage() {
const { data: user } = useAuth();
const { data: tasks, isLoading, error } = useQuery<unknown, Task[]>(getTasks);
useEffect(() => {
console.log(user);
}, [user]);
if (isLoading) return 'Loading...';
if (error) return 'Error: ' + error;
return (
<main>
<img src={waspLogo} alt='wasp logo' />
<h1>
{user.username}
{`'s tasks :)`}
</h1>
<NewTaskForm />
{tasks && <TasksList tasks={tasks} /> }
<button onClick={logout}> Logout </button>
</main>
);
};
function Todo({ id, isDone, description }: Task) {
const handleIsDoneChange = async (event: FormEventHandler<HTMLInputElement>) => {
try {
await updateTask({
taskId: id,
isDone: event.currentTarget.checked,
});
} catch (err: any) {
window.alert('Error while updating task ' + err?.message);
}
};
return (
<li>
<input type='checkbox' id={id.toString()} checked={isDone} onChange={handleIsDoneChange} />
<span>{description}</span>
</li>
);
};
function TasksList({tasks}: { tasks: Task[] }) {
if (tasks.length === 0) return <p>No tasks yet.</p>;
return (
<ol className='tasklist'>
{tasks.map((tsk, idx) => (
<Todo {...tsk} key={idx} />
))}
</ol>
);
};
function NewTaskForm() {
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
try {
const description = event.currentTarget.description.value;
console.log(description)
event.currentTarget.reset();
await createTask({ description });
} catch (err: any) {
window.alert('Error: ' + err?.message);
}
};
return (
<form onSubmit={handleSubmit}>
<input name='description' type='text' defaultValue='' />
<input type='submit' value='Create task' />
</form>
);
};

View File

@ -0,0 +1,16 @@
import { Link } from 'react-router-dom';
import SignupForm from '@wasp/auth/forms/Signup';
export function SignupPage() {
return (
<main>
<h1>Sign Up</h1>
{/** Wasp has built-in auth forms & flows, which you can also opt-out of, if you wish :) */}
<SignupForm />
<br />
<span>
I already have an account (<Link to='/login'>go to login</Link>).
</span>
</main>
);
};

View File

@ -0,0 +1,60 @@
declare module '*.avif' {
const src: string;
export default src;
}
declare module '*.bmp' {
const src: string;
export default src;
}
declare module '*.gif' {
const src: string;
export default src;
}
declare module '*.jpg' {
const src: string;
export default src;
}
declare module '*.jpeg' {
const src: string;
export default src;
}
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.webp' {
const src: string;
export default src;
}
declare module '*.svg' {
import * as React from 'react';
export const ReactComponent: React.FunctionComponent<React.SVGProps<
SVGSVGElement
> & { title?: string }>;
const src: string;
export default src;
}
declare module '*.module.css' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.module.scss' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.module.sass' {
const classes: { readonly [key: string]: string };
export default classes;
}

View File

@ -0,0 +1,55 @@
// =============================== IMPORTANT =================================
//
// This file is only used for Wasp IDE support. You can change it to configure
// your IDE checks, but none of these options will affect the TypeScript
// compiler. Proper TS compiler configuration in Wasp is coming soon :)
{
"compilerOptions": {
// JSX support
"jsx": "preserve",
"strict": true,
// Allow default imports.
"esModuleInterop": true,
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
// Wasp needs the following settings enable IDE support in your source
// files. Editing them might break features like import autocompletion and
// definition lookup. Don't change them unless you know what you're doing.
//
// The relative path to the generated web app's root directory. This must be
// set to define the "paths" option.
"baseUrl": "../../.wasp/out/web-app/",
"paths": {
// Resolve all "@wasp" imports to the generated source code.
"@wasp/*": [
"src/*"
],
// Resolve all non-relative imports to the correct node module. Source:
// https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
"*": [
// Start by looking for the definiton inside the node modules root
// directory...
"node_modules/*",
// ... If that fails, try to find it inside definitely-typed type
// definitions.
"node_modules/@types/*"
]
},
// Correctly resolve types: https://www.typescriptlang.org/tsconfig#typeRoots
"typeRoots": [
"../../.wasp/out/web-app/node_modules/@types"
],
// Since this TS config is used only for IDE support and not for
// compilation, the following directory doesn't exist. We need to specify
// it to prevent this error:
// https://stackoverflow.com/questions/42609768/typescript-error-cannot-write-file-because-it-would-overwrite-input-file
"outDir": "phantom"
},
"exclude": [
"phantom"
],
}

View File

@ -0,0 +1,6 @@
export type Task = {
id: number;
description: string;
isDone: boolean;
userId: number | null;
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,34 @@
import HttpError from '@wasp/core/HttpError.js';
import { Context, Task } from './serverTypes'
type CreateArgs = Pick<Task, 'description'>;
export async function createTask({ description }: CreateArgs, context: Context) {
if (!context.user) {
throw new HttpError(401);
}
return context.entities.Task.create({
data: {
description,
user: { connect: { id: context.user.id } },
},
});
};
// type UpdateArgs = { taskId: Task['id']; isDone: Task['isDone'] };
type UpdateArgs = Pick<Task, 'id' | 'isDone'>;
export async function updateTask({ id, isDone }: UpdateArgs, context: Context) {
if (!context.user) {
throw new HttpError(401);
}
return context.entities.Task.updateMany({
where: {
id,
user: { id: context.user.id },
},
data: { isDone },
});
};

View File

@ -0,0 +1,9 @@
import HttpError from '@wasp/core/HttpError.js';
import { Context, Task } from './serverTypes'
export async function getTasks(args: unknown, context: Context): Promise<Task[]> {
if (!context.user) {
throw new HttpError(401);
}
return context.entities.Task.findMany({ where: { user: { id: context.user.id } } });
};

View File

@ -0,0 +1,11 @@
import { User, Prisma } from '@prisma/client';
export { Task } from '@prisma/client';
export type Context = {
user: User;
entities: {
Task: Prisma.TaskDelegate<{}>;
User: Prisma.UserDelegate<{}>;
};
};

View File

@ -0,0 +1,48 @@
// =============================== IMPORTANT =================================
//
// This file is only used for Wasp IDE support. You can change it to configure
// your IDE checks, but none of these options will affect the TypeScript
// compiler. Proper TS compiler configuration in Wasp is coming soon :)
{
"compilerOptions": {
// Allows default imports.
"esModuleInterop": true,
"allowJs": true,
"strict": true,
// Wasp needs the following settings enable IDE support in your source
// files. Editing them might break features like import autocompletion and
// definition lookup. Don't change them unless you know what you're doing.
//
// The relative path to the generated web app's root directory. This must be
// set to define the "paths" option.
"baseUrl": "../../.wasp/out/server/",
"paths": {
// Resolve all "@wasp" imports to the generated source code.
"@wasp/*": [
"src/*"
],
// Resolve all non-relative imports to the correct node module. Source:
// https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
"*": [
// Start by looking for the definiton inside the node modules root
// directory...
"node_modules/*",
// ... If that fails, try to find it inside definitely-typed type
// definitions.
"node_modules/@types/*"
]
},
// Correctly resolve types: https://www.typescriptlang.org/tsconfig#typeRoots
"typeRoots": [
"../../.wasp/out/server/node_modules/@types"
],
// Since this TS config is used only for IDE support and not for
// compilation, the following directory doesn't exist. We need to specify
// it to prevent this error:
// https://stackoverflow.com/questions/42609768/typescript-error-cannot-write-file-because-it-would-overwrite-input-file
"outDir": "phantom",
},
"exclude": [
"phantom"
],
}

View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
// Enable default imports in TypeScript.
"esModuleInterop": true,
"allowJs": true,
// The following settings enable IDE support in user-provided source files.
// Editing them might break features like import autocompletion and
// definition lookup. Don't change them unless you know what you're doing.
//
// The relative path to the generated web app's root directory. This must be
// set to define the "paths" option.
"baseUrl": "../../.wasp/out/server/",
"paths": {
// Resolve all non-relative imports to the correct node module. Source:
// https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
"*": [
// Start by looking for the definiton inside the node modules root
// directory...
"node_modules/*",
// ... If that fails, try to find it inside definitely-typed type
// definitions.
"node_modules/@types/*"
]
},
// Correctly resolve types: https://www.typescriptlang.org/tsconfig#typeRoots
"typeRoots": ["../../.wasp/out/server/node_modules/@types"],
}
}

View File

@ -16,9 +16,9 @@ import ImgWithCaption from './components/ImgWithCaption'
![amicus hero shot](../static/img/amicus-usecase/amicus-hero-shot.png) ![amicus hero shot](../static/img/amicus-usecase/amicus-hero-shot.png)
[Erlis Kllogjri](https://github.com/ErlisK) is an engineer based in San Francisco with broad experience ranging from mechanical engineering and C/C++ microcontroller programming to Python and web app development. In his free time, Erlis enjoys working on side projects, which is also how Amicus started out. [Erlis Kllogjri](https://github.com/ErlisK) is an engineer based in San Francisco with broad experience ranging from mechanical engineering and C/C++ microcontroller programming to Python and web app development. In his free time, Erlis enjoys working on side projects, which is also how [Amicus](https://www.amicus.work/) started out.
Amicus is a SaaS for legal teams - think about it as "Asana for lawyers", but with features and workflows tailored to the domain of law. [Amicus](https://www.amicus.work/) is a SaaS for legal teams - think about it as "Asana for lawyers", but with features and workflows tailored to the domain of law.
Read on to learn how long it took Erlis to develop the first version of his SaaS with Wasp, how he got his first paying customers, and what features he plans to add next! Read on to learn how long it took Erlis to develop the first version of his SaaS with Wasp, how he got his first paying customers, and what features he plans to add next!

View File

@ -0,0 +1,132 @@
---
title: 'Hosting Our First Hackathon: Results & Review'
authors: [vinny]
tags: [fullstack, webdev, hackathon, startups]
---
import ImgWithCaption from './components/ImgWithCaption'
To finalize the Wasp Beta launch week, we held a Beta Hackathon, which we dubbed the “Betathon”. The idea was to hold a simple, open, and fun hackathon to encourage users to build with Wasp, and thats exactly what they did!
As Wasp is still in its early days, we werent sure what the response would be, or if thered be any response at all. Considering that we didnt do much promotion of the Hackathon outside of our own channels, we were surprised by the results.
In this post, Ill give you a quick run-down of:
- the hackathon results 🏆
- how the hackathon was organized
- how we promoted it
- the community response
## …and the Winners Are:
Whats a hackathon without the participants!? Lets get this post off to a proper start by congratulating our winners and showcasing their work. 🔍
### 🥇 Tims Job Board
<ImgWithCaption
alt="Tim's Job Board"
source="img/betathon/tim.png"
/>
Tim really went for it and created a feature-rich Job Board:
- View the [App](https://client-production-54e7.up.railway.app/) & [GitHub Repo](https://github.com/tskaggs/wasp-jobs)
- Follow [Tim on Twitter](https://twitter.com/tskaggs)
- 🎉 Prizes: Wasp-colored Mechanical Keyboard, Wasp swag, $200 [Railway.app](http://Railway.app) credits
> “***Wasp is very awesome!*** *Easy setup and start-up especially if you're familiar with the Prisma ORM and Tailwind CSS. The stack is small but powerful... I'm going to use Wasp on a few MVP projects this year.”* - Tim
>
### 🥈Chriss “Cook Wherever” Recipes App
<ImgWithCaption
alt="Chris's Cook Wherever Recipes App"
source="img/betathon/chris.png"
/>
Chris created an extensive database of recipes in a slick app:
- View the [App](https://cookwherever.com) & [GitHub Repo](https://github.com/cookwherever/cookwherever)
- Follow [Chris on Twitter](https://twitter.com/breadchris)
- 🎉 Prizes: Wasp swag, $125 [Railway.app](http://Railway.app) credits
> “***This was the best app dev experience I ever had!*** *…Walking through the docs, I immediately figured out how to use Wasp and was able to make a prototype in a couple of days.”* - Chris
>
### 🥉 Richards Roadmap & Feature Voting App
<ImgWithCaption
alt="Richards Roadmap & Feature Voting App"
source="img/betathon/richard.png"
/>
- View the [App](https://droad.netlify.app/) & [GitHub Repo](https://github.com/Fecony/droad)
- Follow [Richard on Twitter](https://twitter.com/webrickony)
- 🎉 Prizes: Wasp Shirt, $75 [Railway.app](http://Railway.app) credits
> “***I liked how Wasp simplified writing query/actions*** *that are used to interact with the backend and frontend. How everything is defined and configured in wasp file and just works. Also […] login/signup was really easy to do since Wasp provides these two methods for use.”* -
>
### 🥉 Emmanuels Notes App
<ImgWithCaption
alt="Emmanuels Notes App"
source="img/betathon/emmanuel.png"
/>
- View the [GitHub Repo](https://github.com/EmmanuelTheCoder/noteapp-with-wasp)
- Follow [Emmanuel on Twitter](https://twitter.com/EmmanuelCoder)
- 🎉 Prizes: Wasp Shirt, $75 [Railway.app](http://Railway.app) credits
> *I joined the hackathon less than 48 hours before the submission deadline.* ***Wasp made it look easy because it handled the hard parts for me.*** *For example, username/password authentication took less than 7 lines of code to implement. -* excerpt from [Emmanuels Betathon Blog Post](https://dev.to/emmanuelthecoder/making-something-waspy-a-review-of-wasp-571j)
>
## Hackathon How-to
Personally, Ive never organized a hackathon before, and this was Wasps first hackathon as well, so when youre a complete newbie at something, you often look towards others for inspiration. Being admirers of the work and style of Supabase, we drew a lot of inspiration from their “[launch week](https://supabase.com/blog/launch-week-5-hackathon)” approach when preparing for our own Beta launch and hacakthon.
<ImgWithCaption
alt="Wasp Betathon Homepage"
source="img/betathon/betathonpage.png"
caption="Our dedicated hackathon landing page w/ intro video & submission form"
/>
With some good inspiration in hand, we set off to create a simple, easy-going Hackathon experience. We werent certain wed get many participants, so we decided to make the process as open as possible: *two weeks to work on any project using Wasp, alone or in a team of up to 4 people, submitted on our [Betathon Homepage](https://betathon.wasp-lang.dev/) before the deadline*. That was it.
When youre an early-stage startup, you cant offer big cash prizes, so we asked Railway if theyd be interested in sponsoring some prizes, as were big fans of their deployment and hosting platform. Luckily, they agreed (thanks, Railway 🙏🚂). It was also a great match, since we already had the documentation for deploying Wasp apps to Railway on our website, making it an obvious choice for the participants to deploy their Hackathon apps with.
<ImgWithCaption
alt="Keyboard"
source="img/betathon/keyboard.png"
caption="Disclaimer: actual prize keyboard will be cooler and waspier 😎🐝"
/>
On top of that, we decided that a cool grand prize could be a Wasp-colored mechanical keyboard. Nothing fancy, but keyboards are an item a lot of programmers love. We also threw in some Wasp beanies and shirts, and stated that wed spotlight the winners on our platforms and social media accounts.
## Promotion
For the Wasp Beta Launch Week, we were active and publicising Wasp on many platforms. We didnt outright promote the hackathon on those platforms, but we were getting a lot of incoming interest to our Website and Discord, so we made noise about it there. We posted banners on the homepage, and made announcements on Discord and Twitter that directed people to a [Beta Hacakthon homepage](https://betathon.wasp-lang.dev) we created.
The homepage was nice to have as a central spot for all the rules and relevant info. We also added a fun intro video to give the hackathon a more personal touch. I also think the effort put into making an intro video gives participants the feeling that theyre entering into a serious contest and committing to something of substance.
<ImgWithCaption
alt="Hackathon Wasp app repo"
source="img/betathon/github.png"
caption="Wanna host your own Hackathon? Use our template app!"
/>
As an extra bonus, we wrote the Betathon Homepage with Wasp, and put the [source code up on our GitHub](https://github.com/wasp-lang/wasp/tree/main/examples/hackathon). We thought it might inspire people to build with Wasp, using it as a guide while creating their own projects for the hackathon, plus it could be used by others in the future if they want to host their own hackathon. 💻
### The Response
The response overall was small but significant, considering Wasps age. We were also extremely happy with the quality of the engagement. We had thirteen participants register overall, a nice number considering we only started promoting the hackathon on the day that we announced it (this is probably something wed do differently next time)!
We also asked participants for their feedback on participating in the Hackathon, and they were all pleased with the open, straight-forward approach we took, so well most likely be repeating this for future versions. Other good signs were the many comments that participants were eager to take part in our next hackathon, as well as some dedicated new community members, which makes it all the more motivating for us. 💪
---
**A big THANK YOU again to all the participants for their hard work and feedback. Heres to the next one! 🍻**

View File

@ -4,10 +4,12 @@ import useBaseUrl from "@docusaurus/useBaseUrl";
const ImgWithCaption = (props) => { const ImgWithCaption = (props) => {
return ( return (
<div> <div>
<p align="center"> <p align='center'>
<figure> <figure>
<img style={{'width': props.width}} alt={props.alt} src={useBaseUrl(props.source)} /> <img style={{ width: props.width }} alt={props.alt} src={useBaseUrl(props.source)} />
<figcaption class="image-caption">{props.caption}</figcaption> <figcaption class='image-caption' style={{ fontStyle: 'italic', opacity: 0.6, fontSize: '0.9rem' }}>
{props.caption}
</figcaption>
</figure> </figure>
</p> </p>
</div> </div>

View File

@ -0,0 +1,5 @@
:::tip Using an external auth method?
if your app is using an external authentication method(s) supported by Wasp (such as [Google](/docs/language/features#google) or [GitHub](/docs/language/features#github)), make sure to set the necessary environment variables.
:::

View File

@ -3,6 +3,8 @@ title: Deploying
--- ---
import useBaseUrl from '@docusaurus/useBaseUrl'; import useBaseUrl from '@docusaurus/useBaseUrl';
import AddExternalAuthEnvVarsReminder from './_addExternalAuthEnvVarsReminder.md'
:::info :::info
Wasp is in beta, so keep in mind there might be some kinks / bugs, and possibly a bit bigger changes in the future. Wasp is in beta, so keep in mind there might be some kinks / bugs, and possibly a bit bigger changes in the future.
If you encounter any issues, reach out to us on [Discord](https://discord.gg/rzdnErX) and we will make sure to help you out! If you encounter any issues, reach out to us on [Discord](https://discord.gg/rzdnErX) and we will make sure to help you out!
@ -43,6 +45,8 @@ Server uses following environment variables, so you need to ensure they are set
- `WASP_WEB_CLIENT_URL` -> The URL of where the frontend app is running (e.g. `https://<app-name>.netlify.app`), which is necessary for CORS. - `WASP_WEB_CLIENT_URL` -> The URL of where the frontend app is running (e.g. `https://<app-name>.netlify.app`), which is necessary for CORS.
- `JWT_SECRET` -> You need this if you are using Wasp's `auth` feature. Set it to a random string (password), at least 32 characters long. - `JWT_SECRET` -> You need this if you are using Wasp's `auth` feature. Set it to a random string (password), at least 32 characters long.
<AddExternalAuthEnvVarsReminder />
### Deploying to Fly.io (free, recommended) ### Deploying to Fly.io (free, recommended)
Fly.io offers a variety of free services that are perfect for deploying your first Wasp app! You will need a Fly.io account and the [`flyctl` CLI](https://fly.io/docs/hands-on/install-flyctl/). Fly.io offers a variety of free services that are perfect for deploying your first Wasp app! You will need a Fly.io account and the [`flyctl` CLI](https://fly.io/docs/hands-on/install-flyctl/).
@ -99,8 +103,14 @@ Next, let's add a few more environment variables:
flyctl secrets set PORT=8080 flyctl secrets set PORT=8080
flyctl secrets set JWT_SECRET=<random_string_at_least_32_characters_long> flyctl secrets set JWT_SECRET=<random_string_at_least_32_characters_long>
flyctl secrets set WASP_WEB_CLIENT_URL=<url_of_where_frontend_will_be_deployed> flyctl secrets set WASP_WEB_CLIENT_URL=<url_of_where_frontend_will_be_deployed>
# If you are using an external auth method (Google or GitHub), make sure to add their vars too!
# flyctl secrets set GOOGLE_CLIENT_ID=<google_client_id>
# flyctl secrets set GOOGLE_CLIENT_SECRET=<google_client_secret>
``` ```
<AddExternalAuthEnvVarsReminder />
NOTE: If you do not know what your frontend URL is yet, don't worry. You can set `WASP_WEB_CLIENT_URL` after you deploy your frontend. NOTE: If you do not know what your frontend URL is yet, don't worry. You can set `WASP_WEB_CLIENT_URL` after you deploy your frontend.
If you want to make sure you've added your secrets correctly, run `flyctl secrets list` in the terminal. Note that you will see hashed versions of your secrets to protect your sensitive data. If you want to make sure you've added your secrets correctly, run `flyctl secrets list` in the terminal. Note that you will see hashed versions of your secrets to protect your sensitive data.
@ -156,8 +166,13 @@ heroku create <app-name>
Unless you have external Postgres database that you want to use, let's create new database on Heroku and attach it to our app: Unless you have external Postgres database that you want to use, let's create new database on Heroku and attach it to our app:
``` ```
heroku addons:create --app <app-name> heroku-postgresql:hobby-dev heroku addons:create --app <app-name> heroku-postgresql:mini
``` ```
:::caution
Heroku does not offer a free plan anymore and `mini` is their cheapest database instance - it costs $5/mo.
:::
Heroku will also set `DATABASE_URL` env var for us at this point. If you are using external database, you will have to set it yourself. Heroku will also set `DATABASE_URL` env var for us at this point. If you are using external database, you will have to set it yourself.
@ -375,6 +390,8 @@ Go to the server instance's `Settings` tab, and click `Generate Domain`. Do the
The Postgres database is already initialized with a domain, so click on the Postgres instance, go to the **Connect** tab and copy the `Postgres Connection URL`. The Postgres database is already initialized with a domain, so click on the Postgres instance, go to the **Connect** tab and copy the `Postgres Connection URL`.
Go back to your `server` instance and navigate to its `Variables` tab. Now add the copied Postgres URL as `DATABASE_URL`, as well as the client's domain as `WASP_WEB_CLIENT_URL`. Go back to your `server` instance and navigate to its `Variables` tab. Now add the copied Postgres URL as `DATABASE_URL`, as well as the client's domain as `WASP_WEB_CLIENT_URL`.
<AddExternalAuthEnvVarsReminder />
Next, copy the server's domain, move over to the client's `Variables` tab and add the generated server domain as a new variable called `REACT_APP_API_URL`. Next, copy the server's domain, move over to the client's `Variables` tab and add the generated server domain as a new variable called `REACT_APP_API_URL`.

View File

@ -127,7 +127,7 @@ module.exports = {
'@docusaurus/preset-classic', '@docusaurus/preset-classic',
{ {
gtag: { gtag: {
trackingID: 'G-3ZEDH3BVGE', trackingID: 'GTM-WJX89HZ',
anonymizeIP: true, anonymizeIP: true,
}, },
docs: { docs: {

View File

@ -13,7 +13,7 @@ const examples = [
authorImg: 'https://avatars.githubusercontent.com/u/55102317', authorImg: 'https://avatars.githubusercontent.com/u/55102317',
repoName: "waspello-example-app", repoName: "waspello-example-app",
repoUrl: "https://github.com/wasp-lang/wasp/tree/main/examples/waspello", repoUrl: "https://github.com/wasp-lang/wasp/tree/main/examples/waspello",
//demoUrl: "https://waspello.netlify.app/", demoUrl: "https://waspello-demo.netlify.app/",
// todo: try in GitPod/Replit url // todo: try in GitPod/Replit url
}, },
{ {
@ -122,9 +122,12 @@ const ExampleCard = (props) => (
<div className='mt-3 flex items-center gap-2'> <div className='mt-3 flex items-center gap-2'>
<SeeTheCodeButton repoUrl={props.repoUrl} /> <SeeTheCodeButton repoUrl={props.repoUrl} />
{props.demoUrl && ( {/* Demo apps are not mobile-friendly yet so hiding them on mobile for now. */}
<DemoButton demoUrl={props.demoUrl} /> <span className='hidden md:block'>
)} {props.demoUrl && (
<DemoButton demoUrl={props.demoUrl} />
)}
</span>
</div> </div>
</div> </div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 KiB