mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-12-24 17:44:21 +03:00
Added TodoApp tutorial example.
This commit is contained in:
parent
bbe6dae679
commit
b480420690
@ -1,6 +1,6 @@
|
||||
|
||||
.PHONY: buildTodoApp
|
||||
buildTodoApp:
|
||||
buildTodoApp:
|
||||
cd todoApp/out && npm install && npm run-script build
|
||||
|
||||
.PHONY: deploy
|
||||
|
@ -2,7 +2,7 @@ Each example has `src/` and `out/` directory, where `out/` is code generated by
|
||||
|
||||
Examples are deployed to https://examples.wasp-lang.dev.
|
||||
|
||||
NOTE: Right now, there is only one example, and it is deployed directly to https://examples.wasp-lang.dev.
|
||||
NOTE: Right now, only todoApp/ is deployed, and it is deployed directly to https://examples.wasp-lang.dev.
|
||||
Plan is, when we will have more examples, to deploy them next to each other, each in its own directory, so they would be accessed as https://examples.wasp-lang.dev/todoApp and so on.
|
||||
But, for that we need support in Wasp for the scenario where frontend is not hosted at server root, which we don't support yet.
|
||||
|
||||
|
1
examples/tutorials/TodoApp/.gitignore
vendored
Normal file
1
examples/tutorials/TodoApp/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/.wasp/
|
1
examples/tutorials/TodoApp/.wasproot
Normal file
1
examples/tutorials/TodoApp/.wasproot
Normal file
@ -0,0 +1 @@
|
||||
File marking the root of Wasp project.
|
19
examples/tutorials/TodoApp/ext/Clocks.js
Normal file
19
examples/tutorials/TodoApp/ext/Clocks.js
Normal file
@ -0,0 +1,19 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import Clock from 'react-clock'
|
||||
import 'react-clock/dist/Clock.css'
|
||||
|
||||
export default () => {
|
||||
const [time, setTime] = useState(new Date())
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => setTime(new Date()), 1000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex' }}>
|
||||
<Clock value={time} />
|
||||
<Clock value={new Date(time.getTime() + 60 * 60000)} />
|
||||
</div>
|
||||
)
|
||||
}
|
81
examples/tutorials/TodoApp/ext/MainPage.js
Normal file
81
examples/tutorials/TodoApp/ext/MainPage.js
Normal file
@ -0,0 +1,81 @@
|
||||
import React, { useState } from 'react'
|
||||
|
||||
import { useQuery } from '@wasp/queries'
|
||||
import getTasks from '@wasp/queries/getTasks'
|
||||
import createTask from '@wasp/actions/createTask'
|
||||
import updateTask from '@wasp/actions/updateTask'
|
||||
import Clocks from './Clocks'
|
||||
|
||||
const MainPage = () => {
|
||||
const { data: tasks, isFetching, error } = useQuery(getTasks)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<NewTaskForm />
|
||||
|
||||
{tasks && <TasksList tasks={tasks} />}
|
||||
|
||||
<p> <Clocks /> </p>
|
||||
|
||||
{isFetching && 'Fetching...'}
|
||||
{error && 'Error: ' + error}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Task = (props) => {
|
||||
const handleIsDoneChange = async (event) => {
|
||||
try {
|
||||
await updateTask({
|
||||
taskId: props.task.id,
|
||||
data: { isDone: event.target.checked }
|
||||
})
|
||||
} catch (error) {
|
||||
window.alert('Error while updating task: ' + error.message)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
type='checkbox' id={props.task.id}
|
||||
checked={props.task.isDone} readonly
|
||||
onChange={handleIsDoneChange}
|
||||
/>
|
||||
{props.task.description}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const TasksList = (props) => {
|
||||
if (!props.tasks?.length) return 'No tasks'
|
||||
return props.tasks.map((task, idx) => <Task task={task} key={idx} />)
|
||||
}
|
||||
|
||||
const NewTaskForm = (props) => {
|
||||
const defaultDescription = ''
|
||||
const [description, setDescription] = useState(defaultDescription)
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault()
|
||||
try {
|
||||
await createTask({ description })
|
||||
setDescription(defaultDescription)
|
||||
} catch (err) {
|
||||
window.alert('Error: ' + err.message)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
type='text'
|
||||
value={description}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
/>
|
||||
<input type='submit' value='Create task' />
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default MainPage
|
12
examples/tutorials/TodoApp/ext/actions.js
Normal file
12
examples/tutorials/TodoApp/ext/actions.js
Normal file
@ -0,0 +1,12 @@
|
||||
export const createTask = async ({ description }, context) => {
|
||||
return context.entities.Task.create({
|
||||
data: { description }
|
||||
})
|
||||
}
|
||||
|
||||
export const updateTask = async (args, context) => {
|
||||
return context.entities.Task.update({
|
||||
where: { id: args.taskId },
|
||||
data: args.data
|
||||
})
|
||||
}
|
4
examples/tutorials/TodoApp/ext/queries.js
Normal file
4
examples/tutorials/TodoApp/ext/queries.js
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
export const getTasks = async (args, context) => {
|
||||
return context.entities.Task.findMany({})
|
||||
}
|
33
examples/tutorials/TodoApp/main.wasp
Normal file
33
examples/tutorials/TodoApp/main.wasp
Normal file
@ -0,0 +1,33 @@
|
||||
app TodoApp {
|
||||
title: "TodoApp"
|
||||
}
|
||||
|
||||
route "/" -> page Main
|
||||
page Main {
|
||||
component: import Main from "@ext/MainPage.js"
|
||||
}
|
||||
|
||||
entityPSL Task {=psl
|
||||
id Int @id @default(autoincrement())
|
||||
description String
|
||||
isDone Boolean @default(false)
|
||||
psl=}
|
||||
|
||||
query getTasks {
|
||||
fn: import { getTasks } from "@ext/queries.js",
|
||||
entities: [Task]
|
||||
}
|
||||
|
||||
action createTask {
|
||||
fn: import { createTask } from "@ext/actions.js",
|
||||
entities: [Task]
|
||||
}
|
||||
|
||||
action updateTask {
|
||||
fn: import { updateTask } from "@ext/actions.js",
|
||||
entities: [Task]
|
||||
}
|
||||
|
||||
dependencies {=json
|
||||
"react-clock": "3.0.0"
|
||||
json=}
|
@ -0,0 +1,43 @@
|
||||
# Migration `20201008211343-added-task`
|
||||
|
||||
This migration has been generated by Martin Sosic at 10/8/2020, 11:13:43 PM.
|
||||
You can check out the [state of the schema](./schema.prisma) after the migration.
|
||||
|
||||
## Database Steps
|
||||
|
||||
```sql
|
||||
CREATE TABLE "Task" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"description" TEXT NOT NULL,
|
||||
"isDone" BOOLEAN NOT NULL DEFAULT false
|
||||
)
|
||||
```
|
||||
|
||||
## Changes
|
||||
|
||||
```diff
|
||||
diff --git schema.prisma schema.prisma
|
||||
migration ..20201008211343-added-task
|
||||
--- datamodel.dml
|
||||
+++ datamodel.dml
|
||||
@@ -1,0 +1,17 @@
|
||||
+
|
||||
+datasource db {
|
||||
+ provider = "sqlite"
|
||||
+ url = "***"
|
||||
+}
|
||||
+
|
||||
+generator client {
|
||||
+ provider = "prisma-client-js"
|
||||
+ output = "../server/node_modules/.prisma/client"
|
||||
+}
|
||||
+
|
||||
+model Task {
|
||||
+ id Int @id @default(autoincrement())
|
||||
+ description String
|
||||
+ isDone Boolean @default(false)
|
||||
+}
|
||||
+
|
||||
```
|
||||
|
||||
|
@ -0,0 +1,17 @@
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = "***"
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
output = "../server/node_modules/.prisma/client"
|
||||
}
|
||||
|
||||
model Task {
|
||||
id Int @id @default(autoincrement())
|
||||
description String
|
||||
isDone Boolean @default(false)
|
||||
}
|
||||
|
@ -0,0 +1,113 @@
|
||||
{
|
||||
"version": "0.3.14-fixed",
|
||||
"steps": [
|
||||
{
|
||||
"tag": "CreateSource",
|
||||
"source": "db"
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Source",
|
||||
"source": "db"
|
||||
},
|
||||
"argument": "provider",
|
||||
"value": "\"sqlite\""
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Source",
|
||||
"source": "db"
|
||||
},
|
||||
"argument": "url",
|
||||
"value": "\"***\""
|
||||
},
|
||||
{
|
||||
"tag": "CreateModel",
|
||||
"model": "Task"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Task",
|
||||
"field": "id",
|
||||
"type": "Int",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Task",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Task",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Task",
|
||||
"field": "id"
|
||||
},
|
||||
"directive": "default"
|
||||
},
|
||||
"argument": "",
|
||||
"value": "autoincrement()"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Task",
|
||||
"field": "description",
|
||||
"type": "String",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateField",
|
||||
"model": "Task",
|
||||
"field": "isDone",
|
||||
"type": "Boolean",
|
||||
"arity": "Required"
|
||||
},
|
||||
{
|
||||
"tag": "CreateDirective",
|
||||
"location": {
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Task",
|
||||
"field": "isDone"
|
||||
},
|
||||
"directive": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "CreateArgument",
|
||||
"location": {
|
||||
"tag": "Directive",
|
||||
"path": {
|
||||
"tag": "Field",
|
||||
"model": "Task",
|
||||
"field": "isDone"
|
||||
},
|
||||
"directive": "default"
|
||||
},
|
||||
"argument": "",
|
||||
"value": "false"
|
||||
}
|
||||
]
|
||||
}
|
3
examples/tutorials/TodoApp/migrations/migrate.lock
Normal file
3
examples/tutorials/TodoApp/migrations/migrate.lock
Normal file
@ -0,0 +1,3 @@
|
||||
# Prisma Migrate lockfile v1
|
||||
|
||||
20201008211343-added-task
|
Loading…
Reference in New Issue
Block a user