Skip to content

Commit

Permalink
Merge pull request #950 from hngprojects/feat/hng-product-api-integra…
Browse files Browse the repository at this point in the history
…tion

refactor: edit product view
  • Loading branch information
kiisi authored Aug 9, 2024
2 parents 0964a47 + 8a38b00 commit 1ccddd0
Show file tree
Hide file tree
Showing 9 changed files with 69 additions and 63 deletions.
2 changes: 1 addition & 1 deletion src/actions/product.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// // /api/v1/organisations/{orgId}/products
"use server ";

import axios from "axios";
import { z } from "zod";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
TableRow,
} from "~/components/ui/table";
import { useOrgContext } from "~/contexts/orgContext";
import { useProductModal } from "~/hooks/admin-product/use-product.modal";
import { cn } from "~/lib/utils";
import { Product } from "~/types";
import { ProductGridCard } from "./product-grid-card";
Expand All @@ -29,32 +28,32 @@ export const ProductContentView = ({
searchTerm,
view = "grid",
}: Properties) => {
const { products } = useOrgContext();
const {
updateOpen,
updateProductId,
product_id,
products,
isActionModal,
setIsActionModal,
updateOpen,
setIsDelete,
} = useProductModal();

setSelectedProduct,
} = useOrgContext();
const router = useRouter();

if (!products) return;

const handleOpenActionModal = (product_id: string) => {
updateProductId(product_id);
setSelectedProduct(product_id);
setIsActionModal(!isActionModal);
};

const handleOpenDetail = (product_id: string) => {
setIsActionModal(false);
updateProductId(product_id);
setSelectedProduct(product_id);
updateOpen(true);
};
const handleDeleteAction = (id: string) => {
setIsActionModal(false);
updateProductId(id);
setSelectedProduct(id);
setIsDelete(true);
};
const handleEditAction = (id: string) => {
Expand Down Expand Up @@ -99,24 +98,17 @@ export const ProductContentView = ({
subset.map((product, index) => (
<ProductListRow
key={product.id}
id={product.id}
selectedId={product_id}
searchTerm={searchTerm}
price={product.price}
description={product.description}
title={product.name}
category={product.category}
isBottomRow={
index === subset.length - 1 || index === subset.length - 2
}
isActionModal={isActionModal}
onCloseActionModal={() => setIsActionModal(false)}
onOpenDetails={() => handleOpenDetail(product.id)}
onEdit={() => handleEditAction(product.id)}
onDelete={() => handleDeleteAction(product.id)}
onOpenActionModal={() => handleOpenActionModal(product.id)}
onCloseActionModal={() => setIsActionModal(false)}
status={"in_stock"}
imgSrc={""}
{...product}
/>
))}
</TableBody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function ProductGridCard({
<div className="space-y-2">
<div className="flex flex-wrap justify-between gap-2 text-[16.917px] font-bold -tracking-[0.338px]">
<h2>
<ProductHighlightTerm searchTerm={searchTerm} title={name} />
<ProductHighlightTerm searchTerm={searchTerm} name={name} />
</h2>
<p>{formatPrice(price)}</p>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { cn } from "~/lib/utils";

type ProductHighlightTermProperties = {
searchTerm: string;
title: string;
name: string;
};

export function ProductHighlightTerm({
title,
name,
searchTerm = "",
}: ProductHighlightTermProperties) {
return (
Expand All @@ -18,7 +18,7 @@ export function ProductHighlightTerm({
searchTerm.length > 2 ? "w-[50px] overflow-x-auto" : "",
)}
dangerouslySetInnerHTML={{
__html: title.replaceAll(
__html: name.replaceAll(
new RegExp(`(${searchTerm})`, "gi"),
(match, group) =>
`<span style="color: black; background-color: ${
Expand All @@ -30,7 +30,7 @@ export function ProductHighlightTerm({
}}
/>
) : (
<span className="">{title}</span>
<span className="">{name}</span>
)}
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,52 +6,45 @@ import BlurImage from "~/components/miscellaneous/blur-image";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import { TableCell, TableRow } from "~/components/ui/table";
import { useOrgContext } from "~/contexts/orgContext";
import { cn, formatPrice } from "~/lib/utils";
import { Product } from "~/types";
import { ProductHighlightTerm } from "./product-highlight-term";

type ProductListRowProperties = {
id: string;
title: string;
description: string;
category: string;
status: "in_stock" | "out_of_stock" | "low_on_stock";
price: number;
imgSrc: string;
interface ProductListRowProperties extends Product {
searchTerm: string;
isBottomRow: boolean;
selectedId?: string;
searchTerm?: string;
isActionModal: boolean;
onOpenActionModal: () => void;
onCloseActionModal: () => void;
onEdit: () => void; // (id: string) => void;
onDelete: () => void; // (id: string) => void;
onSelect?: () => void; // (id: string) => void;
onOpenDetails: () => void;
};
}

export function ProductListRow({
id,
selectedId,
status,
price,
imgSrc,
title,
category,
onOpenDetails,
image,
searchTerm = "",
isBottomRow,
isActionModal,
onCloseActionModal,
onOpenActionModal,
onEdit,
onDelete,
// onSelect,
name,
stock_status,
onOpenDetails,
}: ProductListRowProperties) {
const { selectedProduct } = useOrgContext();
const modalReference = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!isActionModal || !modalReference.current) return;
modalReference.current.scrollIntoView({ behavior: "smooth" });
// handle click outside modal

const handleOutsideClick = (event: MouseEvent) => {
if (
modalReference.current &&
Expand All @@ -70,7 +63,7 @@ export function ProductListRow({
key={id}
className={cn(
"relative bg-white",
selectedId === id ? "bg-[#F1F5F9]" : "",
selectedProduct === id ? "bg-[#F1F5F9]" : "",
)}
>
<TableCell className="flex items-center justify-start gap-x-2 whitespace-nowrap md:gap-x-4">
Expand All @@ -80,7 +73,7 @@ export function ProductListRow({
className="sticky left-0 size-4 min-[500px]:size-5 lg:size-8"
/>
<BlurImage
src={imgSrc}
src={image}
alt="Product"
width={40}
height={40}
Expand All @@ -92,7 +85,7 @@ export function ProductListRow({
onClick={onOpenDetails}
className="hide_scrollbar ml-0.5 w-[110px] whitespace-break-spaces text-[10px] text-neutral-dark-2 min-[376px]:text-xs sm:ml-1 md:w-[200px] md:text-base lg:w-[200px]"
>
<ProductHighlightTerm searchTerm={searchTerm} title={title} />
<ProductHighlightTerm searchTerm={searchTerm} name={name} />
</span>
</TableCell>
<TableCell
Expand Down Expand Up @@ -124,22 +117,22 @@ export function ProductListRow({
>
<span
className={cn("size-2 rounded-full sm:size-3", {
"bg-[#6DC347]": status === "in_stock",
"bg-[#DC2626]": status === "out_of_stock",
"bg-[#EAB308]": status === "low_on_stock",
"bg-[#6DC347]": stock_status === "in stock",
"bg-[#DC2626]": stock_status === "out of stock",
"bg-[#EAB308]": stock_status === "preorder",
})}
/>
{status === "in_stock" && "In Stock"}
{status === "low_on_stock" && "Low on Stock"}
{status === "out_of_stock" && "Out of Stock"}
{stock_status === "in stock" && "In Stock"}
{stock_status === "preorder" && "Low on Stock"}
{stock_status === "out of stock" && "Out of Stock"}
</span>
</TableCell>
<TableCell className="relative whitespace-nowrap px-2 py-4 md:gap-x-4 min-[1440px]:px-6">
<Button onClick={onOpenActionModal} variant={"ghost"} size={"icon"}>
<MoreVertical />
</Button>
<AnimatePresence>
{isActionModal && selectedId === id && (
{isActionModal && selectedProduct === id && (
<motion.div
ref={modalReference}
initial={{ opacity: 0, y: -20, x: 20 }}
Expand Down
9 changes: 8 additions & 1 deletion src/contexts/orgContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ interface OrgContextProperties {
setIsDelete: React.Dispatch<React.SetStateAction<boolean>>;
isOpen: boolean;
updateOpen: React.Dispatch<React.SetStateAction<boolean>>;
isActionModal: boolean;
setIsActionModal: React.Dispatch<React.SetStateAction<boolean>>;
}

export const OrgContext = createContext({} as OrgContextProperties);
Expand All @@ -50,8 +52,9 @@ const OrgContextProvider = ({ children }: { children: React.ReactNode }) => {
const [isNewModal, setIsNewModal] = useState(false);
const [isDelete, setIsDelete] = useState(false);
const [isOpen, updateOpen] = useState(false);
const [isActionModal, setIsActionModal] = useState(false);

const isAnyModalOpen = isNewModal || isDelete || isOpen;
const isAnyModalOpen = isNewModal || isDelete || isOpen || isActionModal;

useLayoutEffect(() => {
startTransition(() => {
Expand Down Expand Up @@ -81,6 +84,7 @@ const OrgContextProvider = ({ children }: { children: React.ReactNode }) => {
setIsNewModal(false);
setIsDelete(false);
updateOpen(false);
setIsActionModal(false);
}
};

Expand Down Expand Up @@ -115,6 +119,8 @@ const OrgContextProvider = ({ children }: { children: React.ReactNode }) => {
setIsDelete,
isOpen,
updateOpen,
isActionModal,
setIsActionModal,
}),
[
isLoading,
Expand All @@ -126,6 +132,7 @@ const OrgContextProvider = ({ children }: { children: React.ReactNode }) => {
isNewModal,
isDelete,
isOpen,
isActionModal,
],
);

Expand Down
1 change: 1 addition & 0 deletions src/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ declare module "next-auth" {
email: User["email"];
image: User["avatar_url"];
role: User["role"];
is_superadmin?: boolean;
} & DefaultSession["user"];
access_token?: string;
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export const authRoutes = [
"/recovery",
];

export const superAdminRoutes = ["dashboard/admin/faqs"];

export const apiAuthPrefix = "/api/";

/**
Expand Down
31 changes: 21 additions & 10 deletions src/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import { NextRequest, NextResponse } from "next/server";

import { apiAuthPrefix, authRoutes, publicRoutes } from "~/lib/routes";
import {
apiAuthPrefix,
authRoutes,
publicRoutes,
superAdminRoutes,
} from "~/lib/routes";
import { auth } from "./lib/auth";

const NEXT_PUBLIC_ROOT_DOMAIN = "staging.nextjs.boilerplate.hng.tech";

export default async function middleware(request: NextRequest) {
const { nextUrl } = request;
const session = await auth();

const isLoggedIn = true;
const isLoggedIn = session !== null;
const isSuperAdmin = session?.user?.is_superadmin === true;

const isApiAuthRoute = nextUrl.pathname.startsWith(apiAuthPrefix);
const isPublicRoute = publicRoutes.includes(nextUrl.pathname);
const isAuthRoute = authRoutes.includes(nextUrl.pathname);
const isSuperAdminRoute = superAdminRoutes.includes(nextUrl.pathname);

if (isApiAuthRoute || isPublicRoute) return;
if (isApiAuthRoute || isPublicRoute) return NextResponse.next();

const url = request.nextUrl;
let hostname = request.headers
Expand All @@ -21,30 +31,31 @@ export default async function middleware(request: NextRequest) {

hostname = hostname.replace("www.", ""); // remove www. from domain
const searchParameters = request.nextUrl.searchParams.toString();
// Get the pathname of the request (e.g. /, /about, /blog/first-post)
const path = `${url.pathname}${
searchParameters.length > 0 ? `?${searchParameters}` : ""
}`;

//rewrites for dashboard pages and dev subdomains
// Rewrites for dashboard pages and dev subdomains
if (hostname == `dashboard.${NEXT_PUBLIC_ROOT_DOMAIN}`) {
if (!isLoggedIn && !isAuthRoute) {
return Response.redirect(
return NextResponse.redirect(
new URL(`/login?callbackUrl=${nextUrl.pathname}`, nextUrl),
);
} else if (isLoggedIn && isAuthRoute) {
return Response.redirect(new URL("/", nextUrl));
return NextResponse.redirect(new URL("/", nextUrl));
}
if (isSuperAdminRoute && !isSuperAdmin) {
return NextResponse.redirect(new URL("/dashboard", nextUrl));
}
return NextResponse.rewrite(
new URL(`/dashboard${path === "/" ? "/" : path}`, request.url),
);
}

return;
return NextResponse.next();
}

// Optionally, don't invoke Middleware on some paths
export const config = {
// eslint-disable-next-line unicorn/prefer-string-raw
matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
matcher: [String.raw`/((?!.+\.[\w]+$|_next).*)`, "/", "/(api|trpc)(.*)"],
};

0 comments on commit 1ccddd0

Please sign in to comment.