Skip to content

Commit

Permalink
Adds the ability to edit the user
Browse files Browse the repository at this point in the history
  • Loading branch information
gstark committed Oct 20, 2020
1 parent 272ff13 commit d9668d7
Show file tree
Hide file tree
Showing 3 changed files with 247 additions and 0 deletions.
5 changes: 5 additions & 0 deletions ClientApp/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Restaurant } from './pages/Restaurant'
import { SignUp } from './pages/SignUp'
import { SignIn } from './pages/SignIn'
import { getUser, isLoggedIn, logout } from './auth'
import { EditUser } from './pages/EditUser'

export function App() {
const user = getUser()
Expand All @@ -32,6 +33,7 @@ export function App() {
)}
{isLoggedIn() || <Link to="/signup">Sign Up</Link>}
{isLoggedIn() || <Link to="/signin">Sign In</Link>}
{isLoggedIn() && <Link to="/profile">Profile</Link>}
{isLoggedIn() && (
<span className="link" onClick={handleLogout}>
Sign out
Expand Down Expand Up @@ -68,6 +70,9 @@ export function App() {
<Route exact path="/signin">
<SignIn />
</Route>
<Route exact path="/profile">
<EditUser />
</Route>
</Switch>
<footer>
<p>
Expand Down
168 changes: 168 additions & 0 deletions ClientApp/src/pages/EditUser.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import React, { useState } from 'react'
import { Link, useHistory } from 'react-router-dom'
import { useDropzone } from 'react-dropzone'

import { authHeader, getUser, updateUserAuth } from '../auth'

export function EditUser() {
const history = useHistory()
const user = getUser()

const [errorMessage, setErrorMessage] = useState()

const [updatedUser, setUpdatedUser] = useState({
id: user.id,
fullName: user.fullName,
email: user.email,
password: '',
photoURL: user.photoURL,
})

const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop: onDropFile,
})

const [isUploading, setIsUploading] = useState(false)

function handleStringFieldChange(event) {
const value = event.target.value
const fieldName = event.target.name

const newUpdatedUser = { ...updatedUser, [fieldName]: value }

setUpdatedUser(newUpdatedUser)
}

async function handleFormSubmit(event) {
event.preventDefault()

const response = await fetch(`/api/Users/${user.id}`, {
method: 'PUT',
headers: { 'content-type': 'application/json', ...authHeader() },
body: JSON.stringify(updatedUser),
})

const apiResponse = await response.json()

if (apiResponse.status === 400) {
setErrorMessage(Object.values(apiResponse.errors).join(' '))
} else {
updateUserAuth(apiResponse)
window.location.assign('/')
}
}

async function onDropFile(acceptedFiles) {
// Do something with the files
const fileToUpload = acceptedFiles[0]
console.log(fileToUpload)

// Create a formData object so we can send this
// to the API that is expecting som form data.
const formData = new FormData()

// Append a field that is the form upload itself
formData.append('file', fileToUpload)

try {
setIsUploading(true)

// Use fetch to send an authorization header and
// a body containing the form data with the file
const response = await fetch('/api/Uploads', {
method: 'POST',
headers: {
...authHeader(),
},
body: formData,
})

setIsUploading(false)

// If we receive a 200 OK response, set the
// URL of the photo in our state so that it is
// sent along when creating the restaurant,
// otherwise show an error
if (response.status === 200) {
const apiResponse = await response.json()

const url = apiResponse.url

setUpdatedUser({ ...updatedUser, photoURL: url })
} else {
setErrorMessage('Unable to upload image')
}
} catch (error) {
// Catch any network errors and show the user we could not process their upload
console.debug(error)
setErrorMessage('Unable to upload image')
setIsUploading(false)
}
}

let dropZoneMessage = 'Drag a picture of yourself here!'

if (isUploading) {
dropZoneMessage = 'Uploading...'
}

if (isDragActive) {
dropZoneMessage = 'Drop the files here ...'
}

return (
<main className="page">
<nav>
<Link to="/">
<i className="fa fa-home"></i>
</Link>
<h2>Edit Profile</h2>
</nav>

<form onSubmit={handleFormSubmit}>
{errorMessage && <p>{errorMessage}</p>}
<p className="form-input">
<label htmlFor="fullName">Name</label>
<input
type="text"
name="fullName"
value={updatedUser.fullName}
onChange={handleStringFieldChange}
/>
</p>
<p className="form-input">
<label htmlFor="name">Email</label>
<input
type="email"
name="email"
value={updatedUser.email}
onChange={handleStringFieldChange}
/>
</p>
<p className="form-input">
<label htmlFor="password">Password</label>
<input
type="password"
name="password"
value={updatedUser.password}
onChange={handleStringFieldChange}
/>
</p>
{updatedUser.photoURL && (
<p>
<img alt="User Photo" width={200} src={updatedUser.photoURL} />
</p>
)}
<div className="file-drop-zone">
<div {...getRootProps()}>
<input {...getInputProps()} />
{dropZoneMessage}
</div>
</div>
<p>
<input type="submit" value="Submit" />
</p>
</form>
</main>
)
}
74 changes: 74 additions & 0 deletions Controllers/UsersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
Expand All @@ -26,6 +28,65 @@ public UsersController(DatabaseContext context)
_context = context;
}

// PUT: api/Users/5
//
// Update an individual User with the requested id. The id is specified in the URL
// In the sample URL above it is the `5`. The "{id} in the [HttpPut("{id}")] is what tells dotnet
// to grab the id from the URL. It is then made available to us as the `id` argument to the method.
//
// In addition the `body` of the request is parsed and then made available to us as a User
// variable named User. The controller matches the keys of the JSON object the client
// supplies to the names of the attributes of our User POCO class. This represents the
// new values for the record.
//
[HttpPut("{id}")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public async Task<IActionResult> PutUser(int id, User User)
{
// If the ID in the URL does not match the ID in the supplied request body, return a bad request
if (id != User.Id)
{
return BadRequest();
}

if (id != GetCurrentUserId())
{
return BadRequest();
}

// Tell the database to consider everything in User to be _updated_ values. When
// the save happens the database will _replace_ the values in the database with the ones from User
_context.Entry(User).State = EntityState.Modified;

try
{
// Try to save these changes.
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
// Ooops, looks like there was an error, so check to see if the record we were
// updating no longer exists.
if (!UserExists(id))
{
// If the record we tried to update was already deleted by someone else,
// return a `404` not found
return NotFound();
}
else
{
// Otherwise throw the error back, which will cause the request to fail
// and generate an error to the client.
throw;
}
}

// return NoContent to indicate the update was done. Alternatively you can use the
// following to send back a copy of the updated data.
//
return Ok(User);
}

// POST: api/Users
//
// Creates a new user in the database.
Expand Down Expand Up @@ -62,5 +123,18 @@ public async Task<ActionResult<User>> PostUser(User user)
return BadRequest(response);
}
}

// Private helper method that looks up an existing User by the supplied id
private bool UserExists(int id)
{
return _context.Users.Any(User => User.Id == id);
}

// Private helper method to get the JWT claim related to the user ID
private int GetCurrentUserId()
{
// Get the User Id from the claim and then parse it as an integer.
return int.Parse(User.Claims.FirstOrDefault(claim => claim.Type == "Id").Value);
}
}
}

0 comments on commit d9668d7

Please sign in to comment.