Skip to content

Commit

Permalink
fix(show): style consumer reviews like insta comments
Browse files Browse the repository at this point in the history
This patch updates the consumer reviews list styling to mimic the style
of Instagram comments. Eventually, I may even allow users to respond to
other users and create a "discussion" tab similar to a Twitter thread or
a post on Hacker News [[1]].

[1]: https://news.ycombinator.com/item?id=36838051
  • Loading branch information
nicholaschiang committed Jul 23, 2023
1 parent 00dda3f commit b6348ba
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 36 deletions.
23 changes: 23 additions & 0 deletions app/components/avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {
Avatar as AvatarRoot,
AvatarImage,
AvatarFallback,
} from 'components/ui/avatar'

type Source = { name: string; avatar?: string | null }

export function Avatar({ src }: { src?: Source | null }) {
return (
<AvatarRoot>
<AvatarImage src={src?.avatar ?? undefined} alt={src?.name} />
<AvatarFallback>
{src?.name
.split(' ')
.slice(0, 2)
.map((s) => s.substring(0, 1))
.join('')
.toUpperCase()}
</AvatarFallback>
</AvatarRoot>
)
}
48 changes: 48 additions & 0 deletions app/components/ui/avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as AvatarPrimitive from '@radix-ui/react-avatar'
import * as React from 'react'

import { cn } from 'utils/cn'

const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
'relative flex h-8 w-8 shrink-0 overflow-hidden rounded-full',
className,
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName

const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn('aspect-square h-full w-full', className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName

const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
'flex h-full w-full text-xs items-center justify-center rounded-full bg-gray-100 dark:bg-gray-800',
className,
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName

export { Avatar, AvatarImage, AvatarFallback }
75 changes: 40 additions & 35 deletions app/routes/shows.$showId/consumer-reviews.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { useLoaderData } from '@remix-run/react'
import TimeAgo from 'timeago-react'
import * as timeago from 'timeago.js'
import en from 'timeago.js/lib/lang/en_short'

import { Avatar } from 'components/avatar'
import { Empty } from 'components/empty'

import { type loader } from './route'
import { Section } from './section'

timeago.register('en_short', en)

export function ConsumerReviews() {
const show = useLoaderData<typeof loader>()
const reviews = show.reviews.filter((r) => r.publication == null)
Expand All @@ -16,46 +22,45 @@ export function ConsumerReviews() {
</Empty>
)}
{reviews.length > 0 && (
<ol className='mt-2 grid gap-4 grid-cols-2'>
{reviews.map((review) => (
<ol className='mt-2 grid gap-4'>
{reviews.slice(0, 5).map((review) => (
<li key={review.id}>
<Review
score={review.score}
author={review.author}
content={review.content}
/>
<figure className='flex gap-2'>
<Avatar src={review.author} />
<div>
<figcaption className='flex items-center gap-1'>
<cite className='text-2xs not-italic font-semibold'>
{review.author.url != null && (
<a
href={review.author.url}
target='_blank'
rel='noopener noreferrer'
>
{review.author.username}
</a>
)}
{review.author.url == null && (
<span>{review.author.username}</span>
)}
</cite>
<TimeAgo
locale='en_short'
className='text-gray-400 dark:text-gray-500 text-3xs'
datetime={review.createdAt}
/>
</figcaption>
<blockquote className='text-sm'>
<strong className='font-medium mr-1'>
{Number(review.score) * 5}/5
</strong>
{review.content}
</blockquote>
</div>
</figure>
</li>
))}
</ol>
)}
</Section>
)
}

type ReviewProps = {
author: { name: string; url: string | null }
score: string
content: string
}

function Review({ author, score, content }: ReviewProps) {
return (
<figure className='border border-gray-200 dark:border-gray-700 rounded-md'>
<blockquote className='px-4 py-2'>
<p className='font-medium'>{Math.floor(Number(score) * 100)}% Fresh</p>
<p>{content}</p>
</blockquote>
<figcaption className='border-t bg-gray-50 dark:bg-gray-800 border-gray-200 dark:border-gray-700 px-4 py-2'>
<cite>
<span className='text-gray-500'></span>
{author.url != null && (
<a href={author.url} target='_blank' rel='noopener noreferrer'>
{author.name}
</a>
)}
{author.url == null && <span>{author.name}</span>}
</cite>
</figcaption>
</figure>
)
}
5 changes: 4 additions & 1 deletion app/routes/shows.$showId/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ export async function loader({ params }: LoaderArgs) {
collections: {
include: { links: { include: { brand: true, retailer: true } } },
},
reviews: { include: { author: true, publication: true } },
reviews: {
include: { author: true, publication: true },
orderBy: { createdAt: 'desc' },
},
looks: { include: { image: true }, orderBy: { number: 'asc' } },
},
}),
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"@conform-to/react": "^0.7.4",
"@conform-to/zod": "^0.7.4",
"@prisma/client": "^4.16.2",
"@radix-ui/react-avatar": "^1.0.3",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-form": "^0.0.3",
Expand Down Expand Up @@ -86,6 +87,8 @@
"sharp": "^0.32.4",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.6",
"timeago-react": "^3.0.6",
"timeago.js": "^4.0.2",
"tiny-invariant": "^1.3.1",
"ts-key-enum": "^2.0.12",
"web-vitals": "^3.4.0",
Expand Down
46 changes: 46 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b6348ba

Please sign in to comment.