Skip to content

Commit

Permalink
override actions
Browse files Browse the repository at this point in the history
  • Loading branch information
maxdyy committed Aug 14, 2023
1 parent 7add25d commit ce47aca
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 53 deletions.
File renamed without changes.
File renamed without changes.
31 changes: 31 additions & 0 deletions src/app/api/review-status/pending/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { NextResponse, type NextRequest } from 'next/server';
import { authorize } from '~/lib/authorize';
import * as db from '~/lib/db';
import { setPendingReview } from '~/server/bigcommerce-api';

export async function POST(req: NextRequest) {
const authorized = authorize();

if (!authorized) {
return new NextResponse('Unauthorized', { status: 401 });
}

const accessToken = await db.getStoreToken(authorized.storeHash);

if (!accessToken) {
return new NextResponse(
'Access token not found. Try to re-install the app.',
{ status: 401 }
);
}

const reqBody = (await req.json()) as { productId: number; reviewId: number };

const review = await setPendingReview({
...reqBody,
accessToken,
storeHash: authorized.storeHash,
});

return NextResponse.json(review);
}
2 changes: 1 addition & 1 deletion src/components/AIChatBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface AIChatBubbleProps {

export const AIChatBubble = ({ message, hideAvatar }: AIChatBubbleProps) => {
return (
<div className="flex max-w-[450px] items-center">
<div className="flex max-w-[480px] items-center">
{!hideAvatar && (
<div className="mr-4 flex flex-none flex-col items-center space-y-1">
<Image src="/images/vertex-ai.png" alt="" width={32} height={32} />
Expand Down
74 changes: 74 additions & 0 deletions src/components/OverrideActionsPopover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useState } from 'react';

import { type Review } from 'types';

import { Button, Popover } from '@bigcommerce/big-design';
import { MoreHorizIcon } from '@bigcommerce/big-design-icons';

import { GenerateEmailButton } from '~/components/GenerateEmailButton';
import {
ApproveReviewButton,
DisapproveReviewButton,
PendingReviewButton,
} from '~/components/ReviewActionButtons';

interface OverrideActionsPopoverProps {
review: Review;
productId: number;
setReview: (review: Review) => void;
}

export const OverrideActionsPopover = ({
review,
productId,
setReview,
}: OverrideActionsPopoverProps) => {
const [isOpen, setIsOpen] = useState(false);
const [buttonRef, setButtonRef] = useState<HTMLButtonElement | null>(null);

return (
<div className="mb-2 flex">
<Button
variant="subtle"
onClick={() => setIsOpen(true)}
ref={setButtonRef}
>
<MoreHorizIcon />
</Button>
<Popover
anchorElement={buttonRef}
isOpen={isOpen}
label="Example Popover"
onClose={() => setIsOpen(false)}
>
<div className="mb-2">
<GenerateEmailButton variant="thank-you" review={review} />
</div>
<div className="mb-2">
<GenerateEmailButton variant="follow-up" review={review} />
</div>
<div className="mb-2">
<ApproveReviewButton
reviewId={review.id}
productId={productId}
setReview={setReview}
/>
</div>
<div className="mb-2">
<PendingReviewButton
reviewId={review.id}
productId={productId}
setReview={setReview}
/>
</div>
<div className="mb-2">
<DisapproveReviewButton
reviewId={review.id}
productId={productId}
setReview={setReview}
/>
</div>
</Popover>
</div>
);
};
11 changes: 9 additions & 2 deletions src/components/ProductList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { type SimpleProduct } from 'types';
import { Badge, Table } from '@bigcommerce/big-design';
import { ArrowLongRightIcon } from '@heroicons/react/20/solid';
import Image from 'next/image';
import { useState } from 'react';
import { useMemo, useState } from 'react';

import { Breadcrumbs } from '~/components/Breadcrumbs';
import { NextLink } from '~/components/NextLink';
Expand Down Expand Up @@ -40,6 +40,13 @@ const ReviewsAverage = ({ product }: ReviewsAverageProps) => {

const ProductList = ({ products }: ProductListProps) => {
const [filteredProducts, setFilteredProducts] = useState(products);
const sortedProductsByReviews = useMemo(
() =>
[...filteredProducts].sort((a, b) => {
return b.reviews_count - a.reviews_count;
}),
[filteredProducts]
);

return (
<div>
Expand Down Expand Up @@ -122,7 +129,7 @@ const ProductList = ({ products }: ProductListProps) => {
),
},
]}
items={filteredProducts}
items={sortedProductsByReviews}
/>
</div>
</div>
Expand Down
116 changes: 116 additions & 0 deletions src/components/ReviewActionButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { useState } from 'react';

import { type Review } from 'types';

import { Button } from '@bigcommerce/big-design';
import {
CheckIcon,
CloseIcon,
NotificationsIcon,
} from '@bigcommerce/big-design-icons';

interface ReviewActionButtonProps {
reviewId: number;
productId: number;
setReview: (review: Review) => void;
}

export const ApproveReviewButton = ({
reviewId,
productId,
setReview,
}: ReviewActionButtonProps) => {
const [isApproving, setIsApproving] = useState(false);

const onApprove = () => {
setIsApproving(true);
fetch('/api/review-status/approve', {
method: 'POST',
body: JSON.stringify({
productId,
reviewId,
}),
})
.then((res) => res.json() as Promise<Review>)
.then(setReview)
.catch((err) => console.log(err))
.finally(() => setIsApproving(false));
};

return (
<Button
iconLeft={<CheckIcon className="h-6 w-6" />}
isLoading={isApproving}
onClick={onApprove}
>
Approve
</Button>
);
};

export const DisapproveReviewButton = ({
productId,
reviewId,
setReview,
}: ReviewActionButtonProps) => {
const [isDisapproving, setIsDisapproving] = useState(false);

const onDisapprove = () => {
setIsDisapproving(true);
fetch('/api/review-status/disapprove', {
method: 'POST',
body: JSON.stringify({
productId,
reviewId,
}),
})
.then((res) => res.json() as Promise<Review>)
.then(setReview)
.catch((err) => console.log(err))
.finally(() => setIsDisapproving(false));
};

return (
<Button
iconLeft={<CloseIcon className="h-6 w-6" />}
isLoading={isDisapproving}
onClick={onDisapprove}
actionType="destructive"
>
Disapprove
</Button>
);
};

export const PendingReviewButton = ({
reviewId,
productId,
setReview,
}: ReviewActionButtonProps) => {
const [isSettingPending, setIsSettingPending] = useState(false);

const onPending = () => {
setIsSettingPending(true);
fetch('/api/review-status/pending', {
method: 'POST',
body: JSON.stringify({
productId,
reviewId,
}),
})
.then((res) => res.json() as Promise<Review>)
.then(setReview)
.catch((err) => console.log(err))
.finally(() => setIsSettingPending(false));
};

return (
<Button
iconLeft={<NotificationsIcon className="h-6 w-6" />}
isLoading={isSettingPending}
onClick={onPending}
>
Set Pending
</Button>
);
};
78 changes: 28 additions & 50 deletions src/components/ReviewDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ import { Card } from '~/components/Card';
import { ComplimentBadges } from '~/components/ComplimentBadges';
import { GenerateEmailButton } from '~/components/GenerateEmailButton';
import { IssueBadges } from '~/components/IssueBadges';
import { OverrideActionsPopover } from '~/components/OverrideActionsPopover';
import {
ApproveReviewButton,
DisapproveReviewButton,
} from '~/components/ReviewActionButtons';
import { ReviewStatusBadge } from '~/components/ReviewStatusBadge';
import { ScoreCircle } from '~/components/ScoreCircle';
import { StarRating } from '~/components/StarRating';
Expand All @@ -39,38 +44,6 @@ export const ReviewDetail = ({
sentimentAnalysis,
}: ReviewDetailProps) => {
const [review, setReview] = useState(reviewProp);
const [isApproving, setIsApproving] = useState(false);
const [isDisapproving, setIsDisapproving] = useState(false);

const onApprove = () => {
setIsApproving(true);
fetch('/api/approve-review', {
method: 'POST',
body: JSON.stringify({
productId: product.id,
reviewId: review.id,
}),
})
.then((res) => res.json() as Promise<Review>)
.then(setReview)
.catch((err) => console.log(err))
.finally(() => setIsApproving(false));
};

const onDisapprove = () => {
setIsDisapproving(true);
fetch('/api/disapprove-review', {
method: 'POST',
body: JSON.stringify({
productId: product.id,
reviewId: review.id,
}),
})
.then((res) => res.json() as Promise<Review>)
.then(setReview)
.catch((err) => console.log(err))
.finally(() => setIsDisapproving(false));
};

const totalCustomerSpendings = customerOrders.reduce(
(acc, order) =>
Expand Down Expand Up @@ -168,9 +141,18 @@ export const ReviewDetail = ({
padding="small"
borderRadius="normal"
>
<h2 className="text-2xl font-semibold text-gray-600">
Feedback and Suggestions
</h2>
<div className="flex">
<h2 className="text-2xl font-semibold text-gray-600">
Feedback and Suggestions
</h2>
<div className="ml-auto">
<OverrideActionsPopover
review={review}
productId={product.id}
setReview={setReview}
/>
</div>
</div>

<div className="flex flex-1 flex-col items-center justify-center pb-3 pt-4">
<div className="space-y-3">
Expand Down Expand Up @@ -202,26 +184,22 @@ export const ReviewDetail = ({
/>
</div>

<div className="pl-16">
<div className="pl-16 pt-4">
{review.status !== 'approved' &&
(parsedScore.isNeutral || parsedScore.isPositive) && (
<Button
iconLeft={<CheckIcon className="h-6 w-6" />}
isLoading={isApproving}
onClick={onApprove}
>
Approve
</Button>
<ApproveReviewButton
productId={product.id}
reviewId={review.id}
setReview={setReview}
/>
)}

{review.status !== 'disapproved' && parsedScore.isNegative && (
<Button
iconLeft={<CloseIcon className="h-6 w-6" />}
isLoading={isDisapproving}
onClick={onDisapprove}
>
Disapprove
</Button>
<DisapproveReviewButton
productId={product.id}
reviewId={review.id}
setReview={setReview}
/>
)}

{parsedScore.isPositive && (
Expand Down
Loading

0 comments on commit ce47aca

Please sign in to comment.