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

refactor: Copy and refactor nav #107

Merged
merged 7 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
129 changes: 92 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,120 @@
"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"
// onClick={() => setCurrentMenuItem("")}
soniaklimas marked this conversation as resolved.
Show resolved Hide resolved
>
<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})`,
karolkarolka marked this conversation as resolved.
Show resolved Hide resolved
}}
/>
<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: {
karolkarolka marked this conversation as resolved.
Show resolved Hide resolved
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
Loading