feat: implement new footer
Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
@ -19,7 +19,7 @@ const FooterMenuItem = ({ item }: { item: Menu }) => {
|
||||
<Link
|
||||
href={item.path}
|
||||
className={clsx(
|
||||
'block p-2 text-lg underline-offset-4 hover:text-black hover:underline md:inline-block md:text-sm dark:hover:text-neutral-300',
|
||||
'block py-2 text-lg underline-offset-4 hover:underline md:inline-block md:text-sm',
|
||||
{
|
||||
'text-black dark:text-neutral-300': active
|
||||
}
|
||||
@ -31,16 +31,31 @@ const FooterMenuItem = ({ item }: { item: Menu }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default function FooterMenu({ menu }: { menu: Menu[] }) {
|
||||
function FooterMenu({ menu }: { menu: Menu[] }) {
|
||||
if (!menu.length) return null;
|
||||
|
||||
return (
|
||||
<nav>
|
||||
<ul>
|
||||
{menu.map((item: Menu) => {
|
||||
return <FooterMenuItem key={item.title} item={item} />;
|
||||
})}
|
||||
</ul>
|
||||
<ul>
|
||||
{menu.map((item: Menu) => {
|
||||
return <FooterMenuItem key={item.title} item={item} />;
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FooterMenuGrid({ menu }: { menu: Menu[] }) {
|
||||
if (!menu.length) return null;
|
||||
|
||||
return (
|
||||
<nav className="ml-2 flex lg:ml-auto">
|
||||
<div className="grid w-full grid-cols-2 gap-0 md:grid-cols-3 lg:gap-4">
|
||||
{menu.map((item) => (
|
||||
<div key={item.title}>
|
||||
<span className="text-primary">{item.title}</span>
|
||||
<FooterMenu menu={item.items} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import Link from 'next/link';
|
||||
import FooterMenu from 'components/layout/footer-menu';
|
||||
import LogoSquare from 'components/logo-square';
|
||||
import { getMenu } from 'lib/shopify';
|
||||
import Image from 'next/image';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
const { COMPANY_NAME, SITE_NAME } = process.env;
|
||||
@ -11,17 +12,49 @@ export default async function Footer() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const copyrightDate = 2023 + (currentYear > 2023 ? `-${currentYear}` : '');
|
||||
const skeleton = 'w-full h-6 animate-pulse rounded bg-neutral-200 dark:bg-neutral-700';
|
||||
const menu = await getMenu('next-js-frontend-footer-menu');
|
||||
const menu = await getMenu('footer');
|
||||
const copyrightName = COMPANY_NAME || SITE_NAME || '';
|
||||
|
||||
return (
|
||||
<footer className="text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<div className="mx-auto flex w-full max-w-7xl flex-col gap-6 border-t border-neutral-200 px-6 py-12 text-sm md:flex-row md:gap-12 md:px-4 min-[1320px]:px-0 dark:border-neutral-700">
|
||||
<div>
|
||||
<Link className="flex items-center text-black md:pt-1 dark:text-white" href="/">
|
||||
<footer className="bg-dark text-sm text-white">
|
||||
<div className="mx-auto flex w-full max-w-7xl flex-row flex-wrap items-start gap-6 px-6 py-12 text-sm md:gap-12 md:px-4 min-[1320px]:px-0">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Link className="flex items-center text-white md:pt-1" href="/">
|
||||
<LogoSquare />
|
||||
<span className="uppercase">{SITE_NAME}</span>
|
||||
</Link>
|
||||
<a href={`tel:${8882422605}`} className="ml-4 text-white">
|
||||
(888) 242-2605
|
||||
</a>
|
||||
<p className="ml-4">Monday - Friday 9:00am - 8:00pm EST</p>
|
||||
<p className="ml-4">Saturday 11:00am - 4:00pm EST</p>
|
||||
<div className="ml-4 mt-3 flex flex-row items-center gap-4">
|
||||
<a href="https://www.facebook.com/carpartplanet" target="_blank" rel="noreferrer">
|
||||
<Image alt="facebook" src="/icons/facebook.png" width={20} height={20} />
|
||||
</a>
|
||||
<a href="https://www.instagram.com/carpartplanet" target="_blank" rel="noreferrer">
|
||||
<Image alt="instagram" src="/icons/instagram.png" width={20} height={20} />
|
||||
</a>
|
||||
<a
|
||||
href="https://www.youtube.com/channel/UC8CxAf0QAozd2g0clMhkmKA/videos"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Image
|
||||
alt="youtube"
|
||||
src="/icons/youtube.png"
|
||||
width={20}
|
||||
height={20}
|
||||
className="mt-1"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://www.pinterest.com/carpartplanet" target="_blank" rel="noreferrer">
|
||||
<Image alt="pinterest" src="/icons/pinterest.png" width={20} height={20} />
|
||||
</a>
|
||||
<a href="https://twitter.com/carpartplanet" target="_blank" rel="noreferrer">
|
||||
<Image alt="twitter" src="/icons/twitter.png" width={16} height={16} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<Suspense
|
||||
fallback={
|
||||
@ -44,6 +77,16 @@ export default async function Footer() {
|
||||
© {copyrightDate} {copyrightName}
|
||||
{copyrightName.length && !copyrightName.endsWith('.') ? '.' : ''} All rights reserved.
|
||||
</p>
|
||||
<div className="ml-0 flex flex-row items-center gap-2 md:ml-auto">
|
||||
<Image alt="visa" src="/icons/visa.png" width={30} height={20} />
|
||||
<Image alt="mastercard" src="/icons/mastercard.png" width={30} height={20} />
|
||||
<Image
|
||||
alt="american-express"
|
||||
src="/icons/american-express.png"
|
||||
width={30}
|
||||
height={20}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { HIDDEN_PRODUCT_TAG, SHOPIFY_GRAPHQL_API_ENDPOINT, TAGS } from 'lib/constants';
|
||||
import { isShopifyError } from 'lib/type-guards';
|
||||
import { ensureStartsWith } from 'lib/utils';
|
||||
import { ensureStartsWith, normalizeUrl } from 'lib/utils';
|
||||
import { revalidateTag } from 'next/cache';
|
||||
import { headers } from 'next/headers';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
@ -346,12 +346,16 @@ export async function getMenu(handle: string): Promise<Menu[]> {
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
res.body?.data?.menu?.items.map((item: { title: string; url: string }) => ({
|
||||
const formatMenuItems = (
|
||||
menu: { title: string; url: string; items?: { title: string; url: string }[] }[] = []
|
||||
): Menu[] =>
|
||||
menu.map((item) => ({
|
||||
title: item.title,
|
||||
path: item.url.replace(domain, '').replace('/collections', '/search').replace('/pages', '')
|
||||
})) || []
|
||||
);
|
||||
path: normalizeUrl(domain, item.url),
|
||||
items: item.items?.length ? formatMenuItems(item.items) : []
|
||||
}));
|
||||
|
||||
return formatMenuItems(res.body?.data?.menu?.items);
|
||||
}
|
||||
|
||||
export async function getPage(handle: string): Promise<Page> {
|
||||
|
@ -4,6 +4,10 @@ export const getMenuQuery = /* GraphQL */ `
|
||||
items {
|
||||
title
|
||||
url
|
||||
items {
|
||||
title
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ export type Image = {
|
||||
export type Menu = {
|
||||
title: string;
|
||||
path: string;
|
||||
items: Menu[];
|
||||
};
|
||||
|
||||
export type Money = {
|
||||
|
@ -43,3 +43,7 @@ export const validateEnvironmentVariables = () => {
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export function normalizeUrl(domain: string, url: string) {
|
||||
return url.replace(domain, '').replace('/collections', '/search').replace('/pages', '');
|
||||
}
|
||||
|
BIN
public/icons/american-express.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
public/icons/facebook.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
public/icons/instagram.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
public/icons/mastercard.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
public/icons/pinterest.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
public/icons/twitter.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
public/icons/visa.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
public/icons/youtube.png
Normal file
After Width: | Height: | Size: 12 KiB |