diff --git a/examples/realworld/ext/MainPage.js b/examples/realworld/ext/MainPage.js index 2f5768b82..924adb8a4 100644 --- a/examples/realworld/ext/MainPage.js +++ b/examples/realworld/ext/MainPage.js @@ -2,7 +2,6 @@ import React from 'react' import { Link } from 'react-router-dom' import useAuth from '@wasp/auth/useAuth.js' -import logout from '@wasp/auth/logout.js' const MainPage = () => { const { data: user } = useAuth() @@ -19,9 +18,8 @@ const UserWidget = (props) => { // TODO: Make links lead somewhere. return (
- Settings - { props.user.username } - + Settings + { props.user.username }
) } else { diff --git a/examples/realworld/ext/UserProfilePage.js b/examples/realworld/ext/UserProfilePage.js new file mode 100644 index 000000000..1b4747396 --- /dev/null +++ b/examples/realworld/ext/UserProfilePage.js @@ -0,0 +1,38 @@ +import React, { useState } from 'react' +import { Link, useHistory } from 'react-router-dom' + +import useAuth from '@wasp/auth/useAuth.js' +import logout from '@wasp/auth/logout.js' +import updateUser from '@wasp/actions/updateUser' +import getUser from '@wasp/queries/getUser' +import { useQuery } from '@wasp/queries' + +const UserProfilePage = (props) => { + const history = useHistory() + + const username = props.match.params.username + const { data: user, error: userError } = useQuery(getUser, { username }) + + console.log(user, userError) + if (userError) { + console.log(userError) + history.push("/") + } + + // TODO: There is no navbar right now, but there should be one! It is only on Main page. + // I wish I had a "layout" mechanism that I would apply on certain pages + // and they would all have this same layout. + // For now, I should probably implement a HOC that will take care of this. + + return user ? ( +
+

{ user.username }

+

{ user.bio }

+
+ Edit Profile Settings +
+
+ ) : null +} + +export default UserProfilePage diff --git a/examples/realworld/ext/UserSettingsPage.js b/examples/realworld/ext/UserSettingsPage.js new file mode 100644 index 000000000..6d0a44836 --- /dev/null +++ b/examples/realworld/ext/UserSettingsPage.js @@ -0,0 +1,120 @@ +import React, { useState } from 'react' +import { Link, useHistory } from 'react-router-dom' + +import useAuth from '@wasp/auth/useAuth.js' +import logout from '@wasp/auth/logout.js' +import updateUser from '@wasp/actions/updateUser' + +const UserSettingsPage = () => { + const { data: user, isError } = useAuth({ keepPreviousData: true }) + // TODO: Instead of this logic here, I wish I could use ACL via Wasp and just + // receive user via props instead of useAuth(). + if (!user || isError) { + return Please log in. + } + + // TODO: There is no navbar right now, but there should be one! It is only on Main page. + // I wish I had a "layout" mechanism that I would apply on certain pages + // and they would all have this same layout. + // For now, I should probably implement a HOC that will take care of this. + + return ( +
+ +
+ ) + +} + +const UserSettings = (props) => { + const user = props.user + + const history = useHistory() + + const [profilePictureUrl, setProfilePictureUrl] = useState(user.profilePictureUrl || '') + const [username, setUsername] = useState(user.username || '') + const [bio, setBio] = useState(user.bio || '') + const [email, setEmail] = useState(user.email || '') + const [newPassword, setNewPassword] = useState('') + + const [submitError, setSubmitError] = useState(null) + + const handleSubmit = async (event) => { + event.preventDefault() + setSubmitError(null) + try { + await updateUser({ + profilePictureUrl, + username, + bio, + email, + newPassword + }) + // TODO: If update was successful, send user to his profile (/@). + history.push(`/@${username}`) + } catch (err) { + setSubmitError(err) + } + } + + const handleLogout = async () => { + await logout() + history.push('/') + } + + return ( +
+ { submitError && ( +

+ { submitError.message || submitError } +

+ ) } + +
+ +

URL of profile picture

+ setProfilePictureUrl(e.target.value)} + /> + +

Username

+ setUsername(e.target.value)} + /> + +

Short bio

+ setBio(e.target.value)} + /> + +

Email

+ setEmail(e.target.value)} + /> + +

New password

+ setNewPassword(e.target.value)} + /> + +
+ +
+
+ + +
+ ) +} + +export default UserSettingsPage diff --git a/examples/realworld/ext/actions.js b/examples/realworld/ext/actions.js index 4060c6c22..6740c0742 100644 --- a/examples/realworld/ext/actions.js +++ b/examples/realworld/ext/actions.js @@ -13,3 +13,17 @@ export const signup = async ({ username, email, password }, context) => { throw err } } + +export const updateUser = async ({ email, username, bio, profilePictureUrl, newPassword }, context) => { + // TODO: Nicer error handling! Right now everything is returned as 500 while it could be instead + // useful error message about username being taken / not unique, and other validation errors. + await context.entities.User.update({ + where: { email }, + data: { + username, + bio, + profilePictureUrl, + password: newPassword || undefined + } + }) +} diff --git a/examples/realworld/ext/queries.js b/examples/realworld/ext/queries.js new file mode 100644 index 000000000..6a9b650e8 --- /dev/null +++ b/examples/realworld/ext/queries.js @@ -0,0 +1,8 @@ +import HttpError from '@wasp/core/HttpError.js' + +export const getUser = async ({ username }, context) => { + // TODO: Do some error handling? + const user = await context.entities.User.findOne({ where: { username } }) + if (!user) throw new HttpError(404, 'No user with username ' + username) + return user +} diff --git a/examples/realworld/main.wasp b/examples/realworld/main.wasp index 25c3e90b7..a8050106d 100644 --- a/examples/realworld/main.wasp +++ b/examples/realworld/main.wasp @@ -17,11 +17,23 @@ page Signup { component: import Signup from "@ext/SignupPage.js" } +route "/settings" -> page UserSettings +page UserSettings { + component: import UserSettings from "@ext/UserSettingsPage.js" +} + +route "/@:username" -> page UserProfile +page UserProfile { + component: import UserProfile from "@ext/UserProfilePage.js" +} + entity User {=psl id Int @id @default(autoincrement()) username String @unique email String @unique password String + bio String? + profilePictureUrl String? psl=} auth { @@ -32,4 +44,14 @@ auth { action signup { fn: import { signup } from "@ext/actions.js", entities: [User] +} + +action updateUser { + fn: import { updateUser } from "@ext/actions.js", + entities: [User] +} + +query getUser { + fn: import { getUser } from "@ext/queries.js", + entities: [User] } \ No newline at end of file diff --git a/examples/realworld/migrations/20201117145436-added-bio-and-picture-to-user/README.md b/examples/realworld/migrations/20201117145436-added-bio-and-picture-to-user/README.md new file mode 100644 index 000000000..ee696a6c0 --- /dev/null +++ b/examples/realworld/migrations/20201117145436-added-bio-and-picture-to-user/README.md @@ -0,0 +1,38 @@ +# Migration `20201117145436-added-bio-and-picture-to-user` + +This migration has been generated by Martin Sosic at 11/17/2020, 3:54:36 PM. +You can check out the [state of the schema](./schema.prisma) after the migration. + +## Database Steps + +```sql +ALTER TABLE "User" ADD COLUMN "bio" TEXT; +ALTER TABLE "User" ADD COLUMN "profilePictureUrl" TEXT +``` + +## Changes + +```diff +diff --git schema.prisma schema.prisma +migration 20201030185724-fixed-user..20201117145436-added-bio-and-picture-to-user +--- datamodel.dml ++++ datamodel.dml +@@ -1,8 +1,8 @@ + datasource db { + provider = "sqlite" +- url = "***" ++ url = "***" + } + generator client { + provider = "prisma-client-js" +@@ -13,6 +13,8 @@ + id Int @id @default(autoincrement()) + username String @unique + email String @unique + password String ++ bio String? ++ profilePictureUrl String? + } +``` + + diff --git a/examples/realworld/migrations/20201117145436-added-bio-and-picture-to-user/schema.prisma b/examples/realworld/migrations/20201117145436-added-bio-and-picture-to-user/schema.prisma new file mode 100644 index 000000000..2deb025fc --- /dev/null +++ b/examples/realworld/migrations/20201117145436-added-bio-and-picture-to-user/schema.prisma @@ -0,0 +1,20 @@ + +datasource db { + provider = "sqlite" + url = "***" +} + +generator client { + provider = "prisma-client-js" + output = "../server/node_modules/.prisma/client" +} + +model User { + id Int @id @default(autoincrement()) + username String @unique + email String @unique + password String + bio String? + profilePictureUrl String? +} + diff --git a/examples/realworld/migrations/20201117145436-added-bio-and-picture-to-user/steps.json b/examples/realworld/migrations/20201117145436-added-bio-and-picture-to-user/steps.json new file mode 100644 index 000000000..1ff4f8844 --- /dev/null +++ b/examples/realworld/migrations/20201117145436-added-bio-and-picture-to-user/steps.json @@ -0,0 +1,19 @@ +{ + "version": "0.3.14-fixed", + "steps": [ + { + "tag": "CreateField", + "model": "User", + "field": "bio", + "type": "String", + "arity": "Optional" + }, + { + "tag": "CreateField", + "model": "User", + "field": "profilePictureUrl", + "type": "String", + "arity": "Optional" + } + ] +} \ No newline at end of file diff --git a/examples/realworld/migrations/migrate.lock b/examples/realworld/migrations/migrate.lock index 2567df567..da01af925 100644 --- a/examples/realworld/migrations/migrate.lock +++ b/examples/realworld/migrations/migrate.lock @@ -1,4 +1,5 @@ # Prisma Migrate lockfile v1 20201030161549-user -20201030185724-fixed-user \ No newline at end of file +20201030185724-fixed-user +20201117145436-added-bio-and-picture-to-user \ No newline at end of file