From 0ce7c575f0b6b8b086068880201a0899eb55efc5 Mon Sep 17 00:00:00 2001 From: manny404 Date: Wed, 20 Jul 2022 13:29:42 -0400 Subject: [PATCH 1/4] add ability to upload banner image for community page --- frontend/packages/client/src/App.sass | 2 + .../Community/CommunityEditorProfile.js | 125 +++++++++++++++--- .../src/components/CommunityCreate/StepOne.js | 104 ++++++++++++--- .../packages/client/src/hooks/useCommunity.js | 14 +- .../packages/client/src/pages/Community.js | 11 +- .../client/src/pages/CommunityEditor.js | 1 + 6 files changed, 214 insertions(+), 43 deletions(-) diff --git a/frontend/packages/client/src/App.sass b/frontend/packages/client/src/App.sass index 1f659ed76..3cf889a9a 100644 --- a/frontend/packages/client/src/App.sass +++ b/frontend/packages/client/src/App.sass @@ -142,6 +142,8 @@ code border: 1px solid $grey-lighter .border-lighter-dark-grey border: 1px solid $lighter-dark-grey +.border-dashed-dark + border: 2px dashed $lighter-dark-grey .flex-1 flex: 1 0 0 .flex-2 diff --git a/frontend/packages/client/src/components/Community/CommunityEditorProfile.js b/frontend/packages/client/src/components/Community/CommunityEditorProfile.js index 63f3027cd..39f5845fb 100644 --- a/frontend/packages/client/src/components/Community/CommunityEditorProfile.js +++ b/frontend/packages/client/src/components/Community/CommunityEditorProfile.js @@ -4,12 +4,13 @@ import { Upload } from 'components/Svg'; import { WrapperResponsive, Loader } from 'components'; import { getReducedImg } from 'utils'; import { useErrorHandlerContext } from 'contexts/ErrorHandler'; -import { MAX_AVATAR_FILE_SIZE } from 'const'; +import { MAX_AVATAR_FILE_SIZE, MAX_FILE_SIZE } from 'const'; function CommunityEditorProfile({ name, body = '', logo, + banner, // fn to update community payload updateCommunity, // fn to upload image @@ -20,13 +21,15 @@ function CommunityEditorProfile({ const [isUpdating, setIsUpdating] = useState(''); const [enableSave, setEnableSave] = useState(false); const [image, setImage] = useState({ imageUrl: logo }); + const [bannerImage, setBannerImage] = useState({ imageUrl: banner }); const { notifyError } = useErrorHandlerContext(); useEffect(() => { if ( (communityName !== name && communityName.length > 0) || communityDescription !== body || - image.file + image.file || + bannerImage.file ) { setEnableSave(true); } @@ -34,25 +37,33 @@ function CommunityEditorProfile({ communityName.trim().length === 0 || (communityName === name && communityDescription === body && - image.file === undefined) + image.file === undefined && + bannerImage.file === undefined) ) { setEnableSave(false); } - }, [name, body, communityName, communityDescription, image]); + }, [name, body, communityName, communityDescription, image, bannerImage]); const saveData = async () => { setIsUpdating(true); + // upload images if any let newImageUrl; - // upload image if any + let newBannerImageUrl; if (image.file) { newImageUrl = await uploadFile(image.file); } + if (bannerImage.file) { + newBannerImageUrl = await uploadFile(bannerImage.file); + } const updates = { ...(communityName !== name ? { name: communityName.trim() } : undefined), ...(communityDescription !== body ? { body: communityDescription.trim() } : undefined), ...(newImageUrl?.fileUrl ? { logo: newImageUrl.fileUrl } : undefined), + ...(newBannerImageUrl?.fileUrl + ? { bannerImgUrl: newBannerImageUrl.fileUrl } + : undefined), }; // updated fields if (Object.keys(updates).length > 0) await updateCommunity(updates); @@ -61,7 +72,7 @@ function CommunityEditorProfile({ }; const onDrop = useCallback( - (acceptedFiles) => { + (filename, dataKey, maxFileSize) => (acceptedFiles) => { acceptedFiles.forEach((imageFile) => { // validate type if ( @@ -75,33 +86,51 @@ function CommunityEditorProfile({ } // validate size if (imageFile.size > MAX_AVATAR_FILE_SIZE) { + const sizeLimit = + maxFileSize === MAX_AVATAR_FILE_SIZE ? '2MB' : '5MB'; notifyError({ status: 'Image file size not allowed', - statusText: 'The selected file exceeds the 2MB limit.', + statusText: `The selected file exceeds the ${sizeLimit} limit.`, }); return; } const imageAsURL = URL.createObjectURL(imageFile); - + const setters = { + logo: setImage, + banner: setBannerImage, + }; const img = new Image(); img.onload = function (e) { - // reduce image if necessary before upload - if (e.target.width > 150) { - getReducedImg(e.target, 150, 'community_image').then((result) => { - setImage({ imageUrl: imageAsURL, file: result.imageFile }); + // reduce logo images if necessary before upload + if (e.target.width > 150 && dataKey === 'logo') { + getReducedImg(e.target, 150, filename).then((result) => { + setters[dataKey]({ + imageUrl: imageAsURL, + file: result.imageFile, + }); }); } else { - setImage({ imageUrl: imageAsURL, file: imageFile }); + setters[dataKey]({ imageUrl: imageAsURL, file: imageFile }); } }; img.src = imageAsURL; }); }, - [setImage, notifyError] + [setImage, setBannerImage, notifyError] ); - const { getRootProps, getInputProps } = useDropzone({ - onDrop, + const { getRootProps: getLogoRootProps, getInputProps: getLogoInputProps } = + useDropzone({ + onDrop: onDrop('community_image', 'logo', MAX_AVATAR_FILE_SIZE), + maxFiles: 1, + accept: 'image/jpeg,image/png', + }); + + const { + getRootProps: getBannerRootProps, + getInputProps: getBannerInputProps, + } = useDropzone({ + onDrop: onDrop('community_banner', 'banner', MAX_FILE_SIZE), maxFiles: 1, accept: 'image/jpeg,image/png', }); @@ -143,13 +172,13 @@ function CommunityEditorProfile({ ? { border: '1px dashed #757575' } : undefined), }} - {...getRootProps()} + {...getLogoRootProps()} > {!image && !isUpdating && ( <> Avatar - + )} {image?.imageUrl && ( @@ -176,7 +205,65 @@ function CommunityEditorProfile({ }} > - + + + ) : ( +
+

Uploading...

+
+ )} + + + +
+
+
+ {!bannerImage && !isUpdating && ( + <> + + Community Banner Image + + JPG or PNG 200px X 1200px recommended + + + + )} + {bannerImage?.imageUrl && ( +
+ )} + {!(isUpdating && bannerImage.file) ? ( +
+ +
) : (
{ + (filename, dataKey, maxFileSize) => (acceptedFiles) => { acceptedFiles.forEach((imageFile) => { // validate type if ( @@ -56,10 +56,12 @@ export default function StepOne({ return; } // validate size - if (imageFile.size > MAX_AVATAR_FILE_SIZE) { + if (imageFile.size > maxFileSize) { + const sizeLimit = + maxFileSize === MAX_AVATAR_FILE_SIZE ? '2MB' : '5MB'; notifyError({ status: 'Image file size not allowed', - statusText: 'The selected file exceeds the 2MB limit.', + statusText: `The selected file exceeds the ${sizeLimit} limit.`, }); return; } @@ -67,15 +69,17 @@ export default function StepOne({ const img = new Image(); img.onload = function (e) { - // reduce image if necessary before upload - if (e.target.width > 150) { - getReducedImg(e.target, 150, 'community_image').then((result) => { + // reduce logo images if necessary before upload + if (e.target.width > 150 && dataKey === 'logo') { + getReducedImg(e.target, 150, filename).then((result) => { onDataChange({ - logo: { imageUrl: imageAsURL, file: result.imageFile }, + [dataKey]: { imageUrl: imageAsURL, file: result.imageFile }, }); }); } else { - onDataChange({ logo: { imageUrl: imageAsURL, file: imageFile } }); + onDataChange({ + [dataKey]: { imageUrl: imageAsURL, file: imageFile }, + }); } }; img.src = imageAsURL; @@ -84,8 +88,18 @@ export default function StepOne({ [onDataChange, notifyError] ); - const { getRootProps, getInputProps } = useDropzone({ - onDrop, + const { getRootProps: getLogoRootProps, getInputProps: getLogoInputProps } = + useDropzone({ + onDrop: onDrop('community_image', 'logo', MAX_AVATAR_FILE_SIZE), + maxFiles: 1, + accept: 'image/jpeg,image/png', + }); + + const { + getRootProps: getBannerRootProps, + getInputProps: getBannerInputProps, + } = useDropzone({ + onDrop: onDrop('community_banner', 'banner', MAX_FILE_SIZE), maxFiles: 1, accept: 'image/jpeg,image/png', }); @@ -94,6 +108,7 @@ export default function StepOne({ communityName, communityDescription, logo, + banner, communityTerms, category, } = stepData || {}; @@ -179,16 +194,9 @@ export default function StepOne({ position: 'relative', ...(!logo ? { border: '1px dashed #757575' } : undefined), }} - {...getRootProps()} + {...getLogoRootProps()} > - {!logo && ( - <> - - Avatar - - - )} - {logo && ( + {logo ? (
+ ) : ( + <> + + Avatar + + )} {logo?.file ? (
- + +
+ ) : null} +
+
+
+
+
+
+ {banner ? ( +
+ ) : ( + <> + + Community Banner Image + + JPG or PNG 200px X 1200px recommended + + + + )} + {banner?.file ? ( +
+ +
) : null}
diff --git a/frontend/packages/client/src/hooks/useCommunity.js b/frontend/packages/client/src/hooks/useCommunity.js index 886d4dbb1..5fc3bc105 100644 --- a/frontend/packages/client/src/hooks/useCommunity.js +++ b/frontend/packages/client/src/hooks/useCommunity.js @@ -94,6 +94,7 @@ export default function useCommunity({ twitterUrl, websiteUrl, logo, + banner, contractAdrress: contractAddr, contractName: contractN, storagePath: storageP, @@ -102,9 +103,10 @@ export default function useCommunity({ strategies, } = communityData; + // check for logo / banner uploads + // admins can edit later the images let communityLogo; - // check for logo upload - // admins can edit later the image + let communityBanner; if (logo?.file) { try { communityLogo = await uploadFile(logo.file); @@ -112,6 +114,13 @@ export default function useCommunity({ communityLogo = undefined; } } + if (banner?.file) { + try { + communityBanner = await uploadFile(banner.file); + } catch (err) { + communityBanner = undefined; + } + } const fetchOptions = { method: 'POST', @@ -133,6 +142,7 @@ export default function useCommunity({ websiteUrl, discordUrl, logo: communityLogo?.fileUrl, + bannerImgUrl: communityBanner?.fileUrl, contractAddress: setDefaultValue( contractAddr, '0x0ae53cb6e3f42a79' diff --git a/frontend/packages/client/src/pages/Community.js b/frontend/packages/client/src/pages/Community.js index 948812ff3..017496e3f 100644 --- a/frontend/packages/client/src/pages/Community.js +++ b/frontend/packages/client/src/pages/Community.js @@ -265,7 +265,16 @@ export default function Community() { return (
{community ? ( -
+
diff --git a/frontend/packages/client/src/pages/CommunityEditor.js b/frontend/packages/client/src/pages/CommunityEditor.js index 048c6de3c..867294a75 100644 --- a/frontend/packages/client/src/pages/CommunityEditor.js +++ b/frontend/packages/client/src/pages/CommunityEditor.js @@ -190,6 +190,7 @@ export default function CommunityEditorPage() { name={community?.name} body={community?.body} logo={community?.logo} + banner={community?.bannerImgUrl} updateCommunity={updateCommunity} uploadFile={uploadFile} /> From 6daf0112187497b5837cc8f35c8add786b7d3971 Mon Sep 17 00:00:00 2001 From: manny404 Date: Wed, 20 Jul 2022 14:38:43 -0400 Subject: [PATCH 2/4] make community editor profile avatar/banner experience consistent with create community --- .../components/Community/CommunityEditorProfile.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/packages/client/src/components/Community/CommunityEditorProfile.js b/frontend/packages/client/src/components/Community/CommunityEditorProfile.js index 39f5845fb..cf6fb64bf 100644 --- a/frontend/packages/client/src/components/Community/CommunityEditorProfile.js +++ b/frontend/packages/client/src/components/Community/CommunityEditorProfile.js @@ -174,7 +174,7 @@ function CommunityEditorProfile({ }} {...getLogoRootProps()} > - {!image && !isUpdating && ( + {!isUpdating && !image?.imageUrl && !image?.file && ( <> Avatar @@ -193,7 +193,7 @@ function CommunityEditorProfile({ }} /> )} - {!(isUpdating && image.file) ? ( + {!isUpdating && (image?.imageUrl || image?.file) && (
- ) : ( + )} + {isUpdating && (
- {!bannerImage && !isUpdating && ( + {!isUpdating && !bannerImage?.imageUrl && ( <> Community Banner Image @@ -250,7 +251,7 @@ function CommunityEditorProfile({ }} /> )} - {!(isUpdating && bannerImage.file) ? ( + {!isUpdating && (bannerImage.imageUrl || bannerImage.file) && (
- ) : ( + )} + {isUpdating && (
Date: Wed, 20 Jul 2022 15:22:07 -0400 Subject: [PATCH 3/4] resize banners to 1200px if over --- .../components/Community/CommunityEditorProfile.js | 12 ++++++------ .../client/src/components/CommunityCreate/StepOne.js | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/frontend/packages/client/src/components/Community/CommunityEditorProfile.js b/frontend/packages/client/src/components/Community/CommunityEditorProfile.js index cf6fb64bf..6c7afd2c3 100644 --- a/frontend/packages/client/src/components/Community/CommunityEditorProfile.js +++ b/frontend/packages/client/src/components/Community/CommunityEditorProfile.js @@ -72,7 +72,7 @@ function CommunityEditorProfile({ }; const onDrop = useCallback( - (filename, dataKey, maxFileSize) => (acceptedFiles) => { + (filename, dataKey, maxFileSize, maxWidth) => (acceptedFiles) => { acceptedFiles.forEach((imageFile) => { // validate type if ( @@ -101,9 +101,9 @@ function CommunityEditorProfile({ }; const img = new Image(); img.onload = function (e) { - // reduce logo images if necessary before upload - if (e.target.width > 150 && dataKey === 'logo') { - getReducedImg(e.target, 150, filename).then((result) => { + // reduce images if necessary before upload + if (e.target.width > maxWidth) { + getReducedImg(e.target, maxWidth, filename).then((result) => { setters[dataKey]({ imageUrl: imageAsURL, file: result.imageFile, @@ -121,7 +121,7 @@ function CommunityEditorProfile({ const { getRootProps: getLogoRootProps, getInputProps: getLogoInputProps } = useDropzone({ - onDrop: onDrop('community_image', 'logo', MAX_AVATAR_FILE_SIZE), + onDrop: onDrop('community_image', 'logo', MAX_AVATAR_FILE_SIZE, 150), maxFiles: 1, accept: 'image/jpeg,image/png', }); @@ -130,7 +130,7 @@ function CommunityEditorProfile({ getRootProps: getBannerRootProps, getInputProps: getBannerInputProps, } = useDropzone({ - onDrop: onDrop('community_banner', 'banner', MAX_FILE_SIZE), + onDrop: onDrop('community_banner', 'banner', MAX_FILE_SIZE, 1200), maxFiles: 1, accept: 'image/jpeg,image/png', }); diff --git a/frontend/packages/client/src/components/CommunityCreate/StepOne.js b/frontend/packages/client/src/components/CommunityCreate/StepOne.js index e9d018c2b..bd5c939fb 100644 --- a/frontend/packages/client/src/components/CommunityCreate/StepOne.js +++ b/frontend/packages/client/src/components/CommunityCreate/StepOne.js @@ -43,7 +43,7 @@ export default function StepOne({ ); const onDrop = useCallback( - (filename, dataKey, maxFileSize) => (acceptedFiles) => { + (filename, dataKey, maxFileSize, maxWidth) => (acceptedFiles) => { acceptedFiles.forEach((imageFile) => { // validate type if ( @@ -69,9 +69,9 @@ export default function StepOne({ const img = new Image(); img.onload = function (e) { - // reduce logo images if necessary before upload - if (e.target.width > 150 && dataKey === 'logo') { - getReducedImg(e.target, 150, filename).then((result) => { + // reduce images if necessary before upload + if (e.target.width > maxWidth) { + getReducedImg(e.target, maxWidth, filename).then((result) => { onDataChange({ [dataKey]: { imageUrl: imageAsURL, file: result.imageFile }, }); @@ -90,7 +90,7 @@ export default function StepOne({ const { getRootProps: getLogoRootProps, getInputProps: getLogoInputProps } = useDropzone({ - onDrop: onDrop('community_image', 'logo', MAX_AVATAR_FILE_SIZE), + onDrop: onDrop('community_image', 'logo', MAX_AVATAR_FILE_SIZE, 150), maxFiles: 1, accept: 'image/jpeg,image/png', }); @@ -99,7 +99,7 @@ export default function StepOne({ getRootProps: getBannerRootProps, getInputProps: getBannerInputProps, } = useDropzone({ - onDrop: onDrop('community_banner', 'banner', MAX_FILE_SIZE), + onDrop: onDrop('community_banner', 'banner', MAX_FILE_SIZE, 1200), maxFiles: 1, accept: 'image/jpeg,image/png', }); From 7525ff260b87992a3ffdaae122ff6d392086720b Mon Sep 17 00:00:00 2001 From: manny404 Date: Thu, 21 Jul 2022 17:45:52 -0400 Subject: [PATCH 4/4] standardize dashed, remove dash on banner upload, fix isUpdating logic --- .../Community/CommunityEditorProfile.js | 32 +++++++++++++------ .../src/components/CommunityCreate/StepOne.js | 14 ++++++-- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/frontend/packages/client/src/components/Community/CommunityEditorProfile.js b/frontend/packages/client/src/components/Community/CommunityEditorProfile.js index 6c7afd2c3..03cc836f4 100644 --- a/frontend/packages/client/src/components/Community/CommunityEditorProfile.js +++ b/frontend/packages/client/src/components/Community/CommunityEditorProfile.js @@ -1,4 +1,5 @@ import React, { useState, useEffect, useCallback } from 'react'; +import classnames from 'classnames'; import { useDropzone } from 'react-dropzone'; import { Upload } from 'components/Svg'; import { WrapperResponsive, Loader } from 'components'; @@ -19,6 +20,8 @@ function CommunityEditorProfile({ const [communityName, setCommunityName] = useState(name); const [communityDescription, setCommunityDescription] = useState(body); const [isUpdating, setIsUpdating] = useState(''); + const [isUpdatingImage, setIsUpdatingImage] = useState(false); + const [isUpdatingBanner, setIsUpdatingBanner] = useState(false); const [enableSave, setEnableSave] = useState(false); const [image, setImage] = useState({ imageUrl: logo }); const [bannerImage, setBannerImage] = useState({ imageUrl: banner }); @@ -50,9 +53,11 @@ function CommunityEditorProfile({ let newImageUrl; let newBannerImageUrl; if (image.file) { + setIsUpdatingImage(true); newImageUrl = await uploadFile(image.file); } if (bannerImage.file) { + setIsUpdatingBanner(true); newBannerImageUrl = await uploadFile(bannerImage.file); } const updates = { @@ -68,6 +73,8 @@ function CommunityEditorProfile({ // updated fields if (Object.keys(updates).length > 0) await updateCommunity(updates); setIsUpdating(false); + setIsUpdatingImage(false); + setIsUpdatingBanner(false); setEnableSave(false); }; @@ -135,6 +142,13 @@ function CommunityEditorProfile({ accept: 'image/jpeg,image/png', }); + const imageDropClasses = classnames( + 'is-flex is-flex-direction-column is-align-items-center is-justify-content-center cursor-pointer rounded-lg', + { + 'border-dashed-dark': !bannerImage.file && !bannerImage.imageUrl, + } + ); + return ( - {!isUpdating && !image?.imageUrl && !image?.file && ( + {!isUpdatingImage && !image?.imageUrl && !image?.file && ( <> Avatar @@ -193,7 +207,7 @@ function CommunityEditorProfile({ }} /> )} - {!isUpdating && (image?.imageUrl || image?.file) && ( + {!isUpdatingImage && (image?.imageUrl || image?.file) && (
)} - {isUpdating && ( + {isUpdatingImage && (
- {!isUpdating && !bannerImage?.imageUrl && ( + {!isUpdatingBanner && !bannerImage?.imageUrl && ( <> Community Banner Image @@ -240,7 +254,7 @@ function CommunityEditorProfile({ )} {bannerImage?.imageUrl && (
)} - {!isUpdating && (bannerImage.imageUrl || bannerImage.file) && ( + {!isUpdatingBanner && (bannerImage.imageUrl || bannerImage.file) && (
)} - {isUpdating && ( + {isUpdatingBanner && (
@@ -235,13 +243,13 @@ export default function StepOne({
{banner ? (