mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 20:13:49 +00:00
Feat: Implement infinite scrolling brewery comment section
Refactor beer comment schemas to work on brewery comments as well. Add robots.txt to block crawling for now.
This commit is contained in:
9
src/components/BreweryById/BreweryBeerSection.tsx.tsx
Normal file
9
src/components/BreweryById/BreweryBeerSection.tsx.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { FC } from 'react';
|
||||
|
||||
interface BreweryCommentsSectionProps {}
|
||||
|
||||
const BreweryBeersSection: FC<BreweryCommentsSectionProps> = () => {
|
||||
return <div className="card h-full"></div>;
|
||||
};
|
||||
|
||||
export default BreweryBeersSection;
|
||||
65
src/components/BreweryById/BreweryCommentsSection.tsx
Normal file
65
src/components/BreweryById/BreweryCommentsSection.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import UserContext from '@/contexts/userContext';
|
||||
import BreweryPostQueryResult from '@/services/BreweryPost/types/BreweryPostQueryResult';
|
||||
import { FC, MutableRefObject, useContext, useRef } from 'react';
|
||||
import { z } from 'zod';
|
||||
import useBreweryPostComments from '@/hooks/useBreweryPostComments';
|
||||
import LoadingComponent from '../BeerById/LoadingComponent';
|
||||
import CommentsComponent from '../ui/CommentsComponent';
|
||||
|
||||
interface BreweryBeerSectionProps {
|
||||
breweryPost: z.infer<typeof BreweryPostQueryResult>;
|
||||
}
|
||||
|
||||
const BreweryCommentForm: FC = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const BreweryCommentsSection: FC<BreweryBeerSectionProps> = ({ breweryPost }) => {
|
||||
const { user } = useContext(UserContext);
|
||||
|
||||
const { id } = breweryPost;
|
||||
|
||||
const PAGE_SIZE = 4;
|
||||
|
||||
const { comments, isLoading, setSize, size, isLoadingMore, isAtEnd } =
|
||||
useBreweryPostComments({ id, pageSize: PAGE_SIZE });
|
||||
|
||||
const commentSectionRef: MutableRefObject<HTMLDivElement | null> = useRef(null);
|
||||
|
||||
return (
|
||||
<div className="w-full space-y-3" ref={commentSectionRef}>
|
||||
<div className="card">
|
||||
{user ? (
|
||||
<BreweryCommentForm />
|
||||
) : (
|
||||
<div className="flex h-52 flex-col items-center justify-center">
|
||||
<div className="text-lg font-bold">Log in to leave a comment.</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{
|
||||
/**
|
||||
* If the comments are loading, show a loading component. Otherwise, show the
|
||||
* comments.
|
||||
*/
|
||||
isLoading ? (
|
||||
<div className="card pb-6">
|
||||
<LoadingComponent length={PAGE_SIZE} />
|
||||
</div>
|
||||
) : (
|
||||
<CommentsComponent
|
||||
comments={comments}
|
||||
isLoadingMore={isLoadingMore}
|
||||
isAtEnd={isAtEnd}
|
||||
pageSize={PAGE_SIZE}
|
||||
setSize={setSize}
|
||||
size={size}
|
||||
commentSectionRef={commentSectionRef}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BreweryCommentsSection;
|
||||
95
src/components/BreweryById/BreweryInfoHeader.tsx
Normal file
95
src/components/BreweryById/BreweryInfoHeader.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import UserContext from '@/contexts/userContext';
|
||||
import useGetBreweryPostLikeCount from '@/hooks/useGetBreweryPostLikeCount';
|
||||
import useTimeDistance from '@/hooks/useTimeDistance';
|
||||
import BreweryPostQueryResult from '@/services/BreweryPost/types/BreweryPostQueryResult';
|
||||
import { format } from 'date-fns';
|
||||
import { FC, useContext } from 'react';
|
||||
import { Link } from 'react-daisyui';
|
||||
import { FaRegEdit } from 'react-icons/fa';
|
||||
import { z } from 'zod';
|
||||
import BreweryPostLikeButton from '../BreweryIndex/BreweryPostLikeButton';
|
||||
|
||||
interface BreweryInfoHeaderProps {
|
||||
breweryPost: z.infer<typeof BreweryPostQueryResult>;
|
||||
}
|
||||
const BreweryInfoHeader: FC<BreweryInfoHeaderProps> = ({ breweryPost }) => {
|
||||
const createdAt = new Date(breweryPost.createdAt);
|
||||
const timeDistance = useTimeDistance(createdAt);
|
||||
|
||||
const { user } = useContext(UserContext);
|
||||
const idMatches = user && breweryPost.postedBy.id === user.id;
|
||||
const isPostOwner = !!(user && idMatches);
|
||||
|
||||
const { likeCount, mutate } = useGetBreweryPostLikeCount(breweryPost.id);
|
||||
|
||||
return (
|
||||
<article className="card flex flex-col justify-center bg-base-300">
|
||||
<div className="card-body">
|
||||
<header className="flex justify-between">
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold lg:text-4xl">{breweryPost.name}</h1>
|
||||
<h2 className="text-lg font-semibold lg:text-2xl">
|
||||
Located in
|
||||
{` ${breweryPost.location.city}, ${
|
||||
breweryPost.location.stateOrProvince || breweryPost.location.country
|
||||
}`}
|
||||
</h2>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="italic">
|
||||
{' posted by '}
|
||||
<Link
|
||||
href={`/users/${breweryPost.postedBy.id}`}
|
||||
className="link-hover link"
|
||||
>
|
||||
{`${breweryPost.postedBy.username} `}
|
||||
</Link>
|
||||
{timeDistance && (
|
||||
<span
|
||||
className="tooltip tooltip-right"
|
||||
data-tip={format(createdAt, 'MM/dd/yyyy')}
|
||||
>{`${timeDistance} ago`}</span>
|
||||
)}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
{isPostOwner && (
|
||||
<div className="tooltip tooltip-left" data-tip={`Edit '${breweryPost.name}'`}>
|
||||
<Link
|
||||
href={`/breweries/${breweryPost.id}/edit`}
|
||||
className="btn-ghost btn-xs btn"
|
||||
>
|
||||
<FaRegEdit className="text-xl" />
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
<div className="space-y-2">
|
||||
<p>{breweryPost.description}</p>
|
||||
<div className="flex items-end justify-between">
|
||||
<div className="space-y-1">
|
||||
<div>
|
||||
{(!!likeCount || likeCount === 0) && (
|
||||
<span>
|
||||
Liked by {likeCount} user{likeCount !== 1 && 's'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-actions">
|
||||
{user && (
|
||||
<BreweryPostLikeButton
|
||||
breweryPostId={breweryPost.id}
|
||||
mutateCount={mutate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
};
|
||||
|
||||
export default BreweryInfoHeader;
|
||||
43
src/components/BreweryById/BreweryMap.tsx
Normal file
43
src/components/BreweryById/BreweryMap.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import useMediaQuery from '@/hooks/useMediaQuery';
|
||||
|
||||
import { FC } from 'react';
|
||||
import Map, { Marker } from 'react-map-gl';
|
||||
|
||||
interface BreweryMapProps {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
}
|
||||
const BreweryMap: FC<BreweryMapProps> = ({ latitude, longitude }) => {
|
||||
const isDesktop = useMediaQuery('(min-width: 1024px)');
|
||||
const theme =
|
||||
typeof window !== 'undefined' ? window.localStorage.getItem('theme') : 'dark';
|
||||
|
||||
const mapStyle =
|
||||
theme === 'dark'
|
||||
? 'mapbox://styles/mapbox/dark-v11'
|
||||
: 'mapbox://styles/mapbox/light-v10';
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
<Map
|
||||
initialViewState={{
|
||||
latitude,
|
||||
longitude,
|
||||
zoom: 17,
|
||||
}}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: isDesktop ? 400 : 200,
|
||||
}}
|
||||
mapStyle={mapStyle}
|
||||
mapboxAccessToken={process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN as string}
|
||||
scrollZoom={true}
|
||||
>
|
||||
<Marker latitude={latitude} longitude={longitude} />
|
||||
</Map>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BreweryMap;
|
||||
Reference in New Issue
Block a user