Skip to content

Commit

Permalink
feat(public-layout): scaffold - PostPage
Browse files Browse the repository at this point in the history
  • Loading branch information
yoopark committed Apr 21, 2024
1 parent c5bea5d commit dfd7964
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 10 deletions.
82 changes: 77 additions & 5 deletions app/(public)/users/[userHandle]/posts/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,87 @@
import Image from 'next/image';

import { EllipsisVertical, Share2 } from 'lucide-react';

import {
AppBar,
AppBarBack,
AppBarTitle,
} from '@/components/ui-unstable/app-bar';
import { CopyCurrentPageTrigger } from '@/components/ui-unstable/copy-current-page-trigger';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { IconButton } from '@/components/ui/icon-button';
import { PostPageAllPostSection } from '@/features/post-page/post-page-all-post-section';
import { PostPageBottomBar } from '@/features/post-page/post-page-bottom-bar';
import { ReportPostBottomActionSheet } from '@/features/post-page/report-post-bottom-action-sheet';
import { UserHandlePageTrendingPostSection } from '@/features/user-handle-page/user-handle-page-trending-post-section';
import { generatePost } from '@/lib/utils';

const post = generatePost();

const { author, thumbnail } = post;

type PostPageProps = {
params: {
username: string;
userHandle: string;
slug: string;
};
};

const PostPage = ({ params: { username, slug } }: PostPageProps) => {
const PostPage = ({ params: { userHandle: _, slug: __ } }: PostPageProps) => {
return (
<div>
PostPage (username: {username}, slug: {slug})
</div>
<>
<AppBar className="border-b-0 bg-blccu-white/90 backdrop-blur-lg">
<AppBarBack />
<div className="item-center flex w-full justify-between">
<AppBarTitle className="flex items-center gap-3">
<Avatar size="xs">
<AvatarImage src={author.profileImage} />
<AvatarFallback className="bg-blccu-neutral-400" />
</Avatar>
<p className="text-sm font-medium">{author.username}</p>
</AppBarTitle>
<ReportPostBottomActionSheet
trigger={
<IconButton size="lg">
<EllipsisVertical className="h-5 w-5" />
</IconButton>
}
/>
</div>
</AppBar>
<div className="pt-14">
<Image
src={thumbnail}
alt="Post thumbnail"
width={1280}
height={2000}
/>
<Image
src={thumbnail}
alt="Post thumbnail"
width={1280}
height={2000}
/>
<div className="flex items-center">
<ReportPostBottomActionSheet
trigger={
<IconButton size="lg">
<EllipsisVertical className="h-5 w-5" />
</IconButton>
}
/>
<CopyCurrentPageTrigger asChild>
<IconButton size="lg">
<Share2 className="h-5 w-5" />
</IconButton>
</CopyCurrentPageTrigger>
</div>
<PostPageAllPostSection user={author} />
<UserHandlePageTrendingPostSection user={author} />
<div className="h-[24px]" />
</div>
<PostPageBottomBar />
</>
);
};

Expand Down
14 changes: 10 additions & 4 deletions components/ui-unstable/app-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,26 @@ const AppBarBack = ({ onClick }: AppBarBackProps) => {

type AppBarTitleProps = {
align?: 'center' | 'left';
} & PropsWithChildren;
} & PropsWithChildren &
PropsWithClassName;

const AppBarTitle = ({ align = 'left', children }: AppBarTitleProps) => {
const AppBarTitle = ({
align = 'left',
children,
className,
}: AppBarTitleProps) => {
return (
<p
<div
className={cn(
'text-lg font-bold',
align === 'center' &&
'absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2',
align === 'left' && 'ml-2',
className,
)}
>
{children}
</p>
</div>
);
};

Expand Down
2 changes: 1 addition & 1 deletion components/ui/button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const meta: Meta<typeof Button> = {
control: { type: 'radio' },
},
size: {
options: ['default', 'sm'],
options: ['default', 'sm', 'none'],
control: { type: 'radio' },
},
radius: {
Expand Down
1 change: 1 addition & 0 deletions components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const buttonVariants = cva(
size: {
default: 'h-10 px-6',
sm: 'h-9 text-xs px-5',
none: 'h-auto px-0' /* it is only used for ghost variant */,
},
radius: {
default: 'rounded-lg',
Expand Down
55 changes: 55 additions & 0 deletions features/post-page/post-page-all-post-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Link from 'next/link';

import {
Section,
SectionContent,
SectionTitle,
} from '@/components/ui-unstable/section';
import { StackedPostCard } from '@/components/ui-unstable/stacked-post-card';
import { ROUTES } from '@/constants/routes';
import { getPostPageAllPostSectionTitleDescriptor } from '@/lib/get-descriptor';
import { generatePosts } from '@/lib/utils';
import { type User } from '@/types/mocking-entity';

type PostPageAllPostSectionProps = {
user: User;
};

const PostPageAllPostSection = ({ user }: PostPageAllPostSectionProps) => {
const posts = generatePosts(5, user);

const titleDescriptor = getPostPageAllPostSectionTitleDescriptor(
user.username,
);

return (
<Section>
<SectionTitle className="mx-4">{titleDescriptor}</SectionTitle>
<SectionContent>
<div className="flex flex-col">
{posts.map(
(
{ author, title, slug, description, thumbnail, createdAt },
index,
) => (
<Link href={ROUTES.POST_OF(author.handle, slug)} key={index}>
<div className="px-4 py-3">
<StackedPostCard
username={author.username}
userHandle={author.handle}
title={title}
description={description}
thumbnail={thumbnail}
date={createdAt}
/>
</div>
</Link>
),
)}
</div>
</SectionContent>
</Section>
);
};

export { PostPageAllPostSection };
39 changes: 39 additions & 0 deletions features/post-page/post-page-author-profile-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import { getFollowerDescriptor } from '@/lib/get-descriptor';
import { type User } from '@/types/mocking-entity';

type PostPageAuthorProfileSectionProps = {
author: User;
};

const PostPageAuthorProfileSection = ({
author,
}: PostPageAuthorProfileSectionProps) => {
const { username, description, profileImage, followerCount } = author;

const followerDescriptor = getFollowerDescriptor(followerCount);

return (
<section className="mx-4 mt-12 flex items-center rounded-xl py-4 shadow-xl">
<div className="flex flex-1 flex-col items-center gap-4">
<Avatar>
<AvatarImage src={profileImage} />
<AvatarFallback className="bg-blccu-neutral-400" />
</Avatar>
<div className="flex flex-col items-center gap-1">
<p className="text-sm font-medium">{username}</p>
<p className="max-w-40 text-center text-xs text-blccu-neutral-400">
{description}
</p>
</div>
</div>
<div className="flex flex-1 flex-col items-center gap-1">
<Button>팔로우</Button>
<p className="text-xs text-blccu-neutral-600">{followerDescriptor}</p>
</div>
</section>
);
};

export { PostPageAuthorProfileSection };
68 changes: 68 additions & 0 deletions features/post-page/post-page-bottom-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use client';

import Link from 'next/link';

import { useState } from 'react';

import { Heart, MessageCircleMore, Share2 } from 'lucide-react';

import { CopyCurrentPageTrigger } from '@/components/ui-unstable/copy-current-page-trigger';
import { Button } from '@/components/ui/button';
import { IconButton } from '@/components/ui/icon-button';
import { ROUTES } from '@/constants/routes';
import { cn, generatePost, randomInt } from '@/lib/utils';

const post = generatePost();

const { slug, author } = post;

const initialLikeCount = randomInt(0, 1000);
const commentCount = randomInt(0, 30);

const PostPageBottomBar = () => {
/**
* @note
* only for demo purposes
*
*/
// start-region
const [like, setLike] = useState<boolean>(false);
const [likeCount, setLikeCount] = useState<number>(initialLikeCount);

const toggleLike = () => {
setLike((prev) => !prev);
setLikeCount((prev) => (like ? prev - 1 : prev + 1));
};
// end-region

return (
<div className="fixed bottom-0 mx-auto flex h-14 w-full max-w-md items-center justify-between bg-blccu-white/90 px-2 backdrop-blur-lg">
<div className="flex items-center gap-1">
<Button
size="none"
variant="ghost"
className="flex items-center gap-1 px-2 py-1"
onClick={toggleLike}
>
<Heart
className={cn('h-4 w-4', like && 'fill-blccu-red text-blccu-red')}
/>
<p className="text-sm">{likeCount.toLocaleString()}</p>
</Button>
<Link href={ROUTES.COMMENTS_OF(author.handle, slug)}>
<div className="flex items-center gap-1 px-2 py-1">
<MessageCircleMore className="h-4 w-4" />
<p className="text-sm">{commentCount.toLocaleString()}</p>
</div>
</Link>
</div>
<CopyCurrentPageTrigger asChild>
<IconButton size="lg">
<Share2 className="h-4 w-4" />
</IconButton>
</CopyCurrentPageTrigger>
</div>
);
};

export { PostPageBottomBar };
5 changes: 5 additions & 0 deletions lib/get-descriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ const getUserHandlePageTrendingPostSectionTitleDescriptor = (
return `${username}님의 인기글`;
};

const getPostPageAllPostSectionTitleDescriptor = (username: string) => {
return `${username}님의 전체글`;
};

const getNotificationTypeDescriptor = (type: NotificationType) => {
switch (type) {
case 'like':
Expand All @@ -53,6 +57,7 @@ export {
getFollowerDescriptor,
getFollowingDescriptor,
getNotificationTypeDescriptor,
getPostPageAllPostSectionTitleDescriptor,
getSignUpMethodDescriptor,
getUserHandlePageTrendingPostSectionTitleDescriptor,
};

0 comments on commit dfd7964

Please sign in to comment.