Skip to content

Commit

Permalink
Multi authors (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
hazemahmedx0 authored Jan 8, 2024
1 parent 880adf7 commit 1166219
Show file tree
Hide file tree
Showing 21 changed files with 744 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { PostFullFragment } from '../generated/graphql';
import { useAppContext } from './contexts/appContext';
import PostAuthorInfo from './post-author-info';

function AboutAuthor() {
const { post: _post } = useAppContext();
const post = _post as unknown as PostFullFragment;
const { publication, author } = post;
let coAuthors = post.coAuthors || [];

const allAuthors = publication?.isTeam ? [author, ...coAuthors] : [author];

return (
<div className="mx-auto mb-5 mt-10 flex w-full flex-col gap-16 px-5 md:max-w-screen-md">
<div className="flex-1 px-2">
<div className="flex flex-col flex-wrap items-start md:flex-nowrap">
<h3 className="border-b-1-1/2 mb-4 w-full pb-2 text-base font-medium tracking-wider text-slate-500 dark:border-slate-800 dark:text-slate-400 ">
Written by
</h3>
<div className="flex w-full flex-col gap-12">
{allAuthors.map((_author) => {
return <PostAuthorInfo key={_author.id.toString()} author={_author} />;
})}
</div>
</div>
</div>
</div>
);
}

export default AboutAuthor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { PostFullFragment } from '../generated/graphql';
import { DEFAULT_AVATAR } from '../utils/const';
import { Button } from './button';
import { useAppContext } from './contexts/appContext';
import CloseSVG from './icons/svgs/CloseSVG';
import { ResizableImage } from './resizable-image';
import CustomScrollArea from './scroll-area';

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="md:bottom-50 md:left-50 lg:w-108 xl:w-116 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:w-96 md:-translate-x-1/2 md:translate-y-1/2 md:rounded-xl"
>
<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
@@ -0,0 +1,53 @@
import { ImgHTMLAttributes } from 'react';

import Image, { ImageProps } from 'next/legacy/image';

type Props = {
src: any; // can be string or StaticImport of next/image
alt: string;
originalSrc: string;
} & ImgHTMLAttributes<any> &
ImageProps;

/**
* Conditionally renders native img for gifs and next/image for other types
* @param props
* @returns <img /> or <Image/>
*/
function CustomImage(props: Props) {
const { originalSrc, ...originalRestOfTheProps } = props;
const {
alt = '',
loader,
quality,
priority,
loading,
unoptimized,
objectFit,
objectPosition,
src,
width,
height,
layout,
placeholder,
blurDataURL,
...restOfTheProps
} = originalRestOfTheProps; // Destructured next/image props on purpose, so that unwanted props don't end up in <img />

if (!originalSrc) {
return null;
}

const isGif = originalSrc.substr(-4) === '.gif';
const isHashnodeCDNImage = src.indexOf('cdn.hashnode.com') > -1;

if (isGif || !isHashnodeCDNImage) {
// restOfTheProps will contain all props excluding the next/image props
return <img {...restOfTheProps} alt={alt} src={src || originalSrc} />;
}

// Notes we are passing whole props object here
return <Image {...originalRestOfTheProps} src={src || originalSrc} />;
}

export default CustomImage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';

export default class BookOpenSVG extends React.Component {
render() {
return (
<svg className={this.props.className} viewBox="0 0 576 512">
<path d="M540.9 56.77c-45.95-16.66-90.23-24.09-129.1-24.75-60.7.94-102.7 17.45-123.8 27.72-21.1-10.27-64.1-26.8-123.2-27.74-40-.05-84.4 8.35-129.7 24.77C14.18 64.33 0 84.41 0 106.7v302.9c0 14.66 6.875 28.06 18.89 36.8 11.81 8.531 26.64 10.98 40.73 6.781 118.9-36.34 209.3 19.05 214.3 22.19C277.8 477.6 281.2 480 287.1 480c6.52 0 10.12-2.373 14.07-4.578 10.78-6.688 98.3-57.66 214.3-22.27 14.11 4.25 28.86 1.75 40.75-6.812C569.1 437.6 576 424.2 576 409.6V106.7c0-22.28-14.2-42.35-35.1-49.93zM272 438.1c-24.95-12.03-71.01-29.37-130.5-29.37-27.83 0-58.5 3.812-91.19 13.77-4.406 1.344-9 .594-12.69-2.047C34.02 417.8 32 413.1 32 409.6V106.7c0-8.859 5.562-16.83 13.86-19.83C87.66 71.7 127.9 63.95 164.5 64c51.8.81 89.7 15.26 107.5 23.66V438.1zm272-28.5c0 4.375-2.016 8.234-5.594 10.84-3.766 2.703-8.297 3.422-12.69 2.125C424.1 391.6 341.3 420.4 304 438.3V87.66c17.8-8.4 55.7-22.85 107.4-23.66 35.31-.063 76.34 7.484 118.8 22.88 8.2 3 13.8 10.96 13.8 19.82v302.9z" />
</svg>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';

export default class CloseSVG extends React.Component {
render() {
return (
<svg className={this.props.className} viewBox="0 0 320 512">
<path d="M193.94 256L296.5 153.44l21.15-21.15c3.12-3.12 3.12-8.19 0-11.31l-22.63-22.63c-3.12-3.12-8.19-3.12-11.31 0L160 222.06 36.29 98.34c-3.12-3.12-8.19-3.12-11.31 0L2.34 120.97c-3.12 3.12-3.12 8.19 0 11.31L126.06 256 2.34 379.71c-3.12 3.12-3.12 8.19 0 11.31l22.63 22.63c3.12 3.12 8.19 3.12 11.31 0L160 289.94 262.56 392.5l21.15 21.15c3.12 3.12 8.19 3.12 11.31 0l22.63-22.63c3.12-3.12 3.12-8.19 0-11.31L193.94 256z" />
</svg>
);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import ArticleSVG from './ArticleSVG';
import BookOpenSVG from './BookOpenSVG';
import ChevronDownSVG from './ChevronDownSVG';
import CloseSVG from './CloseSVG';
import ExternalArrowSVG from './ExternalArrowSVG';
import GithubSVG from './GithubSVG';
import HashnodeSVG from './HashnodeSVG';
Expand Down Expand Up @@ -31,9 +33,11 @@ export {
AboutUsNavSVG,
ArticleSVG,
BlogNavSGV,
BookOpenSVG,
CareersSVG,
CaseStudiesSVG,
ChevronDownSVG,
CloseSVG,
CloudNavBarSVG,
CommunitySVG,
ContactSVG,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { twJoin } from 'tailwind-merge';

import { getBlurHash, resizeImage } from '@starter-kit/utils/image';
import CustomImage from './custom-image';

function PostAuthorInfo(props: any) {
const { author } = props;

return (
<div className="flex w-full flex-1 flex-col md:flex-row">
<div className="mb-4 flex w-full flex-1 flex-row md:mb-0 ">
<div className="mr-4 flex flex-row md:mb-0">
<a
href={`https://hashnode.com/@${author.username}`}
className="block h-10 w-10 overflow-hidden rounded-full border dark:border-slate-800 md:h-14 md:w-14"
>
<CustomImage
className="block"
placeholder="blur"
originalSrc={author.profilePicture}
src={resizeImage(author.profilePicture, {
w: 256,
h: 256,
c: 'thumb',
})}
blurDataURL={getBlurHash(
resizeImage(author.profilePicture, {
w: 256,
h: 256,
c: 'thumb',
}),
)}
width={256}
height={256}
alt={author.name}
/>
</a>
</div>
<div
className={twJoin(
'flex flex-1 flex-col justify-center md:justify-start',
!author.bio?.html ? 'md:justify-center' : '',
)}
>
<div className="flex flex-row items-center md:mb-1">
<h1 className="font-sans text-lg font-semibold text-slate-800 dark:text-slate-100">
<a href={`https://hashnode.com/@${author.username}`}>{author.name}</a>
</h1>
</div>
{author.bio?.html && (
<div className="hidden pr-2 md:block">
<div
className="prose dark:prose-dark text-slate-600 dark:text-slate-300"
dangerouslySetInnerHTML={{ __html: author.bio?.html }}
/>
</div>
)}
</div>
</div>
{author.bio?.html && (
<div className="mb-4 block md:hidden">
<div
className="prose dark:prose-dark text-slate-600 "
dangerouslySetInnerHTML={{ __html: author.bio?.html }}
/>
</div>
)}
</div>
);
}

export default PostAuthorInfo;
Loading

0 comments on commit 1166219

Please sign in to comment.