Skip to content

Commit d6b55b4

Browse files
authored
Update todo-typescript example for 0.11.6 (#1167)
1 parent 799ff1d commit d6b55b4

File tree

10 files changed

+150
-91
lines changed

10 files changed

+150
-91
lines changed

examples/todo-typescript/main.wasp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ app TodoTypescript {
77
auth: {
88
userEntity: User,
99
methods: {
10-
usernameAndPassword: {},
10+
usernameAndPassword: {}, // this is a very naive implementation, use 'email' in production instead
11+
//google: {}, //https://wasp-lang.dev/docs/integrations/google
12+
//gitHub: {}, //https://wasp-lang.dev/docs/integrations/github
13+
//email: {} //https://wasp-lang.dev/docs/guides/email-auth
1114
},
1215
onAuthFailedRedirectTo: "/login",
1316
}
@@ -24,11 +27,11 @@ entity User {=psl
2427
psl=}
2528

2629
entity Task {=psl
27-
id Int @id @default(autoincrement())
28-
description String
29-
isDone Boolean @default(false)
30-
user User? @relation(fields: [userId], references: [id])
31-
userId Int?
30+
id Int @id @default(autoincrement())
31+
description String
32+
isDone Boolean @default(false)
33+
user User @relation(fields: [userId], references: [id])
34+
userId Int
3235
psl=}
3336

3437
route RootRoute { path: "/", to: MainPage }
@@ -66,3 +69,8 @@ action updateTask {
6669
fn: import { updateTask } from "@server/actions.js",
6770
entities: [Task]
6871
}
72+
73+
action deleteTasks {
74+
fn: import { deleteTasks } from "@server/actions.js",
75+
entities: [Task],
76+
}

examples/todo-typescript/migrations/20221219114539_init/migration.sql renamed to examples/todo-typescript/migrations/20231012121747_initial/migration.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ CREATE TABLE "Task" (
1010
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
1111
"description" TEXT NOT NULL,
1212
"isDone" BOOLEAN NOT NULL DEFAULT false,
13-
"userId" INTEGER,
14-
CONSTRAINT "Task_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE
13+
"userId" INTEGER NOT NULL,
14+
CONSTRAINT "Task_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
1515
);
1616

1717
-- CreateIndex
Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
import { Link } from 'react-router-dom';
2-
import { LoginForm } from '@wasp/auth/forms/Login';
1+
import { Link } from "react-router-dom";
2+
import { LoginForm } from "@wasp/auth/forms/Login";
33

44
export function LoginPage() {
55
return (
66
<main>
7-
<h1>Login</h1>
8-
{/** Wasp has built-in auth forms & flows, which you can also opt-out of, if you wish :) */}
7+
{/** Wasp has built-in auth forms & flows, which you can customize or opt-out of, if you wish :)
8+
* https://wasp-lang.dev/docs/guides/auth-ui
9+
*/}
910
<LoginForm />
1011
<br />
1112
<span>
12-
I don't have an account yet (<Link to='/signup'>go to signup</Link>).
13+
I don't have an account yet (<Link to="/signup">go to signup</Link>).
1314
</span>
1415
</main>
1516
);
16-
};
17+
}

examples/todo-typescript/src/client/Main.css

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ img {
2626
max-height: 100px;
2727
}
2828

29-
button {
29+
.logout {
3030
margin-top: 1rem;
3131
}
3232

@@ -43,11 +43,31 @@ code {
4343
font-size: 1.2rem;
4444
}
4545

46+
.buttons {
47+
display: flex;
48+
flex-direction: row;
49+
width: 300px;
50+
justify-content: space-between;
51+
}
52+
4653
.tasklist {
4754
display: flex;
4855
flex-direction: column;
4956
align-items: flex-start;
5057
justify-content: center;
5158
width: 300px;
5259
margin-top: 1rem;
60+
padding: 0
61+
}
62+
63+
li {
64+
width: 100%;
65+
}
66+
67+
.todo-item {
68+
width: 100%;
69+
display: flex;
70+
flex-direction: row;
71+
justify-content: space-between;
72+
align-items: center;
5373
}
Lines changed: 61 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,109 @@
1-
import './Main.css';
2-
import React, { useEffect, FormEventHandler, FormEvent } from 'react';
3-
import logout from '@wasp/auth/logout';
4-
import useAuth from '@wasp/auth/useAuth';
5-
import { useQuery } from '@wasp/queries'; // Wasp uses a thin wrapper around react-query
6-
import getTasks from '@wasp/queries/getTasks';
7-
import createTask from '@wasp/actions/createTask';
8-
import updateTask from '@wasp/actions/updateTask';
9-
import waspLogo from './waspLogo.png';
10-
import { Task } from './types'
1+
import React, { FormEventHandler, FormEvent } from "react";
2+
import waspLogo from "./waspLogo.png";
113

12-
export function MainPage() {
13-
const { data: user } = useAuth();
14-
const { data: tasks, isLoading, error } = useQuery<unknown, Task[]>(getTasks);
4+
import "./Main.css";
5+
// Wasp imports 🐝 = }
6+
import logout from "@wasp/auth/logout";
7+
import { useQuery } from "@wasp/queries"; // Wasp uses a thin wrapper around react-query
8+
import getTasks from "@wasp/queries/getTasks";
9+
import createTask from "@wasp/actions/createTask";
10+
import updateTask from "@wasp/actions/updateTask";
11+
import deleteTasks from "@wasp/actions/deleteTasks";
12+
import type { Task, User } from "@wasp/entities";
1513

16-
useEffect(() => {
17-
console.log(user);
18-
}, [user]);
14+
export const MainPage = ({ user }: { user: User }) => {
15+
const { data: tasks, isLoading, error } = useQuery(getTasks);
1916

20-
if (isLoading) return 'Loading...';
21-
if (error) return 'Error: ' + error;
17+
if (isLoading) return "Loading...";
18+
if (error) return "Error: " + error;
19+
20+
const completed = tasks?.filter((task) => task.isDone).map((task) => task.id);
2221

2322
return (
2423
<main>
25-
<img src={waspLogo} alt='wasp logo' />
26-
<h1>
27-
{user.username}
28-
{`'s tasks :)`}
29-
</h1>
24+
<img src={waspLogo} alt="wasp logo" />
25+
{user && (
26+
<h1>
27+
{user.username}
28+
{`'s tasks :)`}
29+
</h1>
30+
)}
3031
<NewTaskForm />
31-
{tasks && <TasksList tasks={tasks} /> }
32-
<button onClick={logout}> Logout </button>
32+
{tasks && <TasksList tasks={tasks} />}
33+
<div className="buttons">
34+
<button
35+
className="logout"
36+
onClick={() => void deleteTasks(completed ?? [])}
37+
>
38+
Delete completed
39+
</button>
40+
<button className="logout" onClick={logout}>
41+
Logout
42+
</button>
43+
</div>
3344
</main>
3445
);
3546
};
3647

3748
function Todo({ id, isDone, description }: Task) {
38-
const handleIsDoneChange: FormEventHandler<HTMLInputElement> = async (event) => {
49+
const handleIsDoneChange: FormEventHandler<HTMLInputElement> = async (
50+
event
51+
) => {
3952
try {
4053
await updateTask({
4154
id,
4255
isDone: event.currentTarget.checked,
4356
});
4457
} catch (err: any) {
45-
window.alert('Error while updating task ' + err?.message);
58+
window.alert("Error while updating task " + err?.message);
4659
}
47-
}
60+
};
4861

4962
return (
5063
<li>
51-
<input type='checkbox' id={id.toString()} checked={isDone} onChange={handleIsDoneChange} />
52-
<span>{description}</span>
64+
<span className="todo-item">
65+
<input
66+
type="checkbox"
67+
id={id.toString()}
68+
checked={isDone}
69+
onChange={handleIsDoneChange}
70+
/>
71+
<span>{description}</span>
72+
<button onClick={() => void deleteTasks([id])}>Delete</button>
73+
</span>
5374
</li>
5475
);
55-
};
76+
}
5677

57-
function TasksList({tasks}: { tasks: Task[] }) {
78+
function TasksList({ tasks }: { tasks: Task[] }) {
5879
if (tasks.length === 0) return <p>No tasks yet.</p>;
5980
return (
60-
<ol className='tasklist'>
81+
<ol className="tasklist">
6182
{tasks.map((task, idx) => (
6283
<Todo {...task} key={idx} />
6384
))}
6485
</ol>
6586
);
66-
};
87+
}
6788

6889
function NewTaskForm() {
6990
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
7091
event.preventDefault();
71-
92+
7293
try {
7394
const description = event.currentTarget.description.value;
74-
console.log(description)
95+
console.log(description);
7596
event.currentTarget.reset();
7697
await createTask({ description });
7798
} catch (err: any) {
78-
window.alert('Error: ' + err?.message);
99+
window.alert("Error: " + err?.message);
79100
}
80101
};
81102

82103
return (
83104
<form onSubmit={handleSubmit}>
84-
<input name='description' type='text' defaultValue='' />
85-
<input type='submit' value='Create task' />
105+
<input name="description" type="text" defaultValue="" />
106+
<input type="submit" value="Create task" />
86107
</form>
87108
);
88-
};
109+
}
Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
import { Link } from 'react-router-dom';
2-
import { SignupForm } from '@wasp/auth/forms/Signup';
1+
import { Link } from "react-router-dom";
2+
import { SignupForm } from "@wasp/auth/forms/Signup";
33

44
export function SignupPage() {
55
return (
66
<main>
7-
<h1>Sign Up</h1>
8-
{/** Wasp has built-in auth forms & flows, which you can also opt-out of, if you wish :) */}
7+
{/** Wasp has built-in auth forms & flows, which you can customize or opt-out of, if you wish :)
8+
* https://wasp-lang.dev/docs/guides/auth-ui
9+
*/}
910
<SignupForm />
1011
<br />
1112
<span>
12-
I already have an account (<Link to='/login'>go to login</Link>).
13+
I already have an account (<Link to="/login">go to login</Link>).
1314
</span>
1415
</main>
1516
);
16-
};
17+
}

examples/todo-typescript/src/client/types.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import HttpError from '@wasp/core/HttpError.js';
2-
import { Context, Task } from './serverTypes'
1+
import HttpError from "@wasp/core/HttpError.js";
2+
import type { CreateTask, UpdateTask, DeleteTasks } from "@wasp/actions/types";
3+
import type { Task } from "@wasp/entities";
34

4-
type CreateArgs = Pick<Task, 'description'>;
5+
type CreateArgs = Pick<Task, "description">;
56

6-
export async function createTask({ description }: CreateArgs, context: Context) {
7+
export const createTask: CreateTask<CreateArgs, Task> = async (
8+
{ description },
9+
context
10+
) => {
711
if (!context.user) {
812
throw new HttpError(401);
913
}
@@ -16,18 +20,33 @@ export async function createTask({ description }: CreateArgs, context: Context)
1620
});
1721
};
1822

19-
type UpdateArgs = Pick<Task, 'id' | 'isDone'>;
23+
type UpdateArgs = Pick<Task, "id" | "isDone">;
2024

21-
export async function updateTask({ id, isDone }: UpdateArgs, context: Context) {
25+
export const updateTask: UpdateTask<UpdateArgs> = async (
26+
{ id, isDone },
27+
context
28+
) => {
2229
if (!context.user) {
2330
throw new HttpError(401);
2431
}
2532

26-
return context.entities.Task.updateMany({
33+
return context.entities.Task.update({
2734
where: {
2835
id,
29-
user: { id: context.user.id },
3036
},
3137
data: { isDone },
3238
});
3339
};
40+
41+
export const deleteTasks: DeleteTasks<Task["id"][]> = async (
42+
idsToDelete,
43+
context
44+
) => {
45+
return context.entities.Task.deleteMany({
46+
where: {
47+
id: {
48+
in: idsToDelete,
49+
},
50+
},
51+
});
52+
};
Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1-
import HttpError from '@wasp/core/HttpError.js';
2-
import { Context, Task } from './serverTypes'
1+
import HttpError from "@wasp/core/HttpError.js";
2+
import type { GetTasks } from "@wasp/queries/types";
3+
import type { Task } from "@wasp/entities";
34

4-
export async function getTasks(args: unknown, context: Context): Promise<Task[]> {
5+
//Using TypeScript's new 'satisfies' keyword, it will infer the types of the arguments and return value
6+
export const getTasks = ((_args, context) => {
57
if (!context.user) {
68
throw new HttpError(401);
79
}
8-
return context.entities.Task.findMany({ where: { user: { id: context.user.id } } });
9-
};
10+
11+
return context.entities.Task.findMany({
12+
where: { user: { id: context.user.id } },
13+
orderBy: { id: "asc" },
14+
});
15+
}) satisfies GetTasks<void, Task[]>;

examples/todo-typescript/src/server/serverTypes.ts

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)