Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/@types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export type BlogPost = {
export type UserInfo = {
avatar: Blob | null;
about: string;
headerImage: Blob | null;
}

export type ToastNotification = {
Expand Down
51 changes: 27 additions & 24 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -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<PropsWithChildren> = ({children}) => {
const {isOwner} = useContext(UserContext);
const {theme} = useContext(ThemeContext);

const navigate = useNavigate();

return (
<header className={`py-3 sticky top-0 bg-${theme[0]} shadow-lg z-10`}>
<div
className='mx-auto flex items-center justify-between'
style={{maxWidth: '1000px'}}
>
<>
<header className={`py-3 sticky top-0 bg-${theme[0]} shadow-lg z-10`}>
<div
className='cursor-pointer'
onClick={() => navigate(RoutesEnum.home)}
className='mx-auto flex items-center justify-between'
style={{maxWidth: '1000px'}}
>
{/* Logo will go here */}
<span className='font-medium'>BlogSoftware</span>
</div>
{isOwner ? (
<div className='flex items-center space-x-2'>
<OutlinedButton onClick={() => navigate(RoutesEnum.customize)}>
Customize
</OutlinedButton>
<PrimaryButton onClick={() => navigate(RoutesEnum.admin)}>
Manage Your Blog
</PrimaryButton>
<div
className='cursor-pointer'
onClick={() => navigate(RoutesEnum.home)}
>
{/* Logo will go here */}
<span className='font-medium'>BlogSoftware</span>
</div>
) : null}
</div>
</header>
{isOwner && (
<div className='flex items-center space-x-2'>
<OutlinedButton onClick={() => navigate(RoutesEnum.customize)}>
Customize
</OutlinedButton>
<PrimaryButton onClick={() => navigate(RoutesEnum.admin)}>
Manage Your Blog
</PrimaryButton>
</div>
)}
</div>

</header>
{children}
</>
);
};

Expand Down
69 changes: 69 additions & 0 deletions src/components/HeaderImage.tsx
Original file line number Diff line number Diff line change
@@ -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<SetStateAction<Blob | null>>;
}> = ({headerImage, setHeaderImage}) => {
const {theme} = useContext(ThemeContext);

const handleFileInput = (e: ChangeEvent<HTMLInputElement>) => {
setHeaderImage!(e.target.files ? e.target.files[0] : null);
};

return (
<>
{headerImage ? (
<img
src={URL.createObjectURL(headerImage)}
className='w-full h-60 border-2 border-gray-200 object-cover'
alt='profile'
/>

) : (
<div className='h-65 w-screen p-8 border-2 bg-gray-100 border-gray-300 flex flex-col items-center justify-center relative overflow-hidden'>
<ImageOutlinedIcon
sx={{height: 42, width: 42}}
className='text-gray-500'
/>
{setHeaderImage && (
<>
<h3 className='font-bold text-lg mb-4'>Upload a Cover Photo</h3>
<input
type='file'
accept='image/*'
title='Upload a file'
className='absolute w-full h-full opacity-0 cursor-pointer'
onChange={handleFileInput}
/>
</>
)}
</div>
)}
{headerImage && setHeaderImage && (
<p
className={`relative text-sm mt-4 transition-all text-${theme[2]} text-opacity-50 hover:text-opacity-100`}
>
<span className='absolute left-1/2 -translate-x-1/2 cursor-pointer underline'>
Change Cover
</span>
<input
type='file'
title='Upload a file'
className='absolute w-20 h-6 opacity-0 left-1/2 -translate-x-1/2 cursor-pointer'
onChange={handleFileInput}
/>
</p>
)}
</>
);
};

export default HeaderImage;
28 changes: 23 additions & 5 deletions src/context/UserContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type UserContext = {
ownerAddress: string;
ownerIdentity: string;
isOwner: boolean;
saveUserInfo: (info: Pick<UserInfo, 'avatar' | 'about'>) => Promise<void>;
saveUserInfo: (info: Pick<UserInfo, 'avatar' | 'about' | 'headerImage'>) => Promise<void>;
}

export const UserContext = createContext<UserContext>({} as unknown as UserContext);
Expand All @@ -36,7 +36,8 @@ export const ProvideUserContext: FunctionComponent<PropsWithChildren> = ({childr
const [userError, setUserError] = useState<boolean>(false);
const [userInfo, setUserInfo] = useState<UserInfo>({
about: '',
avatar: null
avatar: null,
headerImage: null
});
const [visitorAddress, setVisitorAddress] = useState<string>('');
const [visitorIdentity, setVisitorIdentity] = useState<string>('');
Expand Down Expand Up @@ -77,10 +78,19 @@ export const ProvideUserContext: FunctionComponent<PropsWithChildren> = ({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});
Expand All @@ -95,7 +105,7 @@ export const ProvideUserContext: FunctionComponent<PropsWithChildren> = ({childr
getData();
}, []);

const saveUserInfo = async (info: Pick<UserInfo, 'avatar' | 'about'>) => {
const saveUserInfo = async (info: Pick<UserInfo, 'avatar' | 'about' | 'headerImage'>) => {
setUserSaving(true);
try {
let avatarImage = '';
Expand All @@ -105,8 +115,16 @@ export const ProvideUserContext: FunctionComponent<PropsWithChildren> = ({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'});
Expand Down
5 changes: 3 additions & 2 deletions src/pages/Admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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<string>('');

Expand Down Expand Up @@ -98,7 +99,7 @@ const Admin: FunctionComponent = () => {
return (
<PageLayout>
<div className='h-screen overflow-hidden'>
<Header />
<Header><HeaderImage headerImage={userInfo.headerImage}/></Header>
<main
className='flex mt-4 mx-auto'
style={{maxWidth: '1000px', height: window.screen.height - 220}}
Expand Down
5 changes: 3 additions & 2 deletions src/pages/Blog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {PostsContext} from '../context/PostsContext';
import {ToastContext} from '../context/ToastContext';
import {UserContext} from '../context/UserContext';
import {ThemeContext} from '../context/ThemeContext';
import HeaderImage from '../components/HeaderImage';

const BlogPage: FunctionComponent<{deleted?: boolean}> = ({deleted}) => {
const navigate = useNavigate();
Expand All @@ -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(
Expand Down Expand Up @@ -245,7 +246,7 @@ const BlogPage: FunctionComponent<{deleted?: boolean}> = ({deleted}) => {

return (
<PageLayout>
<Header />
<Header><HeaderImage headerImage={userInfo.headerImage}/></Header>
<main className='pb-4 pt-8 mx-auto' style={{maxWidth: '720px'}}>
<div
className='flex items-center opacity-40 cursor-pointer hover:opacity-90 transition-all -ml-4'
Expand Down
Loading