Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dev -> main #24

Merged
merged 2 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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