From 0b9f68549cc941b694603aa7a77c1ac734a2a22f Mon Sep 17 00:00:00 2001 From: Christophe <christophe.misc+git@protonmail.ch> Date: Sun, 17 Sep 2023 13:05:22 +0200 Subject: [PATCH] feat(template): allow rating template --- frontend/components/Rating.tsx | 47 ++++++++++++++++++++++ frontend/components/templates/Template.tsx | 37 +---------------- frontend/pages/templates/[id].tsx | 17 +++++++- 3 files changed, 64 insertions(+), 37 deletions(-) create mode 100644 frontend/components/Rating.tsx diff --git a/frontend/components/Rating.tsx b/frontend/components/Rating.tsx new file mode 100644 index 0000000..66133a9 --- /dev/null +++ b/frontend/components/Rating.tsx @@ -0,0 +1,47 @@ +import { FC, useState } from 'react'; +import clsx from 'clsx'; +import styles from 'components/templates/Template.module.scss'; +import { StarHalf } from 'lucide-react'; + +type RatingProps = { score: number; className?: string; onChange?: (score: number) => void }; +export const Rating: FC<RatingProps> = ({ score, className, onChange }) => { + const doubleScore = score * 2; + + const [hoverScore, setHoverScore] = useState(doubleScore); + const [hovering, setHovering] = useState(false); + + return ( + <span + className={clsx(className)} + onMouseEnter={onChange ? () => setHovering(true) : undefined} + onMouseLeave={onChange ? () => setHovering(false) : undefined} + > + {[...Array(10)].map((_, i) => { + const classes = clsx( + 'inline', + styles['star'], + i % 2 == 1 ? styles['flipped'] : false + ); + return ( + <span + key={i} + className={clsx( + 'text-yellow-500', + 'text-xl', + 'inline', + 'align-text-bottom' + )} + onMouseEnter={() => setHoverScore(i + (i % 2))} + onClick={() => onChange && onChange((i + (i % 2)) / 2)} + > + {i < (hovering ? hoverScore : doubleScore) ? ( + <StarHalf fill="currentColor" className={classes} /> + ) : ( + <StarHalf className={classes} /> + )} + </span> + ); + })} + </span> + ); +}; diff --git a/frontend/components/templates/Template.tsx b/frontend/components/templates/Template.tsx index ecf8717..751946e 100644 --- a/frontend/components/templates/Template.tsx +++ b/frontend/components/templates/Template.tsx @@ -4,42 +4,7 @@ import clsx from 'clsx'; import { Template as TemplateDto } from 'lib/client/models/template'; import Link from 'next/link'; import Badge from 'components/Badge'; -import { StarHalf } from 'lucide-react'; - -type RatingProps = { score: number; className?: string }; -const Rating: FC<RatingProps> = ({ score, className }) => { - const doubleScore = score * 2; - - return ( - <span className={clsx(className)}> - {[...Array(10)].map((_, i) => ( - <span - key={i} - className={clsx('text-yellow-500', 'text-xl', 'inline', 'align-text-bottom')} - > - {i < doubleScore ? ( - <StarHalf - fill="currentColor" - className={clsx( - 'inline', - styles['star'], - i % 2 == 1 ? styles['flipped'] : false - )} - /> - ) : ( - <StarHalf - className={clsx( - 'inline', - styles['star'], - i % 2 == 1 ? styles['flipped'] : false - )} - /> - )} - </span> - ))} - </span> - ); -}; +import { Rating } from 'components/Rating'; type TemplateProps = { template: TemplateDto; diff --git a/frontend/pages/templates/[id].tsx b/frontend/pages/templates/[id].tsx index 4e83305..35e13dc 100644 --- a/frontend/pages/templates/[id].tsx +++ b/frontend/pages/templates/[id].tsx @@ -1,7 +1,7 @@ import { NextPage } from 'next'; import Layout from 'components/Layout'; import { useTemplateApi } from 'lib/useApi'; -import { useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery } from '@tanstack/react-query'; import { useRouter } from 'next/router'; import LoadingSpinner from 'components/LoadingSpinner'; import { useEffect } from 'react'; @@ -11,6 +11,7 @@ import { firstMatching } from 'lib/firstMatching'; import Badge from 'components/Badge'; import { CutterField } from 'lib/client'; import TemplateForm from 'pages/components/TemplateForm'; +import { Rating } from 'components/Rating'; export const hasDefaultValue = (field: CutterField) => { // TODO: type assertion because of api spec/generator issue @@ -29,8 +30,17 @@ const Template: NextPage = () => { const api = useTemplateApi(); const template = useQuery(['template', templateId], () => api.getTemplate(templateId ?? ''), { enabled: templateId !== undefined, + keepPreviousData: true, }); + const rateTemplate = useMutation( + ['rate', templateId], + (score: number) => api.rateTemplate(templateId ?? '', score), + { + onSuccess: () => template.refetch(), + } + ); + useEffect(() => { if (router.isReady && templateId === undefined) { router.push('/templates'); @@ -61,6 +71,11 @@ const Template: NextPage = () => { <div className="flex flex-row w-100"> <div className="flex-grow"> <h1 className="inline">{template.data.data.title}</h1> + <Rating + score={template.data.data.score ?? 0} + onChange={(score) => rateTemplate.mutate(score)} + className="ml-2" + /> <div className="ml-2 inline-flex gap-1 align-text-top"> {Array.from(template.data.data.tags).map((tag) => ( <Badge type="info" key={tag}> -- GitLab