Skip to content

Commit

Permalink
feat: Adjust storefront for fashion products
Browse files Browse the repository at this point in the history
  • Loading branch information
karolkarolka committed Nov 12, 2024
1 parent 180728a commit a97333f
Show file tree
Hide file tree
Showing 23 changed files with 921 additions and 51 deletions.
15 changes: 14 additions & 1 deletion apps/storefront/messages/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
"artists": "Artists",
"in-stock": "In stock",
"is-exclusive": "Is exclusive",
"is-digital": "Is digital"
"is-digital": "Is digital",
"size": "Size",
"color": "Color"
},
"search": {
"go-to-product": "Go to product {name}",
Expand Down Expand Up @@ -344,5 +346,16 @@
"faq": "FAQ",
"privacy-policy": "Privacy Policy",
"terms-of-use": "Terms of Use"
},
"colors": {
"yellow": "Yellow",
"black": "Black",
"white": "White",
"beige": "Beige",
"grey": "Grey",
"khaki": "Khaki",
"pink": "Pink",
"red": "Red",
"green": "Green"
}
}
21 changes: 3 additions & 18 deletions apps/storefront/src/app/[locale]/(main)/_components/navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@ import {
NavigationMenuTrigger,
} from "@nimara/ui/components/navigation-menu";

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

export const Navigation = ({ menu }: { menu: Maybe<Menu> }) => {
const router = useRouter();

if (!menu || menu?.items?.length === 0) {
return null;
}
Expand All @@ -27,21 +25,8 @@ export const Navigation = ({ menu }: { menu: Maybe<Menu> }) => {
<NavigationMenuList>
{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
}
>
{item.name}
<NavigationMenuTrigger showIcon={!!item.children?.length}>
{item?.url && <Link href={item.url}>{item.name}</Link>}
</NavigationMenuTrigger>
{item.children?.length ? (
<NavigationMenuContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export const ProductsGrid = async ({

const { results: products } = await searchService.search(
{
productIds: gridProductsIds.length ? [...gridProductsIds] : [],
productIds: gridProductsIds?.length ? [...gridProductsIds] : [],
limit: 7,
},
searchContext,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { getTranslations } from "next-intl/server";

import type { Facet } from "@nimara/infrastructure/use-cases/search/types";
import { Toggle } from "@nimara/ui/components/toggle";

import { cn } from "@/lib/utils";
import type { TranslationMessage } from "@/types";

import type { ColorValue } from "./filters-container";

const colors: Record<ColorValue, string> = {
yellow: "bg-[#fffa4B]",
black: "bg-black",
white: "bg-white",
beige: "bg-[#dbc1a3]",
grey: "bg-[#808080]",
khaki: "bg-[#c3b091]",
pink: "bg-[#ff9ac6]",
red: "bg-[#e50101]",
green: "bg-[#339f2b]",
};

export const ColorSwatch = async ({
facet: { choices, name, slug, messageKey },
searchParams,
}: {
facet: Facet;
searchParams: Record<string, string>;
}) => {
const t = await getTranslations();
const label = name ?? t(messageKey as TranslationMessage);
const defaultValue = searchParams[slug]?.split(".") ?? [];

return (
<div className="flex flex-col space-y-4 py-2">
{label && (
<h3 className="text-base font-medium text-stone-700">{label}</h3>
)}
<div className="flex flex-wrap gap-2">
{choices?.map((choice) => (
<div key={choice.value}>
<input
type="checkbox"
name={`group${slug}-${choice.value}`}
id={`group${slug}-${choice.value}`}
className="peer hidden"
defaultChecked={defaultValue.includes(choice.value)}
/>
<Toggle
asChild
defaultPressed={defaultValue.includes(choice.value)}
className="flex items-center justify-center gap-2 rounded-sm text-stone-900"
variant="outline"
>
<label
htmlFor={`group${slug}-${choice.value}`}
className="cursor-pointer peer-checked:bg-accent"
>
<div
className={cn(
"h-6 w-6 border border-stone-200",
colors[choice.value as ColorValue],
)}
/>
{choice.label}
</label>
</Toggle>
</div>
))}
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { getTranslations } from "next-intl/server";

import type { Facet } from "@nimara/infrastructure/use-cases/search/types";
import { Toggle } from "@nimara/ui/components/toggle";

import { type TranslationMessage } from "@/types";

export const FilterText = async ({
facet: { choices, name, slug, messageKey },
searchParams,
}: {
facet: Facet;
searchParams: Record<string, string>;
}) => {
const t = await getTranslations();
const defaultValue = searchParams[slug]?.split(".") ?? [];
const label = name ?? t(messageKey as TranslationMessage);

return (
<div className="flex flex-col space-y-4 py-2">
{label && (
<h3 className="text-base font-medium text-stone-700">{label}</h3>
)}
<div className="flex flex-wrap gap-2">
{choices?.map((choice) => (
<div key={choice.value}>
<input
type="checkbox"
name={`group${slug}-${choice.value}`}
id={`group${slug}-${choice.value}`}
className="peer hidden"
defaultChecked={defaultValue.includes(choice.value)}
/>
<Toggle
asChild
defaultPressed={defaultValue.includes(choice.value)}
className="flex items-center justify-center gap-2 rounded-sm text-stone-900"
variant="outline"
>
<label
htmlFor={`group${slug}-${choice.value}`}
className="cursor-pointer peer-checked:bg-accent"
>
{choice.label}
</label>
</Toggle>
</div>
))}
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Filter } from "lucide-react";
import { getTranslations } from "next-intl/server";

import { type SortByOption } from "@nimara/domain/objects/Search";
import type { SortByOption } from "@nimara/domain/objects/Search";
import type { Facet } from "@nimara/infrastructure/use-cases/search/types";
import { Button } from "@nimara/ui/components/button";
import { Label } from "@nimara/ui/components/label";
Expand All @@ -22,8 +22,10 @@ import { DEFAULT_SORT_BY } from "@/config";
import { type TranslationMessage } from "@/types";

import { handleFiltersFormSubmit } from "../actions";
import { ColorSwatch } from "./color-swatch";
import { FilterBoolean } from "./filter-boolean";
import { FilterDropdown } from "./filter-dropdown";
import { FilterText } from "./filter-text";

type Props = {
facets: Facet[];
Expand All @@ -36,8 +38,23 @@ const renderFilterComponent = (
searchParams: Record<string, string>,
) => {
// TODO: Extend this function for other, more adequate Filter components
switch (facet.type) {
switch (facet?.type) {
case "PLAIN_TEXT":
return (
<FilterText
key={facet.name}
facet={facet}
searchParams={searchParams}
/>
);
case "SWATCH":
return (
<ColorSwatch
key={facet.name}
facet={facet}
searchParams={searchParams}
/>
);
case "MULTISELECT":
case "DROPDOWN":
return (
Expand All @@ -58,12 +75,37 @@ const renderFilterComponent = (
}
};

const colors = [
"yellow",
"black",
"white",
"beige",
"grey",
"khaki",
"pink",
"red",
"green",
] as const;

export type ColorValue = (typeof colors)[number];

export const FiltersContainer = async ({
facets,
searchParams,
sortByOptions,
}: Props) => {
const t = await getTranslations();
const genderFacet = facets.filter((facet) => facet.slug === "gender")[0];
const sizeFacet = facets.filter((facet) => facet.slug === "size")[0];
const colorFacet = facets
.filter((facet) => facet.slug === "color")
.map((facet) => ({
...facet,
choices: colors.map((color) => ({
label: t(`colors.${color}`),
value: color as string,
})),
}))[0];

const updateFiltersWithSearchParams = handleFiltersFormSubmit.bind(
null,
Expand All @@ -86,14 +128,17 @@ export const FiltersContainer = async ({
<form
action={updateFiltersWithSearchParams}
className="flex h-full flex-col"
id="filters-form"
>
<SheetHeader>
<SheetTitle>{t("filters.filters")}</SheetTitle>
<SheetTitle className="text-stone-700">
{t("filters.filters")}
</SheetTitle>
</SheetHeader>

<SheetDescription asChild>
<ScrollArea>
<div className="grid h-full gap-6 py-4">
<div className="grid h-full gap-4 py-4">
<RadioGroup
name="sortBy"
className="grid gap-4 md:hidden"
Expand All @@ -111,22 +156,13 @@ export const FiltersContainer = async ({
</RadioGroup>

<div className="grid items-center gap-4">
{facets
.filter(({ type }) => type !== "BOOLEAN")
.map((facet) => renderFilterComponent(facet, searchParams))}
{renderFilterComponent(genderFacet, searchParams)}
</div>

<div>
<p className="mb-4 text-base text-black">
{t("filters.options")}
</p>
<div className="grid items-center gap-4">
{facets
.filter(({ type }) => type === "BOOLEAN")
.map((facet) =>
renderFilterComponent(facet, searchParams),
)}
</div>
<div className="grid items-center gap-4">
{renderFilterComponent(sizeFacet, searchParams)}
</div>
<div className="grid items-center gap-4">
{renderFilterComponent(colorFacet, searchParams)}
</div>
</div>
</ScrollArea>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ type Props = {
export const ProductsList = ({ products }: Props) => {
return (
<div className="grid grid-cols-[repeat(auto-fill,minmax(300px,1fr))] gap-4">
{products.map((product) => (
<SearchProductCard key={product.id} product={product} />
{products.map((product, index) => (
<SearchProductCard key={`${product.id}-${index}`} product={product} />
))}
</div>
);
Expand Down
6 changes: 5 additions & 1 deletion apps/storefront/src/app/[locale]/(main)/search/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ export const handleFiltersFormSubmit = async (
const params = new URLSearchParams();

formData.forEach((value, key) => {
if (value && typeof value === "string" && !formClear) {
if (key.startsWith("group")) {
const [k, v] = key.replace("group", "").split("-");

params.set(k, params.getAll(k).concat(v).join("."));
} else if (value && typeof value === "string" && !formClear) {
params.set(key, value);
}
});
Expand Down
3 changes: 2 additions & 1 deletion apps/storefront/src/app/[locale]/(main)/search/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export default async function Page({ searchParams }: PageProps) {
limit,
...rest
} = searchParams;

const { results, pageInfo } = await searchService.search(
{
query,
Expand Down Expand Up @@ -94,7 +95,7 @@ export default async function Page({ searchParams }: PageProps) {
if (searchParams.category) {
return (
searchParams.category[0].toUpperCase() + searchParams.category.slice(1)
);
).replaceAll("-", " & ");
}

return null;
Expand Down
2 changes: 1 addition & 1 deletion apps/storefront/src/components/header/search-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type SearchState = {
};

const minLetters = 3;
const maxSearchSuggestions = 15;
const maxSearchSuggestions = 10;
const keyboardCodes = {
ArrowDown: "ArrowDown",
ArrowUp: "ArrowUp",
Expand Down
Loading

0 comments on commit a97333f

Please sign in to comment.