Skip to content

Commit

Permalink
feat: form reviews
Browse files Browse the repository at this point in the history
  • Loading branch information
georgetonietti committed Dec 13, 2024
1 parent 816f1be commit f72035c
Show file tree
Hide file tree
Showing 13 changed files with 316 additions and 76 deletions.
33 changes: 14 additions & 19 deletions components/product/Gallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,20 +74,6 @@ export default function GallerySlider(props: Props) {
))}
</Slider>

<Slider.PrevButton
class="no-animation absolute left-2 top-1/2 btn btn-circle btn-outline disabled:invisible"
disabled
>
<Icon id="chevron-right" class="rotate-180" />
</Slider.PrevButton>

<Slider.NextButton
class="no-animation absolute right-2 top-1/2 btn btn-circle btn-outline disabled:invisible"
disabled={images.length < 2}
>
<Icon id="chevron-right" />
</Slider.NextButton>

<div class="absolute top-2 right-2 bg-base-100 rounded-full">
<label class="btn btn-ghost hidden sm:inline-flex" for={zoomId}>
<Icon id="pan_zoom" />
Expand All @@ -96,16 +82,22 @@ export default function GallerySlider(props: Props) {
</div>
</div>


{/* Dots */}
<div class={clx(
"col-start-1 col-span-1 mobile:flex mobile:justify-center",
"col-start-1 col-span-1 mobile:flex mobile:justify-center flex flex-col desktop:gap-10 desktop:items-center",
)}>

<Slider.PrevButton class="mobile:hidden no-animation btn btn-circle hover:bg-transparent hover:border-[#8F2AED] border border-[#8F2AED]">
<Icon id="arrow-top" stroke="#8F2AED" width={20} height={12} />
</Slider.PrevButton>

<ul
class={clx(
"carousel carousel-center",
"carousel-vertical mobile:carousel-horizontal",
"gap-2",
"max-w-full",
"desktop:carousel-vertical",
"gap-2 desktop:gap-10",
"max-w-full items-center mobile:justify-center",
"overflow-x-auto",
"overflow-y-auto",
)}
Expand All @@ -119,7 +111,7 @@ export default function GallerySlider(props: Props) {
<Slider.Dot index={index}>
<Image
style={{ aspectRatio: "1 / 1" }}
class="group-disabled:border-base-400 border rounded object-cover w-full h-full"
class="group-disabled:border-primary border-2 border-[#F5F5F5] rounded object-cover w-full h-full"
width={64}
height={64}
src={img.url!}
Expand All @@ -140,6 +132,9 @@ export default function GallerySlider(props: Props) {
</li>
))}
</ul>
<Slider.NextButton class="mobile:hidden no-animation btn btn-circle hover:bg-transparent hover:border-[#8F2AED] border border-[#8F2AED]">
<Icon id="arrow-bottom" stroke="#8F2AED" width={20} height={12} />
</Slider.NextButton>
</div>

<Slider.JS rootId={id} />
Expand Down
2 changes: 1 addition & 1 deletion components/product/ProductInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ function ProductInfo({ page, itemsShare }: Props) {
<span class={clx("text-xs text-[#252429]")}>
REF: {productID}
</span>
<ReviewRating reviewCount={aggregateRating?.reviewCount} ratingValue={aggregateRating?.ratingValue} />
<ReviewRating reviewCount={aggregateRating?.reviewCount ?? 0} ratingValue={aggregateRating?.ratingValue ?? 0} />
</div>


Expand Down
73 changes: 54 additions & 19 deletions components/product/ProductReview/ReviewForm.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@

import { useDevice } from "@deco/deco/hooks";
import { AppContext } from "../../../apps/deco/wake.ts";
import { clx } from "../../../sdk/clx.ts";
import { useComponent } from "../../../sections/Component.tsx";
import ReviewRating from "../ReviewRating.tsx";

interface ReviewFormProps {
productVariantId: number;
state?: string;
message?: string;
ratingValue?: number;
reviewCount?: number;
}

export const action = async (
Expand All @@ -19,6 +24,7 @@ export const action = async (

try {
const formData = await req.formData();
// deno-lint-ignore no-explicit-any
await (ctx as any).invoke.wake.actions.review.create({
email,
name: givenName,
Expand All @@ -32,29 +38,58 @@ export const action = async (
}
};

export default function ReviewForm({ productVariantId, state, message }: ReviewFormProps) {
console.log(state, message)
export default function ReviewForm({ productVariantId, state, message, reviewCount, ratingValue }: ReviewFormProps) {
const device = useDevice()

if (state && message) {
return (
<h2 class="text-xl leading-6 text-base-content font-bold">
{message}
</h2>
<h2 class="text-xl leading-6 text-base-content font-bold">
{message}
</h2>
);
}
}
return (
<form
hx-post={useComponent(import.meta.url, { productVariantId, state, message })}
>
<input type="text" id="review" name="review" required class="border border-black rounded-[5px]" />
<div className="rating">
<input type="radio" name="stars" value="1" className="mask mask-star-2 bg-orange-400" />
<input type="radio" name="stars" value="2" className="mask mask-star-2 bg-orange-400" defaultChecked />
<input type="radio" name="stars" value="3" className="mask mask-star-2 bg-orange-400" />
<input type="radio" name="stars" value="4" className="mask mask-star-2 bg-orange-400" />
<input type="radio" name="stars" value="5" className="mask mask-star-2 bg-orange-400" />
</div>
<button>Enviar</button>
</form>
<div class="grid grid-cols-1 gap-[26px] desktop:w-[29.24%] w-full">
{device === "desktop" && (
<>
<div class="flex flex-col gap-4">
<h3 class="font-bold text-black text-2xl">Avaliações de Clientes</h3>
<ReviewRating reviewCount={reviewCount ?? 0} ratingValue={ratingValue ?? 0} typeTwo={true} size={32} />
</div>
<hr />
</>
)}
<form
hx-post={useComponent(import.meta.url, { productVariantId, state, message })}
class="flex flex-col gap-[26px]"
>
<div class="flex flex-col gap-4">
<span class="font-bold text-black text-xl">Adicionar avaliação</span>
<div class="flex flex-col gap-2">
<div className="rating">
<input type="radio" name="stars" value="1" className="mask mask-star-2 bg-primary h-4 w-4 mobile:h-6 mobile:w-6" />
<input type="radio" name="stars" value="2" className="mask mask-star-2 bg-primary h-4 w-4 mobile:h-6 mobile:w-6" />
<input type="radio" name="stars" value="3" className="mask mask-star-2 bg-primary h-4 w-4 mobile:h-6 mobile:w-6" defaultChecked />
<input type="radio" name="stars" value="4" className="mask mask-star-2 bg-primary h-4 w-4 mobile:h-6 mobile:w-6" />
<input type="radio" name="stars" value="5" className="mask mask-star-2 bg-primary h-4 w-4 mobile:h-6 mobile:w-6" />
</div>
<span class="text-xs text-[#373737]">Avalie o produto de 1 a 5 estrelas</span>
</div>
</div>
<textarea
type="text"
id="review"
name="review"
required
class={clx(
"border border-black rounded-[5px] resize-none h-[89px] w-full",
"!rounded-sm !border !border-[#DCDCDC] outline-none p-[14px]"
)}
placeholder="Comente aqui sua experiência"
maxlength={300}
/>
<button class="btn btn-primary text-black text-sm font-bold rounded-[5px]">Enviar Avaliação</button>
</form>
</div>
);
}
44 changes: 44 additions & 0 deletions components/product/ProductReview/Reviews.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

import { AggregateRating, Review } from "apps/commerce/types.ts";
import ReviewRating from "../ReviewRating.tsx";
import convertDate from "./convertDate.ts";
import { useDevice } from "@deco/deco/hooks";
import { clx } from "../../../sdk/clx.ts";

interface ReviewsProps {
reviews?: Review[]
aggregateRating?: AggregateRating
}

export default function Reviews({ reviews, aggregateRating }: ReviewsProps) {
const device = useDevice()
return (
<div class={clx(
"desktop:max-h-[71.2%] w-full desktop:overflow-y-auto flex-1 snap-y custom-scrollbar desktop:pr-4",
"mobile:flex mobile:flex-col mobile:gap-[26px]"
)}>
{device === "mobile" && (
<>
<div class="flex flex-col gap-4">
<h3 class="font-bold text-black text-2xl">Avaliações de Clientes</h3>
<ReviewRating reviewCount={aggregateRating?.reviewCount ?? 0} ratingValue={aggregateRating?.ratingValue ?? 0} typeTwo={true} size={20} />
</div>
<hr />
</>
)}
<ul class="space-y-6 w-full mobile:max-h-[382px] overflow-y-auto snap-y custom-scrollbar mobile:pr-2">
{reviews?.map(review => (
<li class="flex flex-col gap-4 mobile:gap-6 w-full snap-start">
<div class="flex flex-col gap-4">
<ReviewRating reviewCount={0} ratingValue={review.reviewRating?.ratingValue ?? 0} size={16.67} />
<span class="font-bold text-black">{review.author![0].name}</span>
<p class="text-sm text-black">{review.reviewBody}</p>
<span class="text-xs text-[#999999]">{review.datePublished && convertDate(review.datePublished)}</span>
</div>
<hr />
</li>
))}
</ul>
</div>
);
}
12 changes: 12 additions & 0 deletions components/product/ProductReview/convertDate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default function convertDate(isoDate: string) {
const date = new Date(isoDate);

const options: Intl.DateTimeFormatOptions = {
day: "numeric",
month: "long",
year: "numeric"
};

const formattedDate = new Intl.DateTimeFormat("pt-BR", options).format(date);
return formattedDate
}
27 changes: 20 additions & 7 deletions components/product/ReviewRating.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
import { clx } from "../../sdk/clx.ts";
import Icon from "../ui/Icon.tsx";

interface ReviewRatingProps {
ratingValue?: number;
reviewCount?: number;
ratingValue: number;
reviewCount: number;
typeTwo?: boolean;
size?: number;
}

export default function ReviewRating({ ratingValue = 0, reviewCount }: ReviewRatingProps) {
export default function ReviewRating({ ratingValue = 0, reviewCount, typeTwo, size }: ReviewRatingProps) {
return (
<div class="flex gap-1">
<div class={clx(
"flex items-center",
typeTwo ? "gap-[10px]" : "gap-1"
)}>
<div class="flex gap-[3px]">
{Array.from({ length: 5 }).map((_, index) => {
const id = index + 1 <= ratingValue ? "starFull" : "star";
return (<Icon key={index} id={id} width={11} height={12}/>)
return (<Icon key={index} id={id} width={size ?? 11} height={size ?? 12} />)
})}
</div>
{reviewCount && (
<span class="text-[11px] text-black">({reviewCount})</span>
{reviewCount > 0 && (
typeTwo
? (
<span class="text-sm text-black whitespace-nowrap flex gap-[10px]">
<strong class="text-[28px] font-bold text-black">{ratingValue}</strong> (Média de {reviewCount} avaliações)
</span>
)
: (<span class="text-[11px] text-black">({reviewCount})</span>)

)}
</div>

Expand Down
2 changes: 1 addition & 1 deletion components/product/Tabs/TabsDesktop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function TabsDesktop({ productInformations }: TabsDesktopProps) {
const quantityTabs = productInformations.length;

return (
<div role="tablist" class="tabs tabs-bordered !w-full mx-auto !max-w-[82.36%] justify-items-stretch">
<div role="tablist" class="tabs tabs-bordered !w-full mx-auto !max-w-full justify-items-stretch">
{
productInformations?.map(item => (
<>
Expand Down
9 changes: 1 addition & 8 deletions components/product/Tabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { ProductDetailsPage } from "apps/commerce/types.ts";
import { useDevice, useScript } from "@deco/deco/hooks";
import TabsDesktop from "./TabsDesktop.tsx";
import TabsMobile from "./TabsMobile.tsx";
import ReviewForm from "../ProductReview/ReviewForm.tsx";

interface TabsProps {
page: ProductDetailsPage
Expand Down Expand Up @@ -41,7 +40,7 @@ const onLoad = () => {
export default function Tabs({ page }: TabsProps) {
const product = page.product
const requiredContent = ['Descrição', 'Dados Técnicos']
const productInformations: any[] = product.additionalProperty?.filter(
const productInformations = product.additionalProperty?.filter(
item => item.name && item.value && requiredContent.includes(item.name)
).sort((a, b) => {
if (a.name === 'Descrição') return -1;
Expand All @@ -53,12 +52,6 @@ export default function Tabs({ page }: TabsProps) {

if (productInformations?.length === 0) return null

// productInformations.push({
// "@type": "PropertyValue",
// name: "OUTRAS INFORMAÇÕES",
// value: <ReviewForm productVariantId={Number(product.productID)}/>
// })

const device = useDevice();

return (
Expand Down
1 change: 1 addition & 0 deletions components/shipping/Results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ function formatShippingEstimate(estimate: number): string {
export async function action(props: Props, req: Request, ctx: AppContext) {
const form = await req.formData();
try {
// deno-lint-ignore no-explicit-any
const result = await (ctx as any).invoke.wake.actions.shippingSimulation({
...props.items[0],
cep: `${form.get("postalCode") ?? ""}`,
Expand Down
25 changes: 20 additions & 5 deletions sections/Product/ProductDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Tabs from "../../components/product/Tabs/index.tsx";
import { ProductShelfComponent } from "../../sections/Product/ProductShelf.tsx";
import { type Section as SectionType } from "@deco/deco/blocks";
import ReviewForm from "../../components/product/ProductReview/ReviewForm.tsx";
import Reviews from "../../components/product/ProductReview/Reviews.tsx";

export interface Props {
/** @title Integration */
Expand All @@ -18,7 +19,6 @@ export interface Props {
}

export default function ProductDetails(props: Props) {

if (!props.page) {
return (
<div class="w-full flex justify-center items-center py-28">
Expand All @@ -32,13 +32,15 @@ export default function ProductDetails(props: Props) {
);
}

const { productID, aggregateRating, review } = props?.page?.product
console.log(review)
console.log(aggregateRating)

return (
<>
<Section.Container class="mobile:mt-[60px]">
<Breadcrumb itemListElement={props.page.breadcrumbList.itemListElement} />

<div
class={clx(
<div class={clx(
"flex gap-5",
"mobile:flex-col w-full"
)}
Expand All @@ -50,10 +52,23 @@ export default function ProductDetails(props: Props) {
<ProductInfo {...props} />
</div>
</div>
<div class="w-full desktop:max-w-[calc(100%_-_254px)]">
<div class="w-full desktop:max-w-[calc(100%_-_8.33%)] mx-auto">
<Tabs page={props.page} />
</div>
</Section.Container>
<Section.Container class="bg-[#F5F5F5] desktop:!h-[507px] !py-12">
<div class={clx(
"flex gap-20 mobile:gap-[26px] items-center w-full desktop:max-w-[calc(100%_-_8.33%)] h-full mx-auto",
"mobile:flex-col-reverse"
)}>
<ReviewForm
productVariantId={Number(productID)}
reviewCount={aggregateRating?.reviewCount}
ratingValue={aggregateRating?.ratingValue}
/>
<Reviews reviews={review} aggregateRating={aggregateRating}/>
</div>
</Section.Container>
<props.productShelf.Component {...props.productShelf.props} />
</>
);
Expand Down
2 changes: 2 additions & 0 deletions static/sprites.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit f72035c

Please sign in to comment.