From 5f63c76a3807e677e24485b2bca49e5c5c35236d Mon Sep 17 00:00:00 2001 From: vincanger <70215737+vincanger@users.noreply.github.com> Date: Tue, 20 Dec 2022 11:11:13 +0100 Subject: [PATCH] add todo app w/ typescript --- examples/todo-typescript/.gitignore | 3 + examples/todo-typescript/.wasproot | 1 + examples/todo-typescript/main.wasp | 67 +++++++++++++ .../20221219114539_init/migration.sql | 18 ++++ .../migrations/migration_lock.toml | 3 + examples/todo-typescript/src/.waspignore | 3 + .../todo-typescript/src/client/LoginPage.tsx | 19 ++++ examples/todo-typescript/src/client/Main.css | 53 ++++++++++ .../todo-typescript/src/client/MainPage.tsx | 94 ++++++++++++++++++ .../todo-typescript/src/client/SignupPage.tsx | 19 ++++ .../src/client/react-app-env.d.ts | 60 +++++++++++ .../todo-typescript/src/client/tsconfig.json | 55 ++++++++++ examples/todo-typescript/src/client/types.ts | 6 ++ .../todo-typescript/src/client/waspLogo.png | Bin 0 -> 24877 bytes .../todo-typescript/src/server/actions.ts | 33 ++++++ .../todo-typescript/src/server/queries.ts | 9 ++ .../todo-typescript/src/server/serverTypes.ts | 11 ++ .../todo-typescript/src/server/tsconfig.json | 48 +++++++++ .../todo-typescript/src/shared/tsconfig.json | 28 ++++++ 19 files changed, 530 insertions(+) create mode 100644 examples/todo-typescript/.gitignore create mode 100644 examples/todo-typescript/.wasproot create mode 100644 examples/todo-typescript/main.wasp create mode 100644 examples/todo-typescript/migrations/20221219114539_init/migration.sql create mode 100644 examples/todo-typescript/migrations/migration_lock.toml create mode 100644 examples/todo-typescript/src/.waspignore create mode 100644 examples/todo-typescript/src/client/LoginPage.tsx create mode 100644 examples/todo-typescript/src/client/Main.css create mode 100644 examples/todo-typescript/src/client/MainPage.tsx create mode 100644 examples/todo-typescript/src/client/SignupPage.tsx create mode 100644 examples/todo-typescript/src/client/react-app-env.d.ts create mode 100644 examples/todo-typescript/src/client/tsconfig.json create mode 100644 examples/todo-typescript/src/client/types.ts create mode 100644 examples/todo-typescript/src/client/waspLogo.png create mode 100644 examples/todo-typescript/src/server/actions.ts create mode 100644 examples/todo-typescript/src/server/queries.ts create mode 100644 examples/todo-typescript/src/server/serverTypes.ts create mode 100644 examples/todo-typescript/src/server/tsconfig.json create mode 100644 examples/todo-typescript/src/shared/tsconfig.json diff --git a/examples/todo-typescript/.gitignore b/examples/todo-typescript/.gitignore new file mode 100644 index 000000000..c51177f6d --- /dev/null +++ b/examples/todo-typescript/.gitignore @@ -0,0 +1,3 @@ +/.wasp/ +/.env.server +/.env.client diff --git a/examples/todo-typescript/.wasproot b/examples/todo-typescript/.wasproot new file mode 100644 index 000000000..ca2cfdb48 --- /dev/null +++ b/examples/todo-typescript/.wasproot @@ -0,0 +1 @@ +File marking the root of Wasp project. diff --git a/examples/todo-typescript/main.wasp b/examples/todo-typescript/main.wasp new file mode 100644 index 000000000..08d34e399 --- /dev/null +++ b/examples/todo-typescript/main.wasp @@ -0,0 +1,67 @@ +app VideoTodoNotifier { + 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 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" +} + +query getTasks { + // We specify the JS implementation of our query (which is an async JS function) + // which can be found in `server/queries.js` as a named export `getTasks`. + 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] +} diff --git a/examples/todo-typescript/migrations/20221219114539_init/migration.sql b/examples/todo-typescript/migrations/20221219114539_init/migration.sql new file mode 100644 index 000000000..7b6262767 --- /dev/null +++ b/examples/todo-typescript/migrations/20221219114539_init/migration.sql @@ -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"); diff --git a/examples/todo-typescript/migrations/migration_lock.toml b/examples/todo-typescript/migrations/migration_lock.toml new file mode 100644 index 000000000..e5e5c4705 --- /dev/null +++ b/examples/todo-typescript/migrations/migration_lock.toml @@ -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" \ No newline at end of file diff --git a/examples/todo-typescript/src/.waspignore b/examples/todo-typescript/src/.waspignore new file mode 100644 index 000000000..1c432f30d --- /dev/null +++ b/examples/todo-typescript/src/.waspignore @@ -0,0 +1,3 @@ +# Ignore editor tmp files +**/*~ +**/#*# diff --git a/examples/todo-typescript/src/client/LoginPage.tsx b/examples/todo-typescript/src/client/LoginPage.tsx new file mode 100644 index 000000000..8adb11ae0 --- /dev/null +++ b/examples/todo-typescript/src/client/LoginPage.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import LoginForm from '@wasp/auth/forms/Login'; + +const LoginPage = () => { + return ( +
+

Login

+ {/** Wasp has built-in auth forms & flows, which you can also opt-out of, if you wish :) */} + +
+ + I don't have an account yet (go to signup). + +
+ ); +}; + +export default LoginPage; diff --git a/examples/todo-typescript/src/client/Main.css b/examples/todo-typescript/src/client/Main.css new file mode 100644 index 000000000..5c086b1dc --- /dev/null +++ b/examples/todo-typescript/src/client/Main.css @@ -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; +} diff --git a/examples/todo-typescript/src/client/MainPage.tsx b/examples/todo-typescript/src/client/MainPage.tsx new file mode 100644 index 000000000..70cdeaeed --- /dev/null +++ b/examples/todo-typescript/src/client/MainPage.tsx @@ -0,0 +1,94 @@ +import './Main.css'; +import React 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' + +const MainPage = () => { + const { data: user } = useAuth(); + const { data: tasks, isLoading, error } = useQuery(getTasks); + + React.useEffect(() => { + console.log(user); + }, [user]); + + if (isLoading) return 'Loading...'; + if (error) return 'Error: ' + error; + + return ( +
+ wasp logo +

+ {user.username} + {`'s tasks :)`} +

+ + {tasks && } + +
+ ); +}; + +const Todo = ({task, number}: {task: Task, number: number}) => { + const handleIsDoneChange = async (event: React.ChangeEvent) => { + try { + await updateTask({ + taskId: task.id, + isDone: event.currentTarget.checked, + }); + } catch (err: any) { + window.alert('Error while updating task: ' + err?.message); + } + }; + + return ( +
+ + {number + 1} + {''} + + + {task.description}{' '} +
+ ); +}; + +const TasksList = ({tasks}: { tasks: Task[] }) => { + if (tasks.length === 0) return

No tasks yet.

; + return ( +
+ {tasks.map((tsk, idx) => ( + + ))} +
+ ); +}; + +const NewTaskForm = () => { + const handleSubmit = async (event: React.FormEvent) => { + 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 ( +
+ + +
+ ); +}; + +export default MainPage; diff --git a/examples/todo-typescript/src/client/SignupPage.tsx b/examples/todo-typescript/src/client/SignupPage.tsx new file mode 100644 index 000000000..8a8e8e7fe --- /dev/null +++ b/examples/todo-typescript/src/client/SignupPage.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import SignupForm from '@wasp/auth/forms/Signup'; + +const SignupPage = () => { + return ( +
+

Sign Up

+ {/** Wasp has built-in auth forms & flows, which you can also opt-out of, if you wish :) */} + +
+ + I already have an account (go to login). + +
+ ); +}; + +export default SignupPage; diff --git a/examples/todo-typescript/src/client/react-app-env.d.ts b/examples/todo-typescript/src/client/react-app-env.d.ts new file mode 100644 index 000000000..e80934ce3 --- /dev/null +++ b/examples/todo-typescript/src/client/react-app-env.d.ts @@ -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 & { 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; +} diff --git a/examples/todo-typescript/src/client/tsconfig.json b/examples/todo-typescript/src/client/tsconfig.json new file mode 100644 index 000000000..d501a4193 --- /dev/null +++ b/examples/todo-typescript/src/client/tsconfig.json @@ -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" + ], +} \ No newline at end of file diff --git a/examples/todo-typescript/src/client/types.ts b/examples/todo-typescript/src/client/types.ts new file mode 100644 index 000000000..7739835bb --- /dev/null +++ b/examples/todo-typescript/src/client/types.ts @@ -0,0 +1,6 @@ +export type Task = { + id: number; + description: string; + isDone: boolean; + userId: number | null; +}; diff --git a/examples/todo-typescript/src/client/waspLogo.png b/examples/todo-typescript/src/client/waspLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..d39a9443a8153b158b76f51dda2e42f3b34a9169 GIT binary patch literal 24877 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 { + 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'] }; + +export const updateTask = async ({ taskId, isDone }: updateArgs, context: Context) => { + if (!context.user) { + throw new HttpError(401); + } + + return context.entities.Task.updateMany({ + where: { + id: taskId, + user: { id: context.user.id }, + }, + data: { isDone }, + }); +}; diff --git a/examples/todo-typescript/src/server/queries.ts b/examples/todo-typescript/src/server/queries.ts new file mode 100644 index 000000000..dab9b76a9 --- /dev/null +++ b/examples/todo-typescript/src/server/queries.ts @@ -0,0 +1,9 @@ +import HttpError from '@wasp/core/HttpError.js'; +import { Context, Task } from './serverTypes' + +export const getTasks = async (args: null, context: Context): Promise => { + if (!context.user) { + throw new HttpError(401); + } + return context.entities.Task.findMany({ where: { user: { id: context.user.id } } }); +}; diff --git a/examples/todo-typescript/src/server/serverTypes.ts b/examples/todo-typescript/src/server/serverTypes.ts new file mode 100644 index 000000000..870f56eca --- /dev/null +++ b/examples/todo-typescript/src/server/serverTypes.ts @@ -0,0 +1,11 @@ +import { Task as TaskType, User, Prisma } from '@prisma/client'; + +export type Task = TaskType; + +export type Context = { + user: User; + entities: { + Task: Prisma.TaskDelegate<{}>; + User: Prisma.UserDelegate<{}>; + }; +}; diff --git a/examples/todo-typescript/src/server/tsconfig.json b/examples/todo-typescript/src/server/tsconfig.json new file mode 100644 index 000000000..70a79b44e --- /dev/null +++ b/examples/todo-typescript/src/server/tsconfig.json @@ -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" + ], +} \ No newline at end of file diff --git a/examples/todo-typescript/src/shared/tsconfig.json b/examples/todo-typescript/src/shared/tsconfig.json new file mode 100644 index 000000000..f78b58a77 --- /dev/null +++ b/examples/todo-typescript/src/shared/tsconfig.json @@ -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"], + } +}