Skip to content

Commit

Permalink
refactor: Copy and refactor nav (#107)
Browse files Browse the repository at this point in the history
  • Loading branch information
soniaklimas authored Dec 11, 2024
1 parent b5e10bd commit 8c1daf7
Show file tree
Hide file tree
Showing 14 changed files with 539 additions and 347 deletions.
128 changes: 91 additions & 37 deletions apps/storefront/src/app/[locale]/(main)/_components/navigation.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,119 @@
"use client";

import { type Menu } from "@nimara/domain/objects/Menu";
import { useState } from "react";

import type { Menu } from "@nimara/domain/objects/Menu";
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
} from "@nimara/ui/components/navigation-menu";
import { RichText } from "@nimara/ui/components/rich-text";

import { Link, useRouter } from "@/i18n/routing";
import { generateLinkUrl } from "@/lib/helpers";
import { paths } from "@/lib/paths";
import { Link } from "@/i18n/routing";
import { isValidJson } from "@/lib/helpers";
import type { Maybe } from "@/lib/types";

export const Navigation = ({ menu }: { menu: Maybe<Menu> }) => {
const router = useRouter();
// Close menu manually
const [currentMenuItem, setCurrentMenuItem] = useState("");

if (!menu || menu?.items?.length === 0) {
return null;
}

return (
<NavigationMenu className="mx-auto hidden pb-6 pt-3 md:flex">
<NavigationMenuList>
<NavigationMenu
onValueChange={setCurrentMenuItem}
value={currentMenuItem}
className="mx-auto hidden max-w-screen-xl pb-2 pt-2 md:flex"
>
<NavigationMenuList className="gap-6">
{menu.items.map((item) => (
<NavigationMenuItem key={item.id}>
<NavigationMenuTrigger
showIcon={!!item.children?.length}
onClick={() =>
item.category?.slug
? router.replace(
paths.search.asPath({
query: {
category: item.category.slug,
},
}),
)
: undefined
}
<Link
href={item.url}
className="text-inherit no-underline hover:underline"
>
{item.name}
</NavigationMenuTrigger>
{item.children?.length ? (
<NavigationMenuTrigger showIcon={!!item.children?.length}>
{item.label}
</NavigationMenuTrigger>
</Link>
{!!item.children?.length && (
<NavigationMenuContent>
<div className="grid w-[400px] p-2">
{item.children?.map((child) => (
<NavigationMenuLink asChild key={child.id} className="p-2">
<Link href={generateLinkUrl(child, paths)}>
<div className="text-sm font-medium leading-none group-hover:underline">
{child.name ||
child.collection?.name ||
child.category?.name}
</div>
</Link>
</NavigationMenuLink>
))}
<div className="grid w-full grid-cols-6 p-6">
<div className="col-span-2 flex flex-col gap-3 pr-6">
{!!item.children?.length &&
item.children
?.filter((child) => !child.collectionImageUrl)
.map((child) => (
<Link
key={child.id}
href={child.url}
className="group block space-y-1 rounded-md p-3 hover:bg-accent"
>
<div className="text-sm font-medium leading-none">
{child.label}
</div>
<div className="text-sm leading-snug text-muted-foreground">
{child.description &&
isValidJson(child.description) ? (
<RichText
className="py-1"
jsonStringData={child.description}
/>
) : (
<p className="py-1">{child.description}</p>
)}
</div>
</Link>
))}
</div>

<div className="col-span-4 grid grid-cols-3 gap-3">
{!!item.children?.length &&
item.children
?.filter((child) => child.collectionImageUrl)
.slice(0, 3)
.map((child) => (
<Link
key={child.id}
href={child.url}
className="group relative min-h-[270px] overflow-hidden rounded-lg bg-accent"
onClick={() => setCurrentMenuItem("")}
>
<div
className="h-1/2 bg-cover bg-center"
style={{
backgroundImage: `url(${child?.collectionImageUrl})`,
}}
/>
<div className="flex h-1/2 flex-col justify-start bg-muted/50 p-6">
<div className="relative z-20 space-y-2">
<div className="text-lg font-medium leading-none group-hover:underline">
{child.label}
</div>
<div className="text-sm leading-snug text-muted-foreground">
{child.description &&
isValidJson(child.description) ? (
<RichText
className="py-1"
jsonStringData={child.description}
/>
) : (
<p className="py-1">{child.description}</p>
)}
</div>
</div>
</div>
</Link>
))}
</div>
</div>
</NavigationMenuContent>
) : null}
)}
</NavigationMenuItem>
))}
</NavigationMenuList>
Expand Down
11 changes: 4 additions & 7 deletions apps/storefront/src/components/footer/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { getTranslations } from "next-intl/server";
import NimaraLogo from "@/assets/nimara-logo.svg";
import { CACHE_TTL } from "@/config";
import { Link } from "@/i18n/routing";
import { generateLinkUrl } from "@/lib/helpers";
import { paths } from "@/lib/paths";
import { getCurrentRegion } from "@/regions/server";
import { cmsMenuService } from "@/services/cms";
Expand Down Expand Up @@ -73,8 +72,8 @@ export const Footer = async () => {
</span>
<ul className="grid gap-4">
{categories?.menu.items.map((item) => (
<li key={item.name}>
<Link href={generateLinkUrl(item, paths)}>{item.name}</Link>
<li key={item.id}>
<Link href={item.url}>{item.label}</Link>
</li>
))}
</ul>
Expand All @@ -84,10 +83,8 @@ export const Footer = async () => {
<span className="text-neutral-600"> {t("footer.help")}</span>
<ul className="grid gap-4">
{pages?.menu.items.map((item) => (
<li key={item.page?.title}>
<Link href={generateLinkUrl(item, paths)}>
{item?.name || item.page?.title}
</Link>
<li key={item.id}>
<Link href={item.url}>{item.label}</Link>
</li>
))}
</ul>
Expand Down
4 changes: 4 additions & 0 deletions apps/storefront/src/components/header/mobile-side-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Suspense, useEffect, useState } from "react";
import type { Menu } from "@nimara/domain/objects/Menu";
import type { User } from "@nimara/domain/objects/User";
import { Button } from "@nimara/ui/components/button";
import { DialogTitle } from "@nimara/ui/components/dialog";
import { Sheet, SheetContent } from "@nimara/ui/components/sheet";

import { MobileNavigation } from "@/components/mobile-navigation";
Expand Down Expand Up @@ -55,6 +56,9 @@ export const MobileSideMenu = ({

<Sheet open={isOpen} onOpenChange={setIsOpen}>
<SheetContent side="left" className="w-screen sm:w-1/2">
<DialogTitle>
<span className="sr-only">Menu</span>
</DialogTitle>
<div className="relative flex h-full flex-col justify-between gap-4 overflow-auto">
<div className="flex h-full flex-col">
<div
Expand Down
46 changes: 14 additions & 32 deletions apps/storefront/src/components/mobile-navigation.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import type { Menu } from "@nimara/domain/objects/Menu";

import { Link } from "@/i18n/routing";
import { generateLinkUrl, isInternalUrl } from "@/lib/helpers";
import { paths } from "@/lib/paths";
import type { Maybe } from "@/lib/types";

export const MobileNavigation = ({
Expand All @@ -12,43 +10,27 @@ export const MobileNavigation = ({
menu: Maybe<Menu>;
onMenuItemClick: (isMenuItemClicked: boolean) => void;
}) => {
const handleClick = () => {
onMenuItemClick(true);
};

if (!menu || menu?.items?.length === 0) {
return null;
}

return (
<ul className="grid py-4">
{menu.items.map((item) => (
<li key={item.id} className="p-2 text-stone-500" onClick={handleClick}>
{isInternalUrl(item.url) ? (
<Link href={generateLinkUrl(item, paths)}>
{item.name || item.category?.name || item.collection?.name}
{item.children?.length ? (
<ul>
{item.children.map((child) => (
<li
key={child.id}
className="py-2 text-stone-900"
onClick={handleClick}
>
<Link href={generateLinkUrl(child, paths)}>
{child.name ||
child.collection?.name ||
child.category?.name}
</Link>
</li>
))}
</ul>
) : null}
</Link>
) : (
<a href={item.url as string} target="_blank">
{item.name}
</a>
<li key={item.id} className="p-2 text-stone-500">
<Link href={item.url} onClick={() => onMenuItemClick(true)}>
{item.label}
</Link>
{!!item.children?.length && (
<ul className="mt-2 pl-6">
{item.children.map((child) => (
<li key={child.id} className="py-1 pl-2 text-stone-700">
<Link href={child.url} onClick={() => onMenuItemClick(true)}>
{child.label}
</Link>
</li>
))}
</ul>
)}
</li>
))}
Expand Down
66 changes: 5 additions & 61 deletions apps/storefront/src/lib/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,9 @@
import type { MenuItem } from "@nimara/domain/objects/Menu";
import { loggingService } from "@nimara/infrastructure/logging/service";
export const isValidJson = (value: string) => {
try {
JSON.parse(value);

interface Paths {
page: {
asPath: (params: { slug: string }) => string;
};
search: {
asPath: (params: { query: Record<string, string> }) => string;
};
}
//TO DO - handle validating internal url
export const generateLinkUrl = (item: MenuItem, paths: Paths): string => {
if (item.collection) {
return paths.search.asPath({
query: { collection: item.collection.slug },
});
}
if (item.category) {
return paths.search.asPath({
query: { category: item.category.slug },
});
}
if (item.page) {
return paths.page.asPath({ slug: item.page.slug });
}
if (item.url) {
return item.url;
}

return "#";
};

const internalUrls = [
"https://nimara-dev.vercel.app",
"https://nimara-stage.vercel.app",
"https://nimara-prod.vercel.app",
"http://localhost",
"https://localhost",
];

export const isInternalUrl = (url: string | null): boolean => {
if (url === null) {
return true;
} catch (error) {
return false;
}
if (url) {
try {
const parsedUrl = new URL(url);

return internalUrls.some((internalUrl) => {
const internalParsedUrl = new URL(internalUrl);

return parsedUrl.hostname === internalParsedUrl.hostname;
});
} catch (e) {
loggingService.error("Given URL is not internal", {
error: e,
});

return false;
}
}

return false;
};
12 changes: 6 additions & 6 deletions packages/codegen/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16873,17 +16873,17 @@ export type OrderLine = Node & ObjectWithMetadata & {
translatedVariantName: Scalars['String']['output'];
/** Price of the order line without discounts. */
undiscountedTotalPrice: TaxedMoney;
/** Price of the single item in the order line without applied an order line discount. */
/** Price of the single item in the order line without any discount applied. */
undiscountedUnitPrice: TaxedMoney;
/** The discount applied to the single order line. */
/** Sum of the line-level discounts applied to the order line. Order-level discounts which affect the line are not visible in this field. For order-level discount portion (if any), please query `order.discounts` field. */
unitDiscount: Money;
/** Reason for any discounts applied on a product in the order. */
/** Reason for line-level discounts applied on the order line. Order-level discounts which affect the line are not visible in this field. For order-level discount reason (if any), please query `order.discounts` field. */
unitDiscountReason: Maybe<Scalars['String']['output']>;
/** Type of the discount: fixed or percent */
/** Type of the discount: `fixed` or `percent`. This field shouldn't be used when multiple discounts affect the line. There is a limitation, that after running `checkoutComplete` mutation the field is always set to `fixed`. */
unitDiscountType: Maybe<DiscountValueTypeEnum>;
/** Value of the discount. Can store fixed value or percent value */
/** Value of the discount. Can store fixed value or percent value. This field shouldn't be used when multiple discounts affect the line. There is a limitation, that after running `checkoutComplete` mutation the field always stores fixed value. */
unitDiscountValue: Scalars['PositiveDecimal']['output'];
/** Price of the single item in the order line. */
/** Price of the single item in the order line with all the line-level discounts and order-level discount portions applied. */
unitPrice: TaxedMoney;
/** A purchased product variant. Note: this field may be null if the variant has been removed from stock at all. Requires one of the following permissions to include the unpublished items: MANAGE_ORDERS, MANAGE_DISCOUNTS, MANAGE_PRODUCTS. */
variant: Maybe<ProductVariant>;
Expand Down
Loading

0 comments on commit 8c1daf7

Please sign in to comment.