Browse Source

Revise organization edit page

pull/41/head
Berkan Sasmaz 6 years ago
parent
commit
3b80d9c607
  1. 2
      src/EventHub.Application.Contracts/Organizations/IOrganizationAppService.cs
  2. 26
      src/EventHub.Application.Contracts/Organizations/UpdateOrganizationDto.cs
  3. 15
      src/EventHub.Application/Organizations/OrganizationAppService.cs
  4. 23
      src/EventHub.Web/Controllers/OrganizationController.cs
  5. 125
      src/EventHub.Web/Pages/Organizations/Edit.cshtml
  6. 58
      src/EventHub.Web/Pages/Organizations/Edit.cshtml.cs
  7. 99
      src/EventHub.Web/Pages/Organizations/Edit.js
  8. 6
      src/EventHub.Web/Pages/Organizations/Profile.cshtml

2
src/EventHub.Application.Contracts/Organizations/IOrganizationAppService.cs

@ -18,7 +18,5 @@ namespace EventHub.Organizations
Task<bool> IsOrganizationOwnerAsync(Guid organizationId);
Task UpdateAsync(Guid id, UpdateOrganizationDto input);
Task SaveProfilePictureAsync(Guid id, byte[] bytes);
}
}

26
src/EventHub.Application.Contracts/Organizations/UpdateOrganizationDto.cs

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
namespace EventHub.Organizations
{
@ -12,16 +13,31 @@ namespace EventHub.Organizations
[StringLength(OrganizationConsts.MaxDescriptionNameLength, MinimumLength = OrganizationConsts.MinDescriptionNameLength)]
public string Description { get; set; }
[CanBeNull]
public byte[] ProfilePictureContent { get; set; }
[CanBeNull]
[StringLength(OrganizationConsts.MaxWebsiteLength)]
public string Website { get; set; }
[CanBeNull]
[StringLength(OrganizationConsts.MaxTwitterUsernameLength)]
public string TwitterUsername { get; set; }
[CanBeNull]
[StringLength(OrganizationConsts.MaxGitHubUsernameLength)]
public string GitHubUsername { get; set; }
[CanBeNull]
[StringLength(OrganizationConsts.MaxFacebookUsernameLength)]
public string FacebookUsername { get; set; }
[CanBeNull]
[StringLength(OrganizationConsts.MaxInstagramUsernameLength)]
public string InstagramUsername { get; set; }
[CanBeNull]
[StringLength(OrganizationConsts.MaxMediumUsernameLength)]
public string MediumUsername { get; set; }
}
}

15
src/EventHub.Application/Organizations/OrganizationAppService.cs

@ -7,6 +7,7 @@ using EventHub.Users;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Authorization;
using Volo.Abp.BlobStoring;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Users;
@ -137,8 +138,7 @@ namespace EventHub.Organizations
if (organization.OwnerUserId != CurrentUser.GetId())
{
//TODO: throw AbpAuthorizationException instead
throw new BusinessException(EventHubErrorCodes.NotAuthorizedToUpdateOrganizationProfile)
throw new AbpAuthorizationException(EventHubErrorCodes.NotAuthorizedToUpdateOrganizationProfile)
.WithData("OrganizationName", organization.DisplayName);
}
@ -151,18 +151,21 @@ namespace EventHub.Organizations
organization.FacebookUsername = input.FacebookUsername;
organization.MediumUsername = input.MediumUsername;
if (input.ProfilePictureContent != null && input.ProfilePictureContent.Length > 0)
{
await SaveProfilePictureAsync(organization.Id, input.ProfilePictureContent);
}
await _organizationRepository.UpdateAsync(organization);
}
[Authorize]
public async Task SaveProfilePictureAsync(Guid id, byte[] bytes)
private async Task SaveProfilePictureAsync(Guid id, byte[] bytes)
{
var organization = await _organizationRepository.GetAsync(x => x.Id == id);
if (organization.OwnerUserId != CurrentUser.GetId())
{
//TODO: throw AbpAuthorizationException instead
throw new BusinessException(EventHubErrorCodes.NotAuthorizedToUpdateOrganizationProfile)
throw new AbpAuthorizationException(EventHubErrorCodes.NotAuthorizedToUpdateOrganizationProfile)
.WithData("Name", organization.DisplayName);
}

23
src/EventHub.Web/Controllers/OrganizationController.cs

@ -1,11 +1,7 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using EventHub.Organizations;
using EventHub.Web.Pages.Organizations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Application.Dtos;
using Volo.Abp.AspNetCore.Mvc;
namespace EventHub.Web.Controllers
@ -32,24 +28,5 @@ namespace EventHub.Web.Controllers
ViewData = ViewData
};
}
[HttpPost]
[Authorize]
[Route("save-profile-picture")]
public async Task SaveProfilePicture([FromForm] OrganizationProfilePictureInput input)
{
var profilePictureContent = new byte[] {};
if (input.ProfilePictureFile.Length > 0)
{
using (var memoryStream = new MemoryStream())
{
await input.ProfilePictureFile.CopyToAsync(memoryStream);
profilePictureContent = memoryStream.ToArray();
}
await _organizationAppService.SaveProfilePictureAsync(input.OrganizationId, profilePictureContent);
}
}
}
}

125
src/EventHub.Web/Pages/Organizations/Edit.cshtml

@ -1,6 +1,7 @@
@page "/organization/edit/{name}"
@inject IHtmlLocalizer<EventHubResource> L
@using EventHub.Localization
@using EventHub.Organizations
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model EventHub.Web.Pages.Organizations.EditPageModel
@ -9,41 +10,97 @@
<abp-script src="/Pages/Organizations/Edit.js"/>
}
<div class="container mb-4">
<h1>@L["EditOrganization"]</h1>
<div class="container pt-4">
<div class="row py-4">
<div class="col-md-9 mx-auto profile-content">
<div class="card">
<div class="card-body" style="min-height: 496px">
<h3 class="mb-4">Edit Organization</h3>
<form method="post" enctype="multipart/form-data">
<div class="form">
<div class="row">
<abp-input asp-for="Organization.Id"/>
<abp-tabs tab-style="Pill">
<abp-tab title="@(L["ProfileInfo"].Value)">
<abp-dynamic-form abp-model="Organization" submit-button="true" class="mt-3"/>
</abp-tab>
<abp-tab title="@(L["ProfilePicture"].Value)">
<form method="post" id="EditOrganizationProfileForm" enctype="multipart/form-data">
<input type="hidden" id="organization-id" value="@Model.Organization.Id"/>
<input type="hidden" id="organization-name" value="@Model.Name"/>
<abp-row>
<abp-column size="_12" v-align="Center">
<div class="form-group">
<label>@L["ProfilePicture"]</label>
<div class="custom-file">
<input id="organization-profile-picture" type="file" class="custom-file-input form-control" required="required">
<label id="choose-cover-image" class="custom-file-label" for="customFile">@L["ChooseOrganizationProfilePicture"]</label>
<small id="Organization_ProfilePictureInfoText" class="form-text text-muted mt-2">@L["OrganizationProfilePictureInfo"]</small>
<div class="col-md-12">
<h4 class="mb-4">Profile Image</h4>
<div class="row">
<div class="col-4">
<div class="image-area">
<img id="imageResult" src="#" alt="" class="img-fluid rounded shadow-sm mx-auto d-block">
</div>
</div>
<div class="col-8">
<div>
<p class="mb-3">Photo size should be <span class="text-primary">max @(OrganizationConsts.MaxProfilePictureFileSize / (1024 * 1024)) MB.</span></p>
<input type="file" id="Organization_ProfilePictureFile" asp-for="Organization.ProfilePictureFile" class="form-control border-0" hidden>
<label id="upload-label" for="Organization_ProfilePictureFile" class="btn btn-primary btn-lg text-white">Upload</label>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-label-group">
<input type="text" id="inputDisplayname" asp-for="Organization.DisplayName" class="form-control" placeholder="Display Name*" required="">
<label for="inputDisplayname">Display Name*</label>
</div>
</div>
<div class="col-md-6">
<div class="form-label-group">
<input type="text" id="inputWebsite" class="form-control" asp-for="Organization.Website" placeholder="Website">
<label for="inputWebsite">Website</label>
</div>
</div>
<div class="col-md-12">
<div class="form-label-group">
<input type="text" id="inputDescription" asp-for="Organization.Description" class="form-control" placeholder="Description*" required=""></input>
<label for="inputDescription">Description*</label>
</div>
</div>
<div class="col-md-6">
<div class="form-label-group">
<input type="text" id="inputTwitterusername" asp-for="Organization.TwitterUsername" class="form-control" placeholder="Twitter Username">
<label for="inputTwitterusername">Twitter Username</label>
</div>
</div>
<div class="col-md-6">
<div class="form-label-group">
<input type="text" id="inputGitHubusername" asp-for="Organization.GitHubUsername" class="form-control" placeholder="GitHub Username">
<label for="inputGitHubusername">GitHub Username</label>
</div>
</div>
<div class="col-md-6">
<div class="form-label-group">
<input type="text" id="inputFacebookusername" asp-for="Organization.FacebookUsername" class="form-control" placeholder="Facebook Username">
<label for="inputFacebookusername">Facebook Username</label>
</div>
</div>
<div class="col-md-6">
<div class="form-label-group">
<input type="text" id="inputInstagramusername" asp-for="Organization.InstagramUsername" class="form-control" placeholder="Instagram Username">
<label for="inputInstagramusername">Instagram Username</label>
</div>
</div>
<div class="col-md-6">
<div class="form-label-group">
<input type="text" id="inputMediumusername" asp-for="Organization.MediumUsername" class="form-control" placeholder="Medium Username">
<label for="inputMediumusername">Medium Username</label>
</div>
</div>
<div class="col-md-12 text-right">
<button type="submit" class="btn btn-primary btn-lg">
Submit
</button>
</div>
</div>
</div>
</abp-column>
</abp-row>
<button id="btnSubmit" type="submit" class="btn btn-primary btn-block">
@L["Submit"]
</button>
</form>
</abp-tab>
</abp-tabs>
<abp-modal centered="true" scrollable="true" size="Large" id="picturePreviewModal">
<abp-modal-header title="@(L["ProfilePicturePreview"].Value)"></abp-modal-header>
<abp-modal-body>
<img class="img-fluid" id="img-upload" alt=""/>
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Close)"></abp-modal-footer>
</abp-modal>
</form>
</div>
</div>
</div>
</div>
</div>

58
src/EventHub.Web/Pages/Organizations/Edit.cshtml.cs

@ -1,11 +1,14 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Threading.Tasks;
using EventHub.Organizations;
using EventHub.Web.Helpers;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
namespace EventHub.Web.Pages.Organizations
{
@ -27,7 +30,9 @@ namespace EventHub.Web.Pages.Organizations
public async Task OnGetAsync()
{
await GetOrganizationAsync();
var organizationProfileDto = await _organizationAppService.GetProfileAsync(Name);
Organization = ObjectMapper.Map<OrganizationProfileDto, EditOrganizationViewModel>(organizationProfileDto);
}
public async Task<IActionResult> OnPostAsync()
@ -37,6 +42,16 @@ namespace EventHub.Web.Pages.Organizations
ValidateModel();
var input = ObjectMapper.Map<EditOrganizationViewModel, UpdateOrganizationDto>(Organization);
if (Organization.ProfilePictureFile != null && Organization.ProfilePictureFile.Length > 0)
{
using (var memoryStream = new MemoryStream())
{
await Organization.ProfilePictureFile.CopyToAsync(memoryStream);
input.ProfilePictureContent = memoryStream.ToArray();
}
}
await _organizationAppService.UpdateAsync(Organization.Id, input);
return RedirectToPage("./Profile", new { name = Name });
@ -47,12 +62,6 @@ namespace EventHub.Web.Pages.Organizations
return Page();
}
}
private async Task GetOrganizationAsync()
{
var organizationProfileDto = await _organizationAppService.GetProfileAsync(Name);
Organization = ObjectMapper.Map<OrganizationProfileDto, EditOrganizationViewModel>(organizationProfileDto);
}
}
public class EditOrganizationViewModel
@ -65,31 +74,32 @@ namespace EventHub.Web.Pages.Organizations
public string DisplayName { get; set; }
[Required]
[DataType(DataType.MultilineText)]
[StringLength(OrganizationConsts.MaxDescriptionNameLength, MinimumLength = OrganizationConsts.MinDescriptionNameLength)]
public string Description { get; set; }
[CanBeNull]
[DataType(DataType.Upload)]
[MaxFileSize(OrganizationConsts.MaxProfilePictureFileSize)]
[AllowedExtensions(new string[] { ".jpg", ".png", ".jpeg" })]
public IFormFile ProfilePictureFile { get; set; }
[StringLength(OrganizationConsts.MaxWebsiteLength)]
public string Website { get; set; }
[StringLength(OrganizationConsts.MaxTwitterUsernameLength)]
public string TwitterUsername { get; set; }
[StringLength(OrganizationConsts.MaxGitHubUsernameLength)]
public string GitHubUsername { get; set; }
[StringLength(OrganizationConsts.MaxFacebookUsernameLength)]
public string FacebookUsername { get; set; }
[StringLength(OrganizationConsts.MaxInstagramUsernameLength)]
public string InstagramUsername { get; set; }
[StringLength(OrganizationConsts.MaxMediumUsernameLength)]
public string MediumUsername { get; set; }
}
public class OrganizationProfilePictureInput
{
[Required]
public Guid OrganizationId { get; set; }
[Required]
[DataType(DataType.Upload)]
[MaxFileSize(OrganizationConsts.MaxProfilePictureFileSize)]
[AllowedExtensions(new string[] { ".jpg", ".png", ".jpeg" })]
public IFormFile ProfilePictureFile { get; set; }
}
}

99
src/EventHub.Web/Pages/Organizations/Edit.js

@ -1,43 +1,39 @@
$(function () {
var l = abp.localization.getResource('EventHub');
var fileInput = document.getElementById("organization-profile-picture");
var infoArea = document.getElementById( 'upload-label' );
var fileInput = document.getElementById('Organization_ProfilePictureFile');
var file;
fileInput.addEventListener('change', function () {
var showModal = true;
var showFile = true;
file = fileInput.files[0];
if(file == undefined) {
$("#choose-cover-image").html(l("ChooseOrganizationProfilePicture"));
if (file === undefined){
$('#Organization_ProfilePictureFile').val('');
$('#imageResult').attr('src', '#');
infoArea.textContent = 'Choose file'
return;
}
else {
$("#choose-cover-image").html(file.name);
}
var permittedExtensions = ["jpg", "jpeg", "png"]
var fileExtension = $(this).val().split('.').pop();
if (permittedExtensions.indexOf(fileExtension) === -1) {
showModal = false;
abp.message.error(l("OrganizationProfilePictureExtensionNotAllowed"))
.then(() => {
$("#choose-cover-image").html(l("ChooseOrganizationProfilePicture"));
showFile = false;
abp.message.error(l('OrganizationProfilePictureExtensionNotAllowed'))
.then(() => {
$('#Organization_ProfilePictureFile').val('');
file = null;
});
}
//1mb
else if(file.size > 1024 * 1024) {
showModal = false;
abp.message.error(l("OrganizationProfilePictureSizeExceedMessage"))
} else if (file.size > 1024 * 1024) {
showFile = false;
abp.message.error(l('OrganizationProfilePictureSizeExceedMessage'))
.then(() => {
$("#choose-cover-image").html(l("ChooseOrganizationProfilePicture"));
$('#Organization_ProfilePictureFile').val('');
file = null;
});
}
var img = new Image();
img.onload = function () {
var imageError = true;
@ -45,12 +41,10 @@ $(function () {
width: this.width,
height: this.height
};
URL.revokeObjectURL(this.src);
if(showModal && imageError) {
if (showFile && imageError) {
readURL(file);
$("#picturePreviewModal").modal('show');
}
}
@ -63,60 +57,11 @@ $(function () {
var reader = new FileReader();
reader.onload = function (e) {
$('#img-upload').attr('src', e.target.result);
$('#imageResult').attr('src', e.target.result);
infoArea.textContent = 'File name: ' + input.name;
}
reader.readAsDataURL(input);
}
}
$("form#EditOrganizationProfileForm").submit(function (e) {
e.preventDefault();
var organizationId = document.getElementById("organization-id").value;
var organizationName = document.getElementById("organization-name").value;
if(!$(this).valid()) {
return false;
}
if(file === undefined) {
file = null;
}
var formData = new FormData();
formData.append("OrganizationId", organizationId);
formData.append("ProfilePictureFile", file);
$.ajax({
xhr: function() {
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener("progress", function(evt) {
if (evt.lengthComputable) {
var percentage = evt.loaded / evt.total;
percentage = parseInt(percentage * 100);
if(percentage !== 100){
$('#btnSubmit').prop("disabled", true);
}
}
}, false);
return xhr;
},
url: `/api/organization/save-profile-picture`,
data: formData,
type: 'POST',
contentType: false,
processData: false,
success: function(data){
abp.message
.success(l("OrganizationProfilePictureEditSuccessMessage"))
.then(data => window.location.href = "/Organizations/" + organizationName);
},
error: function (data) {
abp.message.error(data.responseJSON.error.message);
}
});
});
});

6
src/EventHub.Web/Pages/Organizations/Profile.cshtml

@ -58,6 +58,12 @@
<img gravatar-email="@Model.Organization.OwnerEmail" default-image="Wavatar" gravatar-size="60" class="org-avatar avatar rounded-circle" data-toggle="tooltip" title="" data-original-title="@Model.Organization.OwnerUserName"/>
</div>
@if (Model.IsOrganizationOwner)
{
<a href="/organization/edit/@Model.Organization.Name" class="btn btn-primary mt-4 text-white">
<i class="far fa-edit"></i> Edit
</a>
}
@await Component.InvokeAsync(typeof(JoinAreaViewComponent), new {organizationId = Model.Organization.Id})
@await Component.InvokeAsync(typeof(MembersAreaViewComponent),
new

Loading…
Cancel
Save