-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(routes/show): add consumer review sections
This patch adds the "Rate and review" section to the show page where users can submit their own consumer reviews. This patch also adds the "Consumer reviews" section which contains a very basic list of the consumer reviews currently in database. This patch includes a couple other small drive-by fixes and linking improvements (e.g. we now associate an ID with each section on the show page, making it easier to provide `#` based links to specific parts of the increasingly long page). Closes: NC-625
- Loading branch information
1 parent
6f7cc79
commit a2818b2
Showing
8 changed files
with
205 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { useLoaderData } from '@remix-run/react' | ||
|
||
import { Empty } from 'components/empty' | ||
|
||
import { type loader } from './route' | ||
import { Section } from './section' | ||
|
||
export function ConsumerReviews() { | ||
const show = useLoaderData<typeof loader>() | ||
const reviews = show.reviews.filter((r) => r.publication == null) | ||
return ( | ||
<Section header={`Consumer reviews for ${show.name}`} id='consumer-reviews'> | ||
{reviews.length === 0 && ( | ||
<Empty className='mt-2'> | ||
No consumer reviews to show yet. Try submitting one above. | ||
</Empty> | ||
)} | ||
{reviews.length > 0 && ( | ||
<ol className='mt-2 grid gap-4 grid-cols-2'> | ||
{reviews.map((review) => ( | ||
<li key={review.id}> | ||
<Review | ||
score={review.score} | ||
author={review.author} | ||
content={review.content} | ||
/> | ||
</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> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import { useForm } from '@conform-to/react' | ||
import { parse } from '@conform-to/zod' | ||
import { | ||
Form as RemixForm, | ||
useActionData, | ||
useNavigation, | ||
} from '@remix-run/react' | ||
import { type ActionArgs, json, redirect } from '@vercel/remix' | ||
import { z } from 'zod' | ||
|
||
import { | ||
Form, | ||
FormField, | ||
FormLabel, | ||
FormLabelWrapper, | ||
FormControl, | ||
FormSubmit, | ||
FormMessage, | ||
} from 'components/form' | ||
import { Button } from 'components/ui/button' | ||
import { Input } from 'components/ui/input' | ||
import { Textarea } from 'components/ui/textarea' | ||
|
||
import { prisma } from 'db.server' | ||
import { log } from 'log.server' | ||
import { getUserId } from 'session.server' | ||
|
||
import { Section } from './section' | ||
|
||
const schema = z.object({ | ||
score: z.preprocess( | ||
(score) => Number(score), | ||
z | ||
.number() | ||
.lte(1, 'Score cannot be larger than 1.0') | ||
.gte(0, 'Score cannot be negative'), | ||
), | ||
content: z.string().trim().min(1, 'Required').min(10, 'Too short'), | ||
}) | ||
|
||
export async function action({ request, params }: ActionArgs) { | ||
const showId = Number(params.showId) | ||
if (Number.isNaN(showId)) throw new Response(null, { status: 404 }) | ||
const formData = await request.formData() | ||
const submission = parse(formData, { schema }) | ||
if (!submission.value || submission.intent !== 'submit') | ||
return json(submission, { status: 400 }) | ||
const userId = await getUserId(request) | ||
if (userId == null) | ||
return redirect(`/login?redirectTo=/shows/${showId}#rate-and-review`) | ||
log.info('creating review... %o', submission.value) | ||
const review = await prisma.review.create({ | ||
data: { | ||
score: submission.value.score, | ||
content: submission.value.content, | ||
author: { connect: { id: userId } }, | ||
show: { connect: { id: showId } }, | ||
}, | ||
}) | ||
log.info('created review: %o', review) | ||
return redirect(`/shows/${showId}`) | ||
} | ||
|
||
export function RateAndReview() { | ||
const lastSubmission = useActionData<typeof action>() | ||
const [form, { score, content }] = useForm({ | ||
lastSubmission, | ||
onValidate({ formData }) { | ||
return parse(formData, { schema }) | ||
}, | ||
}) | ||
const navigation = useNavigation() | ||
return ( | ||
<Section header='Rate and review' id='rate-and-review'> | ||
<Form | ||
asChild | ||
className='max-w-sm mt-2 shadow-sm border border-gray-200 dark:border-gray-700 rounded-md p-4' | ||
> | ||
<RemixForm method='post' {...form.props}> | ||
<FormField name={score.name}> | ||
<FormLabelWrapper> | ||
<FormLabel>Review score</FormLabel> | ||
{score.error && <FormMessage>{score.error}</FormMessage>} | ||
</FormLabelWrapper> | ||
<FormControl asChild> | ||
<Input type='number' max={1} min={0} /> | ||
</FormControl> | ||
</FormField> | ||
<FormField name={content.name}> | ||
<FormLabelWrapper> | ||
<FormLabel>What did you think of the runway?</FormLabel> | ||
{content.error && <FormMessage>{content.error}</FormMessage>} | ||
</FormLabelWrapper> | ||
<FormControl asChild> | ||
<Textarea /> | ||
</FormControl> | ||
</FormField> | ||
<FormSubmit asChild> | ||
<Button disabled={navigation.state !== 'idle'}>Submit</Button> | ||
</FormSubmit> | ||
</RemixForm> | ||
</Form> | ||
</Section> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters