From ce99712e57c4a12bae606865e15d278a4c5a4bd6 Mon Sep 17 00:00:00 2001 From: Koochr Date: Thu, 3 Nov 2022 00:13:45 +0000 Subject: [PATCH 1/2] Make profile image and description optional --- src/pages/CreateOrEditProfile.tsx | 88 +++++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 16 deletions(-) diff --git a/src/pages/CreateOrEditProfile.tsx b/src/pages/CreateOrEditProfile.tsx index b389ee0..aafaf2f 100644 --- a/src/pages/CreateOrEditProfile.tsx +++ b/src/pages/CreateOrEditProfile.tsx @@ -1,5 +1,12 @@ -import React, {ChangeEvent, FunctionComponent, useContext, useEffect, useState} from 'react'; -import {OutlinedButton, PrimaryButton} from '../components/Button'; +import React, { + ChangeEvent, + FunctionComponent, + useContext, + useEffect, + useRef, + useState +} from 'react'; +import {ErrorButton, OutlinedButton, PrimaryButton} from '../components/Button'; import {useNavigate} from 'react-router-dom'; import PageLayout from '../layouts/PageLayout'; import ImageOutlinedIcon from '@mui/icons-material/ImageOutlined'; @@ -10,6 +17,7 @@ const CreateOrEditProfile: FunctionComponent<{ edit?: boolean }> = ({edit}) => { const {userInfo, userSaving, saveUserInfo, userLoading, userError} = useContext(UserContext); const {theme} = useContext(ThemeContext); + const [showModal, setShowModal] = useState(false); const [avatar, setAvatar] = useState(userInfo.avatar); const [about, setAbout] = useState(userInfo.about); useEffect(() => { @@ -23,6 +31,12 @@ const CreateOrEditProfile: FunctionComponent<{ edit?: boolean }> = ({edit}) => { const navigate = useNavigate(); + const hiddenFileInput = useRef(null); + + const handleFileButtonClick = () => { + hiddenFileInput?.current?.click(); + }; + const handleFileInput = (e: ChangeEvent) => { setAvatar(e.target.files ? e.target.files[0] : null); }; @@ -68,19 +82,33 @@ const CreateOrEditProfile: FunctionComponent<{ edit?: boolean }> = ({edit}) => { /> )} {avatar && ( -

- - Change - - -

+
+

+ + Change + + +

+

+ setShowModal(true)}> + Remove + +

+
)}
@@ -100,7 +128,7 @@ const CreateOrEditProfile: FunctionComponent<{ edit?: boolean }> = ({edit}) => {
{saveUserInfo({avatar, about});}} > {userSaving ? 'Please Wait' : edit ? 'Update Profile' : 'Finish'} @@ -113,6 +141,34 @@ const CreateOrEditProfile: FunctionComponent<{ edit?: boolean }> = ({edit}) => {
+ {showModal && ( +
+
+
+
+

+ Are you sure you want to remove your profile image? +

+
+ setShowModal(false)}> + Cancel + + { + setAvatar(null); + setShowModal(false); + }} + > + Remove + +
+
+
+
+ )} ); From cfaf4d179ae9351210f41aeed5471c99d6216bc7 Mon Sep 17 00:00:00 2001 From: Koochr Date: Thu, 3 Nov 2022 00:44:12 +0000 Subject: [PATCH 2/2] Implement header image --- src/@types/types.ts | 1 + src/components/Header.tsx | 51 ++++++++++++----------- src/components/HeaderImage.tsx | 69 +++++++++++++++++++++++++++++++ src/context/UserContext.tsx | 28 ++++++++++--- src/pages/Admin.tsx | 5 ++- src/pages/Blog.tsx | 5 ++- src/pages/CreateOrEditProfile.tsx | 24 ++++++----- 7 files changed, 139 insertions(+), 44 deletions(-) create mode 100644 src/components/HeaderImage.tsx diff --git a/src/@types/types.ts b/src/@types/types.ts index a9082b6..900d074 100644 --- a/src/@types/types.ts +++ b/src/@types/types.ts @@ -31,6 +31,7 @@ export type BlogPost = { export type UserInfo = { avatar: Blob | null; about: string; + headerImage: Blob | null; } export type ToastNotification = { diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 9d12200..1cc0b62 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,41 +1,44 @@ import {useNavigate} from 'react-router-dom'; import {RoutesEnum} from '../@types/enums'; import {OutlinedButton, PrimaryButton} from './Button'; -import {FunctionComponent, useContext} from 'react'; +import {FunctionComponent, useContext, PropsWithChildren} from 'react'; import {UserContext} from '../context/UserContext'; import {ThemeContext} from '../context/ThemeContext'; -const Header: FunctionComponent = () => { +const Header: FunctionComponent = ({children}) => { const {isOwner} = useContext(UserContext); const {theme} = useContext(ThemeContext); - const navigate = useNavigate(); return ( -
-
+ <> +
navigate(RoutesEnum.home)} + className='mx-auto flex items-center justify-between' + style={{maxWidth: '1000px'}} > - {/* Logo will go here */} - BlogSoftware -
- {isOwner ? ( -
- navigate(RoutesEnum.customize)}> - Customize - - navigate(RoutesEnum.admin)}> - Manage Your Blog - +
navigate(RoutesEnum.home)} + > + {/* Logo will go here */} + BlogSoftware
- ) : null} -
-
+ {isOwner && ( +
+ navigate(RoutesEnum.customize)}> + Customize + + navigate(RoutesEnum.admin)}> + Manage Your Blog + +
+ )} +
+ +
+ {children} + ); }; diff --git a/src/components/HeaderImage.tsx b/src/components/HeaderImage.tsx new file mode 100644 index 0000000..3ad7ad2 --- /dev/null +++ b/src/components/HeaderImage.tsx @@ -0,0 +1,69 @@ +import { + ChangeEvent, + useContext, + FunctionComponent, + Dispatch, + SetStateAction +} from 'react'; +import ImageOutlinedIcon from '@mui/icons-material/ImageOutlined'; +import {ThemeContext} from '../context/ThemeContext'; + +const HeaderImage: FunctionComponent<{ + headerImage: Blob | null; + setHeaderImage?: Dispatch>; +}> = ({headerImage, setHeaderImage}) => { + const {theme} = useContext(ThemeContext); + + const handleFileInput = (e: ChangeEvent) => { + setHeaderImage!(e.target.files ? e.target.files[0] : null); + }; + + return ( + <> + {headerImage ? ( + profile + + ) : ( +
+ + {setHeaderImage && ( + <> +

Upload a Cover Photo

+ + + )} +
+ )} + {headerImage && setHeaderImage && ( +

+ + Change Cover + + +

+ )} + + ); +}; + +export default HeaderImage; diff --git a/src/context/UserContext.tsx b/src/context/UserContext.tsx index a8d5901..5d566c4 100644 --- a/src/context/UserContext.tsx +++ b/src/context/UserContext.tsx @@ -22,7 +22,7 @@ type UserContext = { ownerAddress: string; ownerIdentity: string; isOwner: boolean; - saveUserInfo: (info: Pick) => Promise; + saveUserInfo: (info: Pick) => Promise; } export const UserContext = createContext({} as unknown as UserContext); @@ -36,7 +36,8 @@ export const ProvideUserContext: FunctionComponent = ({childr const [userError, setUserError] = useState(false); const [userInfo, setUserInfo] = useState({ about: '', - avatar: null + avatar: null, + headerImage: null }); const [visitorAddress, setVisitorAddress] = useState(''); const [visitorIdentity, setVisitorIdentity] = useState(''); @@ -77,10 +78,19 @@ export const ProvideUserContext: FunctionComponent = ({childr setVisitorIdentity(visitorId); if (dataStorageHash) { - const {about, avatar} = await utils.getDataFromStorage(dataStorageHash); + const { + about, + avatar, + headerImage + } = await utils.getDataFromStorage(dataStorageHash); setUserInfo({ about, - avatar: avatar ? await window.point.storage.getFile({id: avatar}) : null + avatar: avatar + ? await window.point.storage.getFile({id: avatar}) + : null, + headerImage: headerImage + ? await window.point.storage.getFile({id: headerImage}) + : null }); } else if (_isOwner) { navigate(RoutesEnum.profile, {replace: true}); @@ -95,7 +105,7 @@ export const ProvideUserContext: FunctionComponent = ({childr getData(); }, []); - const saveUserInfo = async (info: Pick) => { + const saveUserInfo = async (info: Pick) => { setUserSaving(true); try { let avatarImage = ''; @@ -105,8 +115,16 @@ export const ProvideUserContext: FunctionComponent = ({childr const {data} = await window.point.storage.postFile(avatarFormData); avatarImage = data; } + let headerImage = ''; + if (info.headerImage) { + const headerImageFormData = new FormData(); + headerImageFormData.append('files', info.headerImage); + const {data} = await window.point.storage.postFile(headerImageFormData); + headerImage = data; + } const form = JSON.stringify({ avatar: avatarImage, + headerImage, about: info.about }); const file = new File([form], 'user.json', {type: 'application/json'}); diff --git a/src/pages/Admin.tsx b/src/pages/Admin.tsx index 037a48e..e3386af 100644 --- a/src/pages/Admin.tsx +++ b/src/pages/Admin.tsx @@ -18,6 +18,7 @@ import SearchBar from '../components/SearchBar'; import {ThemeContext} from '../context/ThemeContext'; import {UserContext} from '../context/UserContext'; import {PostsContext} from '../context/PostsContext'; +import HeaderImage from '../components/HeaderImage'; enum BlogFilterOptions { published = 'published', @@ -58,7 +59,7 @@ const FilterOption = ({ const Admin: FunctionComponent = () => { const navigate = useNavigate(); - const {isOwner, userLoading} = useContext(UserContext); + const {isOwner, userLoading, userInfo} = useContext(UserContext); const {posts, deletedPosts, postsLoading} = useContext(PostsContext); const [searchTerm, setSearchTerm] = useState(''); @@ -98,7 +99,7 @@ const Admin: FunctionComponent = () => { return (
-
+
= ({deleted}) => { const navigate = useNavigate(); @@ -22,7 +23,7 @@ const BlogPage: FunctionComponent<{deleted?: boolean}> = ({deleted}) => { const id = Number(query.get('id')); const {theme} = useContext(ThemeContext); - const {visitorAddress, isOwner, visitorIdentity} = useContext(UserContext); + const {visitorAddress, isOwner, visitorIdentity, userInfo} = useContext(UserContext); const {setToast} = useContext(ToastContext); const {posts, deletedPosts, postsError, postsLoading} = useContext(PostsContext); const post = useMemo( @@ -245,7 +246,7 @@ const BlogPage: FunctionComponent<{deleted?: boolean}> = ({deleted}) => { return ( -
+
= ({edit}) => { const {userInfo, userSaving, saveUserInfo, userLoading, userError} = useContext(UserContext); @@ -19,6 +21,7 @@ const CreateOrEditProfile: FunctionComponent<{ edit?: boolean }> = ({edit}) => { const [showModal, setShowModal] = useState(false); const [avatar, setAvatar] = useState(userInfo.avatar); + const [headerImage, setHeaderImage] = useState(null); const [about, setAbout] = useState(userInfo.about); useEffect(() => { if (userInfo.about) { @@ -46,12 +49,9 @@ const CreateOrEditProfile: FunctionComponent<{ edit?: boolean }> = ({edit}) => { return ( -
-
- {/* Logo will go here */} - BlogSoftware -
-
+
+ +

{edit ? 'Update' : 'Complete'} Your Profile @@ -88,8 +88,9 @@ const CreateOrEditProfile: FunctionComponent<{ edit?: boolean }> = ({edit}) => { > - Change + className='left-1/2 -translate-x-1/2 cursor-pointer underline' + > + Change = ({edit}) => { > setShowModal(true)}> - Remove + onClick={() => setShowModal(true)} + > + Remove

@@ -129,7 +131,7 @@ const CreateOrEditProfile: FunctionComponent<{ edit?: boolean }> = ({edit}) => {
{saveUserInfo({avatar, about});}} + onClick={() => {saveUserInfo({avatar, about, headerImage});}} > {userSaving ? 'Please Wait' : edit ? 'Update Profile' : 'Finish'}