Skip to content

Commit

Permalink
Adds co authors details below post subtitle for enterprise theme
Browse files Browse the repository at this point in the history
  • Loading branch information
Harsh062 committed Dec 19, 2023
1 parent af9db15 commit 0d81b1f
Show file tree
Hide file tree
Showing 9 changed files with 380 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Button } from './button';
import CloseSVG from './icons/svgs/CloseSVG';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import CustomScrollArea from './scroll-area';
import { DEFAULT_AVATAR } from '../utils/const';
import { ResizableImage } from './resizable-image';
import { useAppContext } from './contexts/appContext';
import { PostFullFragment } from '../generated/graphql';

type CoAuthorsModalProps = {
closeModal: () => void;
};

const AuthorCard = ({ author }: { author: PostFullFragment['author']; }) => {

return (
<div className="flex flex-row items-center justify-between" key={author.id.toString()}>
<div
className="flex w-full flex-wrap items-center justify-between overflow-hidden px-0 py-2.5"
>
<div className="flex flex-wrap items-center overflow-hidden">
<a href={`https://hashnode.com/@${author.username}`} title={author.name} className="mr-2 w-8">
<ResizableImage
src={author.profilePicture || DEFAULT_AVATAR}
resize={{ w: 200, h: 200, c: 'face' }}
className="mr-3 h-8 w-8 rounded-full"
/>
</a>
<div className="flex flex-row items-center text-clip">
<a
href={`https://hashnode.com/@${author.username}`}
title={author.name}
className="truncate font-sans text-sm font-medium text-slate-700 dark:text-slate-200"
>
{author.name}
</a>
</div>
</div>
</div>
</div>
);
};

export default function CoAuthorsModal({ closeModal }: CoAuthorsModalProps) {
const { post } = useAppContext();
const authors = [post?.author, ...(post?.coAuthors || [])];

return (
<DialogPrimitive.Root open>
<DialogPrimitive.Portal>
<DialogPrimitive.Overlay
onClick={closeModal}
className="fixed inset-0 z-50 bg-slate-900 opacity-50 transition-opacity duration-300 ease-out dark:bg-slate-600"
/>
<DialogPrimitive.Content
onEscapeKeyDown={closeModal}
className="fixed bottom-0 left-0 right-0 z-50 flex w-full max-w-[1200px] flex-col items-center overflow-hidden rounded-b-none rounded-t-lg border-slate-200 bg-white text-slate-700 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-50 md:bottom-50 md:left-50 md:w-96 md:-translate-x-1/2 md:translate-y-1/2 md:rounded-xl lg:w-108 xl:w-116"
>
<DialogPrimitive.DialogTitle className="w-full px-6 py-4 text-lg font-semibold">
Authors in this article
</DialogPrimitive.DialogTitle>
<DialogPrimitive.Close className="absolute right-2 top-4 text-slate-900 dark:text-slate-50" asChild>
<Button
type="outline"
label=""
icon={<CloseSVG className="h-5 w-5 fill-current" />}
className="rounded-xl !border-transparent !px-3 !py-2 hover:bg-neutral-800 dark:text-white"
onClick={closeModal}
/>
</DialogPrimitive.Close>
<CustomScrollArea>
<div className="px-6 pb-8">
{authors.map((author) => {
if (!author) {
return null;
}
return <AuthorCard author={author} key={author.id.toString()} />;
})}
</div>
</CustomScrollArea>
</DialogPrimitive.Content>
</DialogPrimitive.Portal>
</DialogPrimitive.Root>
);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { resizeImage } from '@starter-kit/utils/image';
import { User } from '../generated/graphql';
import { PostFullFragment, User } from '../generated/graphql';
import { Avatar } from './avatar';
import { CoverImage } from './cover-image';
import { DateFormatter } from './date-formatter';
import CoAuthorsModal from './co-authors-modal';
import { ReadTimeInMinutes } from './post-read-time-in-minutes';
import { PostTitle } from './post-title';
import { useAppContext } from './contexts/appContext';
import { twJoin } from 'tailwind-merge';
import { useState } from 'react';
import ProfileImage from './profile-image';

type Author = Pick<User, 'username' | 'name' | 'profilePicture'>;

Expand All @@ -17,20 +22,75 @@ type Props = {
};

export const PostHeader = ({ title, coverImage, date, author, readTimeInMinutes }: Props) => {
const { post: _post } = useAppContext();
const post = _post as unknown as PostFullFragment;
const authorsArray = [post.author, ...(post.coAuthors || [])];
const [isCoAuthorModalVisible, setIsCoAuthorModalVisible] = useState(false);
const closeCoAuthorModal = () => {
setIsCoAuthorModalVisible(false);
};
const openCoAuthorModal = () => {
setIsCoAuthorModalVisible(true);
};
return (
<>
<PostTitle>{title}</PostTitle>
<div className="flex flex-row flex-wrap items-center justify-center w-full gap-2 px-2 text-slate-700 dark:text-neutral-300 md:px-0">
<Avatar
username={author.username}
name={author.name}
size={8}
picture={author.profilePicture}
/>
<span className="block font-bold text-slate-500">&middot;</span>
<DateFormatter dateString={date} />
{readTimeInMinutes && <span className="block font-bold text-slate-500">&middot;</span>}
<ReadTimeInMinutes readTimeInMinutes={readTimeInMinutes} />
<div className="mb-5 flex w-full flex-row items-center justify-center md:mb-0 md:w-auto md:justify-start">
{authorsArray.map((coAuthor, index) => (
<div
key={coAuthor.id?.toString()}
style={{ zIndex: index + 1 }}
className={twJoin(
'overflow-hidden rounded-full bg-slate-200 dark:bg-white/20 md:mr-3',
index > 0 ? 'hidden md:block' : '',
authorsArray.length === 1
? 'h-10 w-10 md:h-12 md:w-12'
: 'h-8 w-8 border-2 border-slate-100 dark:border-slate-800 md:h-9 md:w-9 [&:not(:first-of-type)]:-ml-3 md:[&:not(:first-of-type)]:-ml-6 ',
)}
>
<ProfileImage user={coAuthor} width="200" height="200" hoverDisabled={true} />
</div>
))}
{post.coAuthors && post.coAuthors.length > 0 && (
<button
onClick={openCoAuthorModal}
style={{ zIndex: post.coAuthors?.length }}
className="relative -ml-3 flex h-8 w-8 items-center justify-center overflow-hidden rounded-full border-1-1/2 border-slate-100 bg-slate-100 px-1 group-hover:border-slate-200 dark:border-slate-800 dark:bg-slate-600 dark:text-white group-hover:dark:border-slate-700 md:hidden"
>
<p className="truncate text-xs font-normal">+{post.coAuthors.length}</p>
</button>
)}
{!post.coAuthors?.length && (
<a
href={`https://hashnode.com/@${post.author.username}`}
className="ml-2 font-semibold text-slate-600 dark:text-white md:ml-0"
>
<span>{post.author.name}</span>
</a>
)}
{post?.coAuthors?.length && post.coAuthors.length > 0 && (
<button
onClick={openCoAuthorModal}
className="ml-2 text-left font-semibold text-slate-600 hover:underline dark:text-white"
>
<span>{post.author.name}</span>
{post.coAuthors && (
<span className="font-normal">
{' '}
<br className="block sm:hidden" />
with {post.coAuthors.length} co-author{post.coAuthors.length === 1 ? '' : 's'}
</span>
)}
</button>
)}
</div>
<div className="mb-5 flex w-full flex-row items-center justify-center md:mb-0 md:w-auto md:justify-start">
<span className="mx-3 hidden font-bold text-slate-500 md:block">&middot;</span>
<DateFormatter dateString={date} />
{readTimeInMinutes && <span className="mx-3 hidden font-bold text-slate-500 md:block">&middot;</span>}
<ReadTimeInMinutes readTimeInMinutes={readTimeInMinutes} />
</div>
</div>
{coverImage && (
<div className="w-full px-5 sm:mx-0">
Expand All @@ -41,6 +101,9 @@ export const PostHeader = ({ title, coverImage, date, author, readTimeInMinutes
/>
</div>
)}
{isCoAuthorModalVisible && (
<CoAuthorsModal closeModal={closeCoAuthorModal} />
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import { twMerge } from 'tailwind-merge';
import Image from 'next/legacy/image';

import { resizeImage } from '@starter-kit/utils/image';
import { DEFAULT_AVATAR } from '../utils/const';

export default class ProfileImage extends React.Component {
componentDidMount() {
if (!this.props.user) {
return;
}
if (this.props.user.isDeactivated) {
return;
}
}

render() {
const user = this.props.user;
const blogURL = this.props.blogURL;
return (
<a
href={
blogURL
? blogURL
: user && !user.isDeactivated
? `https://hashnode.com/@${user.username}`
: this.props.postUrlForAnonymous
? this.props.postUrlForAnonymous
: '#'
}
ref={(c) => {
this.profileImage = c;
}}
className={`relative block h-full w-full`}
>
<Image
className={twMerge(this.props.className, `relative z-20 block w-full rounded-full`)}
src={
user && user.profilePicture
? resizeImage(user.profilePicture, { w: this.props.width || 70, h: this.props.height || 70, c: 'face' })
: DEFAULT_AVATAR
}
width={this.props.width ? parseInt(this.props.width) : 70}
height={this.props.height ? parseInt(this.props.height) : 70}
// resize={{
// w: this.props.width ? parseInt(this.props.width) : 70,
// h: this.props.height ? parseInt(this.props.height) : 70,
// c: 'face',
// }}
alt={user ? user.name + "'s photo" : undefined}
/>
</a>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import { resizeImage } from '@starter-kit/utils/image';

import { DEFAULT_AVATAR } from '../utils/const';
import { twMerge } from 'tailwind-merge';

/**
* Progressive Image Component which loads low resolution version image before loading original
* @param {string} options.src Image source
* @param {string} options.alt Image alt text
* @param {string} options.className Classname string
* @param {...[type]} options.restOfProps Rest of the props passed to the child
*/
class ProgressiveImage extends React.Component<{
resize: any;
src: string;
alt: string;
className: string;
css: string;
}> {
image: HTMLImageElement | null = null;

componentDidMount() {
if (!(window as any).lazySizes && this.image) {
this.image.setAttribute('src', this.image.getAttribute('data-src') || '');
}
}

// TODO: Improve type
replaceBadImage = (e: any) => {
// eslint-disable-next-line react/destructuring-assignment
if (this.props.resize && this.props.resize.c !== 'face') {
return;
}
e.target.onerror = null;
e.target.src = DEFAULT_AVATAR;
};

render() {
const { src, alt, className, resize = {}, ...restOfProps } = this.props;

if (!src || src.trim().length === 0) return null;

const resizedImage = resizeImage(src, resize);

return (
<img
data-sizes="auto"
loading="lazy"
src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
// eslint-disable-next-line no-return-assign
ref={(c) => (this.image = c || null)}
data-src={resizedImage}
width={resize.w}
height={resize.h}
onError={this.replaceBadImage}
alt={alt}
className={twMerge('lazyload block w-full', className)}
{...restOfProps}
/>
);
}
}

export default ProgressiveImage;

export { ProgressiveImage };
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ProgressiveImage } from './progressive-image';

function ResizableImage(props) {
const { src, alt, resize, className, ...restOfTheProps } = props;

return (
<ProgressiveImage alt={alt} src={src || props.default} resize={resize} className={className} {...restOfTheProps} />
);
}

export default ResizableImage;
export { ResizableImage };
Loading

0 comments on commit 0d81b1f

Please sign in to comment.