wip: Working on footer

This commit is contained in:
Sol Irvine 2023-08-18 18:32:01 +09:00
parent ce7f51a600
commit 95d237c4ac
18 changed files with 141 additions and 80 deletions

View File

@ -12,7 +12,7 @@ export const revalidate = 43200; // 12 hours in seconds
export async function generateMetadata({ export async function generateMetadata({
params params
}: { }: {
params: { page: string; locale: SupportedLocale }; params: { page: string; locale?: SupportedLocale };
}): Promise<Metadata> { }): Promise<Metadata> {
const page = await getPage({ handle: params.page, language: params?.locale?.toUpperCase() }); const page = await getPage({ handle: params.page, language: params?.locale?.toUpperCase() });
@ -32,7 +32,7 @@ export async function generateMetadata({
export default async function Page({ export default async function Page({
params params
}: { }: {
params: { page: string; locale: SupportedLocale }; params: { page: string; locale?: SupportedLocale };
}) { }) {
const page = await getPage({ handle: params.page, language: params?.locale?.toUpperCase() }); const page = await getPage({ handle: params.page, language: params?.locale?.toUpperCase() });

View File

@ -1,9 +1,9 @@
import Navbar from 'components/layout/navbar'; import Navbar from 'components/layout/navbar';
import { Locale } from 'i18n-config'; import { Noto_Serif_JP } from 'next/font/google';
import { Noto_Sans_JP } from 'next/font/google';
import localFont from 'next/font/local'; import localFont from 'next/font/local';
import { ReactNode, Suspense } from 'react'; import { ReactNode, Suspense } from 'react';
import { SupportedLocale } from 'components/layout/navbar/language-control';
import { NextIntlClientProvider } from 'next-intl'; import { NextIntlClientProvider } from 'next-intl';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import './globals.css'; import './globals.css';
@ -56,19 +56,13 @@ const alpina = localFont({
variable: '--font-alpina' variable: '--font-alpina'
}); });
const noto = Noto_Sans_JP({ const noto = Noto_Serif_JP({
subsets: ['latin'], subsets: ['latin'],
display: 'swap', display: 'swap',
weight: ['300', '600'], weight: ['300', '600'],
variable: '--font-noto' variable: '--font-noto'
}); });
const mincho = localFont({
src: '../fonts/A-OTF-A1MinchoStd-Bold.otf',
display: 'swap',
variable: '--font-mincho'
});
export function generateStaticParams() { export function generateStaticParams() {
return [{ locale: 'en' }, { locale: 'ja' }]; return [{ locale: 'en' }, { locale: 'ja' }];
} }
@ -78,7 +72,7 @@ export default async function RootLayout({
params params
}: { }: {
children: ReactNode; children: ReactNode;
params: { locale: Locale }; params: { locale?: SupportedLocale };
}) { }) {
let messages; let messages;
try { try {

View File

@ -25,7 +25,7 @@ export const metadata = {
export default async function HomePage({ export default async function HomePage({
params: { locale } params: { locale }
}: { }: {
params: { locale: SupportedLocale }; params: { locale?: SupportedLocale };
}) { }) {
return ( return (
<> <>

View File

@ -1,5 +1,6 @@
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import LogoNamemark from 'components/icons/namemark';
import { SupportedLocale } from 'components/layout/navbar/language-control'; import { SupportedLocale } from 'components/layout/navbar/language-control';
import Prose from 'components/prose'; import Prose from 'components/prose';
import { getPage } from 'lib/shopify'; import { getPage } from 'lib/shopify';
@ -13,9 +14,9 @@ export const revalidate = 43200; // 12 hours in seconds
export async function generateMetadata({ export async function generateMetadata({
params params
}: { }: {
params: { page: string; locale: SupportedLocale }; params: { locale?: SupportedLocale };
}): Promise<Metadata> { }): Promise<Metadata> {
const page = await getPage({ handle: params.page, language: params?.locale?.toUpperCase() }); const page = await getPage({ handle: 'shop-list', language: params?.locale?.toUpperCase() });
if (!page) return notFound(); if (!page) return notFound();
@ -30,17 +31,16 @@ export async function generateMetadata({
}; };
} }
export default async function Page({ export default async function Page({ params }: { params: { locale?: SupportedLocale } }) {
params const page = await getPage({ handle: 'shop-list', language: params?.locale?.toUpperCase() });
}: {
params: { page: string; locale: SupportedLocale };
}) {
const page = await getPage({ handle: params.page, language: params?.locale?.toUpperCase() });
if (!page) return notFound(); if (!page) return notFound();
return ( return (
<div className="font-multilingual min-h-screen px-4 text-white"> <div className="font-multilingual min-h-screen px-4 text-white">
<div className="pb-12">
<LogoNamemark className="w-[260px] fill-current md:w-[320px]" />
</div>
<ShopsTitle /> <ShopsTitle />
<h2 className="mb-8 text-3xl font-medium">{page.title}</h2> <h2 className="mb-8 text-3xl font-medium">{page.title}</h2>
<Prose className="mb-8" html={page.body as string} /> <Prose className="mb-8" html={page.body as string} />

View File

@ -35,7 +35,7 @@ function ThreeItemGridItem({ item, priority }: { item: Product; priority?: boole
); );
} }
export async function ThreeItemGrid({ lang }: { lang: SupportedLocale }) { export async function ThreeItemGrid({ lang }: { lang?: SupportedLocale }) {
// Collections that start with `hidden-*` are hidden from the search page. // Collections that start with `hidden-*` are hidden from the search page.
const homepageItems = await getCollectionProducts({ const homepageItems = await getCollectionProducts({
collection: 'hidden-homepage-featured-items', collection: 'hidden-homepage-featured-items',

View File

@ -1,46 +1,88 @@
'use client'; 'use client';
import clsx from 'clsx'; import { useTranslations } from 'next-intl';
import { Menu } from 'lib/shopify/types';
import Link from 'next/link'; import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useEffect, useState } from 'react';
const FooterMenuItem = ({ item }: { item: Menu }) => { export default function FooterMenu() {
const pathname = usePathname(); const t = useTranslations('Index');
const [active, setActive] = useState(pathname === item.path);
useEffect(() => {
setActive(pathname === item.path);
}, [pathname, item.path]);
return ( return (
<li> <div className="hidden md:grid md:w-full md:grid-cols-2">
<Link <div className="col-span-1">
href={item.path} <div className="mb-4 font-serif text-base underline">{t('menu.title')}</div>
className={clsx( <nav className="font-multilingual flex flex-col space-y-2 text-left text-base font-normal">
'block p-2 text-lg underline-offset-4 hover:text-black hover:underline dark:hover:text-neutral-300 md:inline-block md:text-sm', <div>
{ <Link href="/products" className="transition-opacity duration-150 hover:opacity-50">
'text-black dark:text-neutral-300': active {t('menu.products')}
} </Link>
)} </div>
>
{item.title}
</Link>
</li>
);
};
export default function FooterMenu({ menu }: { menu: Menu[] }) { <div>
if (!menu.length) return null; <Link href="/shop-list" className="transition-opacity duration-150 hover:opacity-50">
{t('menu.shops')}
</Link>
</div>
return ( <div>
<nav> <Link href="/about" className="transition-opacity duration-150 hover:opacity-50">
<ul> {t('menu.about')}
{menu.map((item: Menu) => { </Link>
return <FooterMenuItem key={item.title} item={item} />; </div>
})}
</ul> <div>
</nav> <Link href="/bar" className="transition-opacity duration-150 hover:opacity-50">
{t('menu.bar')}
</Link>
</div>
<div>
<Link href="/concept" className="transition-opacity duration-150 hover:opacity-50">
{t('menu.concept')}
</Link>
</div>
<div>
<Link href="/stories" className="transition-opacity duration-150 hover:opacity-50">
{t('menu.stories')}
</Link>
</div>
<div>
<Link href="/company" className="transition-opacity duration-150 hover:opacity-50">
{t('menu.company')}
</Link>
</div>
</nav>
</div>
<div className="col-span-1">
<div className="mb-4 font-serif text-base underline">{t('shopping-guide.title')}</div>
<nav className="font-multilingual flex flex-col space-y-2 text-left text-base font-normal">
<div>
<Link href="/terms" className="transition-opacity duration-150 hover:opacity-50">
{t('shopping-guide.terms')}
</Link>
</div>
<div>
<Link href="/legal" className="transition-opacity duration-150 hover:opacity-50">
{t('shopping-guide.legal')}
</Link>
</div>
<div>
<Link href="/privacy" className="transition-opacity duration-150 hover:opacity-50">
{t('shopping-guide.privacy')}
</Link>
</div>
<div>
<Link href="/contact" className="transition-opacity duration-150 hover:opacity-50">
{t('shopping-guide.contact')}
</Link>
</div>
</nav>
</div>
</div>
); );
} }

View File

@ -1,4 +1,5 @@
import { getMenu } from 'lib/shopify'; import clsx from 'clsx';
import FooterMenu from './footer-menu';
import NewsletterFooter from './newsletter-footer'; import NewsletterFooter from './newsletter-footer';
const { COMPANY_NAME, SITE_NAME } = process.env; const { COMPANY_NAME, SITE_NAME } = process.env;
@ -6,16 +7,24 @@ const { COMPANY_NAME, SITE_NAME } = process.env;
export default async function Footer() { export default async function Footer() {
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
const copyrightDate = 2023 + (currentYear > 2023 ? `-${currentYear}` : ''); 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 copyrightName = COMPANY_NAME || SITE_NAME || ''; const copyrightName = COMPANY_NAME || SITE_NAME || '';
return ( return (
<footer className="text-sm"> <footer className="px-6 text-sm">
<div className="mx-auto flex w-full max-w-screen-2xl flex-col gap-6 border-t border-white/20 px-6 py-12 text-sm md:flex-row md:gap-12"> <div
<div className="flex flex-row justify-between"> className={clsx(
'mx-auto flex w-full max-w-screen-2xl justify-between',
'flex-col gap-6 py-12',
'border-t border-subtle',
'text-sm md:flex-row md:gap-12'
)}
>
<div className="w-full md:w-1/2">
<NewsletterFooter /> <NewsletterFooter />
</div> </div>
<div className="hidden md:block md:w-1/3">
<FooterMenu />
</div>
</div> </div>
<div> <div>
<div className="mx-auto flex w-full max-w-7xl flex-col items-center gap-1 pb-12 md:flex-row"> <div className="mx-auto flex w-full max-w-7xl flex-col items-center gap-1 pb-12 md:flex-row">

View File

@ -68,7 +68,7 @@ export function MenuModal() {
<div className="fixed inset-0 grid grid-cols-1 place-content-center bg-dark/80"> <div className="fixed inset-0 grid grid-cols-1 place-content-center bg-dark/80">
<div className="flex flex-row justify-end"> <div className="flex flex-row justify-end">
<div className="flex flex-col space-y-4 px-6 text-right"> <nav className="flex flex-col space-y-4 px-6 text-right">
<div> <div>
<Link <Link
href="/products" href="/products"
@ -80,7 +80,7 @@ export function MenuModal() {
<div> <div>
<Link <Link
href="/shops" href="/shop-list"
className="font-serif text-4xl font-normal transition-opacity duration-150 hover:opacity-50" className="font-serif text-4xl font-normal transition-opacity duration-150 hover:opacity-50"
> >
{t('menu.shops')} {t('menu.shops')}
@ -140,7 +140,7 @@ export function MenuModal() {
{t('menu.contact')} {t('menu.contact')}
</Link> </Link>
</div> </div>
</div> </nav>
</div> </div>
</div> </div>
</Dialog.Panel> </Dialog.Panel>

View File

@ -4,7 +4,7 @@ import clsx from 'clsx';
import Link from 'next-intl/link'; import Link from 'next-intl/link';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
export type SupportedLocale = 'en' | 'ja' | undefined; export type SupportedLocale = 'en' | 'ja';
function removeItem<T>(arr: Array<T>, value: T): Array<T> { function removeItem<T>(arr: Array<T>, value: T): Array<T> {
const index = arr.indexOf(value); const index = arr.indexOf(value);

View File

@ -6,13 +6,13 @@ export default function NewsletterSignup() {
const t = useTranslations('Index'); const t = useTranslations('Index');
return ( return (
<div> <div className="max-w-xl">
<div className="flex flex-row items-baseline justify-between space-x-6"> <div className="flex flex-row items-baseline justify-between space-x-6 pb-2">
<h3 className="grow font-serif text-2xl tracking-wider">{t('newsletter.title')}</h3> <h3 className="grow font-serif text-2xl tracking-wider">{t('newsletter.title')}</h3>
<div className="font-multilingual">{t('footer.newsletter.promo')}</div> <div className="font-multilingual">{t('footer.newsletter.promo')}</div>
</div> </div>
<form <form
className="max-w-xl space-x-px md:flex" className="space-x-px md:flex"
action={`${process?.env?.NEXT_PUBLIC_MAILCHIMP_HOST}/subscribe/post?u=${process?.env?.NEXT_PUBLIC_MAILCHIMP_USER_ID}&amp;id=${process?.env?.NEXT_PUBLIC_MAILCHIMP_LIST_ID}`} action={`${process?.env?.NEXT_PUBLIC_MAILCHIMP_HOST}/subscribe/post?u=${process?.env?.NEXT_PUBLIC_MAILCHIMP_USER_ID}&amp;id=${process?.env?.NEXT_PUBLIC_MAILCHIMP_LIST_ID}`}
method="post" method="post"
name="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form"

View File

@ -15,7 +15,7 @@ export default function Shoplist() {
</div> </div>
<div className="grid w-full grid-cols-2 gap-px"> <div className="grid w-full grid-cols-2 gap-px">
<Link <Link
href="shops/hokkaido" href="shop-list/#hokkaido"
className="group col-span-1 flex flex-row items-center justify-between p-6 outline outline-1 outline-subtle" className="group col-span-1 flex flex-row items-center justify-between p-6 outline outline-1 outline-subtle"
> >
<div>{t('shops.hokkaido')}</div> <div>{t('shops.hokkaido')}</div>
@ -27,7 +27,7 @@ export default function Shoplist() {
</div> </div>
</Link> </Link>
<Link <Link
href="shops/kanto" href="shop-list/#kanto"
className="group col-span-1 flex flex-row items-center justify-between p-6 outline outline-1 outline-subtle" className="group col-span-1 flex flex-row items-center justify-between p-6 outline outline-1 outline-subtle"
> >
<div>{t('shops.kanto')}</div> <div>{t('shops.kanto')}</div>
@ -39,7 +39,7 @@ export default function Shoplist() {
</div> </div>
</Link> </Link>
<Link <Link
href="shops/chubu" href="shop-list/#chubu"
className="group col-span-1 flex flex-row items-center justify-between p-6 outline outline-1 outline-subtle" className="group col-span-1 flex flex-row items-center justify-between p-6 outline outline-1 outline-subtle"
> >
<div>{t('shops.chubu')}</div> <div>{t('shops.chubu')}</div>
@ -51,7 +51,7 @@ export default function Shoplist() {
</div> </div>
</Link> </Link>
<Link <Link
href="shops/kinki" href="shop-list/#kinki"
className="group col-span-1 flex flex-row items-center justify-between p-6 outline outline-1 outline-subtle" className="group col-span-1 flex flex-row items-center justify-between p-6 outline outline-1 outline-subtle"
> >
<div>{t('shops.kinki')}</div> <div>{t('shops.kinki')}</div>
@ -63,7 +63,7 @@ export default function Shoplist() {
</div> </div>
</Link> </Link>
<Link <Link
href="shops/chugoku" href="shop-list/#chugoku"
className="group col-span-1 flex flex-row items-center justify-between p-6 outline outline-1 outline-subtle" className="group col-span-1 flex flex-row items-center justify-between p-6 outline outline-1 outline-subtle"
> >
<div>{t('shops.chugoku')}</div> <div>{t('shops.chugoku')}</div>
@ -75,7 +75,7 @@ export default function Shoplist() {
</div> </div>
</Link> </Link>
<Link <Link
href="shops/kyushu" href="shop-list/#kyushu"
className="group col-span-1 flex flex-row items-center justify-between p-6 outline outline-1 outline-subtle" className="group col-span-1 flex flex-row items-center justify-between p-6 outline outline-1 outline-subtle"
> >
<div>{t('shops.kyushu')}</div> <div>{t('shops.kyushu')}</div>

View File

@ -1,6 +1,7 @@
{ {
"Index": { "Index": {
"menu": { "menu": {
"title": "menu",
"products": "products", "products": "products",
"shops": "shop list", "shops": "shop list",
"about": "about narai", "about": "about narai",
@ -10,6 +11,13 @@
"company": "company", "company": "company",
"contact": "contact" "contact": "contact"
}, },
"shopping-guide": {
"title": "shopping guide",
"terms": "terms of use",
"legal": "legal notice",
"privacy": "privacy policy",
"contact": "contact"
},
"newsletter": { "newsletter": {
"title": "newsletter", "title": "newsletter",
"description": "Subscribe to our newsletter to receive free shipping on your first order, and access to exclusive information regarding events and pairing dinners.", "description": "Subscribe to our newsletter to receive free shipping on your first order, and access to exclusive information regarding events and pairing dinners.",

View File

@ -1,5 +1,5 @@
import { SupportedLocale } from 'components/layout/navbar/language-control';
import 'server-only'; import 'server-only';
import type { Locale } from '../i18n-config';
// We enumerate all dictionaries here for better linting and typescript support // We enumerate all dictionaries here for better linting and typescript support
// We also get the default import for cleaner types // We also get the default import for cleaner types
@ -8,5 +8,5 @@ const dictionaries = {
ja: () => import('./ja.json').then((module) => module.default) ja: () => import('./ja.json').then((module) => module.default)
}; };
export const getDictionary = async (locale: Locale) => export const getDictionary = async (locale: SupportedLocale) =>
dictionaries[locale]?.() ?? dictionaries.en(); dictionaries[locale]?.() ?? dictionaries.en();

View File

@ -1,6 +1,7 @@
{ {
"Index": { "Index": {
"menu": { "menu": {
"title": "menu",
"products": "商品", "products": "商品",
"shops": "取り扱い店", "shops": "取り扱い店",
"about": "naraiについて", "about": "naraiについて",
@ -10,6 +11,13 @@
"company": "会社概要", "company": "会社概要",
"contact": "contact" "contact": "contact"
}, },
"shopping-guide": {
"title": "shopping guide",
"terms": "利用規約",
"legal": "特定商取引法表記",
"privacy": "プライバシーポリシー",
"contact": "お問い合わせ"
},
"newsletter": { "newsletter": {
"title": "newsletter", "title": "newsletter",
"description": "ニュースレターにご登録いただくと、初回送料無料クーポン、購読者限定の情報やペアリングディナーなどのご案内をお送りさせていただきます。", "description": "ニュースレターにご登録いただくと、初回送料無料クーポン、購読者限定の情報やペアリングディナーなどのご案内をお送りさせていただきます。",

View File

@ -13,7 +13,7 @@ module.exports = {
fontFamily: { fontFamily: {
serif: ['var(--font-alpina)', 'serif'], serif: ['var(--font-alpina)', 'serif'],
title: ['var(--font-cinzel)', 'serif'], title: ['var(--font-cinzel)', 'serif'],
japan: ['var(--font-noto)', 'sans-serif'] japan: ['var(--font-noto)', 'serif']
}, },
aspectRatio: { aspectRatio: {
tall: '596 / 845' tall: '596 / 845'