diff --git a/app/routes/shows.$showId/consumer-reviews.tsx b/app/routes/shows.$showId/consumer-reviews.tsx new file mode 100644 index 00000000..d77b4809 --- /dev/null +++ b/app/routes/shows.$showId/consumer-reviews.tsx @@ -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() + const reviews = show.reviews.filter((r) => r.publication == null) + return ( +
+ {reviews.length === 0 && ( + + No consumer reviews to show yet. Try submitting one above. + + )} + {reviews.length > 0 && ( +
    + {reviews.map((review) => ( +
  1. + +
  2. + ))} +
+ )} +
+ ) +} + +type ReviewProps = { + author: { name: string; url: string | null } + score: string + content: string +} + +function Review({ author, score, content }: ReviewProps) { + return ( +
+
+

{Math.floor(Number(score) * 100)}% Fresh

+

{content}

+
+
+ + + {author.url != null && ( + + {author.name} + + )} + {author.url == null && {author.name}} + +
+
+ ) +} diff --git a/app/routes/shows.$showId/reviews.tsx b/app/routes/shows.$showId/critic-reviews.tsx similarity index 73% rename from app/routes/shows.$showId/reviews.tsx rename to app/routes/shows.$showId/critic-reviews.tsx index c6ec2967..65f37cc4 100644 --- a/app/routes/shows.$showId/reviews.tsx +++ b/app/routes/shows.$showId/critic-reviews.tsx @@ -1,17 +1,26 @@ import { useLoaderData } from '@remix-run/react' import { ExternalLink } from 'lucide-react' +import { Empty } from 'components/empty' + import { type loader } from './route' import { Section } from './section' -export function Reviews() { +export function CriticReviews() { const show = useLoaderData() + const reviews = show.reviews.filter( + (review) => review.publication != null && review.url != null, + ) return ( -
-
    - {show.reviews - .filter((r) => r.publication != null && r.url != null) - .map((review) => ( +
    + {reviews.length === 0 && ( + + No critic reviews to show yet. Try checking back later. + + )} + {reviews.length > 0 && ( +
      + {reviews.map((review) => (
    1. ))} -
    +
+ )}
) } @@ -35,7 +45,7 @@ type ReviewProps = { function Review({ author, publication, url, content }: ReviewProps) { return ( -
+
By diff --git a/app/routes/shows.$showId/rate-and-review.tsx b/app/routes/shows.$showId/rate-and-review.tsx new file mode 100644 index 00000000..df5e40c8 --- /dev/null +++ b/app/routes/shows.$showId/rate-and-review.tsx @@ -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() + const [form, { score, content }] = useForm({ + lastSubmission, + onValidate({ formData }) { + return parse(formData, { schema }) + }, + }) + const navigation = useNavigation() + return ( +
+
+ + + + Review score + {score.error && {score.error}} + + + + + + + + What did you think of the runway? + {content.error && {content.error}} + + +