Skip to content

Commit

Permalink
Merge pull request #24 from ildecimo/develop
Browse files Browse the repository at this point in the history
dev -> main
  • Loading branch information
maxdyy committed Aug 14, 2023
2 parents 573456d + ce47aca commit 68f6eee
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 108 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>
);
};
50 changes: 38 additions & 12 deletions src/components/ProductFilters.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Box, Switch } from '@bigcommerce/big-design';
import { useState } from 'react';
import { type SimpleProduct } from 'types';

import { ProductSearch } from '~/components/ProductSearch';
import { Box, Form, FormGroup, Input, Switch } from '@bigcommerce/big-design';

interface ProductFiltersProps {
setFilteredProducts: (value: SimpleProduct[]) => void;
Expand All @@ -14,26 +13,53 @@ export const ProductFilters = ({
products,
}: ProductFiltersProps) => {
const [switchOn, setSwitchOn] = useState(false);
const [searchValue, setSearchValue] = useState('');
const [subFilteredProducts, setSubFilteredProducts] = useState(products);

const searchProduct = (search: string, products: SimpleProduct[]) => {
return products.filter((product) => {
return product.name.toLowerCase().includes(search.toLowerCase());
});
};

const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchValue(event.target.value);

const productsToFilter = switchOn ? subFilteredProducts : products;
const filteredProducts = searchProduct(
event.target.value,
productsToFilter
);

setFilteredProducts(filteredProducts);
};

const handleSwitch = () => {
setSwitchOn(!switchOn);
let filteredProducts = products;
const productsToFilter = searchValue ? subFilteredProducts : products;

if (!switchOn) {
filteredProducts = products.filter((product) => {
return product.reviews_count > 0;
});
}
const filteredProducts = switchOn
? productsToFilter
: productsToFilter.filter((product) => product.reviews_count > 0);

setSubFilteredProducts(filteredProducts);
setFilteredProducts(filteredProducts);
};

return (
<Box border="box" padding="small" borderRadius="normal">
<ProductSearch
products={products}
setFilteredProducts={setFilteredProducts}
/>
<Form>
<FormGroup>
<Input
label="Search products by name"
onChange={handleSearch}
placeholder="Search"
type="text"
value={searchValue}
required
/>
</FormGroup>
</Form>

<div className="mt-4 flex">
<span className="mr-6 font-bold">Show only products with reviews</span>
Expand Down
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
43 changes: 0 additions & 43 deletions src/components/ProductSearch.tsx

This file was deleted.

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>
);
};
Loading

0 comments on commit 68f6eee

Please sign in to comment.