mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-11-24 03:35:17 +03:00
Implemented user settings and profile page.
This commit is contained in:
parent
26ad50c147
commit
e7cf17ea5d
@ -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 (
|
||||
<div>
|
||||
<a href="#"> Settings </a>
|
||||
<a href="#"> { props.user.username } </a>
|
||||
<button onClick={logout}> Log out </button>
|
||||
<Link to='/settings'> Settings </Link>
|
||||
<a href={`/@${props.user.username}`}> { props.user.username } </a>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
|
38
examples/realworld/ext/UserProfilePage.js
Normal file
38
examples/realworld/ext/UserProfilePage.js
Normal file
@ -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 ? (
|
||||
<div>
|
||||
<p> { user.username } </p>
|
||||
<p> { user.bio } </p>
|
||||
<div>
|
||||
<Link to='/settings'>Edit Profile Settings</Link>
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
|
||||
export default UserProfilePage
|
120
examples/realworld/ext/UserSettingsPage.js
Normal file
120
examples/realworld/ext/UserSettingsPage.js
Normal file
@ -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 <span> Please <Link to='/login'>log in</Link>. </span>
|
||||
}
|
||||
|
||||
// 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 (
|
||||
<div>
|
||||
<UserSettings user={user}/>
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
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 (/@<username>).
|
||||
history.push(`/@${username}`)
|
||||
} catch (err) {
|
||||
setSubmitError(err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout()
|
||||
history.push('/')
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ submitError && (
|
||||
<p>
|
||||
{ submitError.message || submitError }
|
||||
</p>
|
||||
) }
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
|
||||
<h2>URL of profile picture</h2>
|
||||
<input
|
||||
type='text'
|
||||
value={profilePictureUrl}
|
||||
onChange={e => setProfilePictureUrl(e.target.value)}
|
||||
/>
|
||||
|
||||
<h2>Username</h2>
|
||||
<input
|
||||
type='text'
|
||||
value={username}
|
||||
onChange={e => setUsername(e.target.value)}
|
||||
/>
|
||||
|
||||
<h2>Short bio</h2>
|
||||
<input
|
||||
type='text'
|
||||
value={bio}
|
||||
onChange={e => setBio(e.target.value)}
|
||||
/>
|
||||
|
||||
<h2>Email</h2>
|
||||
<input
|
||||
type='text'
|
||||
value={email}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
/>
|
||||
|
||||
<h2>New password</h2>
|
||||
<input
|
||||
type='password'
|
||||
value={newPassword}
|
||||
onChange={e => setNewPassword(e.target.value)}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<input type='submit' value='Update Settings' />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<button onClick={handleLogout}> Log out </button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserSettingsPage
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
8
examples/realworld/ext/queries.js
Normal file
8
examples/realworld/ext/queries.js
Normal file
@ -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
|
||||
}
|
@ -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]
|
||||
}
|
@ -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?
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -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?
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
# Prisma Migrate lockfile v1
|
||||
|
||||
20201030161549-user
|
||||
20201030185724-fixed-user
|
||||
20201030185724-fixed-user
|
||||
20201117145436-added-bio-and-picture-to-user
|
Loading…
Reference in New Issue
Block a user