diff --git a/app/[locale]/[page]/page.tsx b/app/[locale]/[page]/page.tsx index 28ae490b2..8d3820714 100644 --- a/app/[locale]/[page]/page.tsx +++ b/app/[locale]/[page]/page.tsx @@ -12,7 +12,7 @@ export const revalidate = 43200; // 12 hours in seconds export async function generateMetadata({ params }: { - params: { page: string; locale: SupportedLocale }; + params: { page: string; locale?: SupportedLocale }; }): Promise<Metadata> { const page = await getPage({ handle: params.page, language: params?.locale?.toUpperCase() }); @@ -32,7 +32,7 @@ export async function generateMetadata({ export default async function Page({ params }: { - params: { page: string; locale: SupportedLocale }; + params: { page: string; locale?: SupportedLocale }; }) { const page = await getPage({ handle: params.page, language: params?.locale?.toUpperCase() }); diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx index 72154a809..8d0736f5c 100644 --- a/app/[locale]/layout.tsx +++ b/app/[locale]/layout.tsx @@ -1,9 +1,9 @@ import Navbar from 'components/layout/navbar'; -import { Locale } from 'i18n-config'; -import { Noto_Sans_JP } from 'next/font/google'; +import { Noto_Serif_JP } from 'next/font/google'; import localFont from 'next/font/local'; import { ReactNode, Suspense } from 'react'; +import { SupportedLocale } from 'components/layout/navbar/language-control'; import { NextIntlClientProvider } from 'next-intl'; import { notFound } from 'next/navigation'; import './globals.css'; @@ -56,19 +56,13 @@ const alpina = localFont({ variable: '--font-alpina' }); -const noto = Noto_Sans_JP({ +const noto = Noto_Serif_JP({ subsets: ['latin'], display: 'swap', weight: ['300', '600'], variable: '--font-noto' }); -const mincho = localFont({ - src: '../fonts/A-OTF-A1MinchoStd-Bold.otf', - display: 'swap', - variable: '--font-mincho' -}); - export function generateStaticParams() { return [{ locale: 'en' }, { locale: 'ja' }]; } @@ -78,7 +72,7 @@ export default async function RootLayout({ params }: { children: ReactNode; - params: { locale: Locale }; + params: { locale?: SupportedLocale }; }) { let messages; try { diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index d30156ede..81f4d5f0f 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -25,7 +25,7 @@ export const metadata = { export default async function HomePage({ params: { locale } }: { - params: { locale: SupportedLocale }; + params: { locale?: SupportedLocale }; }) { return ( <> diff --git a/app/[locale]/shops/[page]/ShopsTitle.tsx b/app/[locale]/shop-list/ShopsTitle.tsx similarity index 100% rename from app/[locale]/shops/[page]/ShopsTitle.tsx rename to app/[locale]/shop-list/ShopsTitle.tsx diff --git a/app/[locale]/shops/[page]/layout.tsx b/app/[locale]/shop-list/layout.tsx similarity index 100% rename from app/[locale]/shops/[page]/layout.tsx rename to app/[locale]/shop-list/layout.tsx diff --git a/app/[locale]/shops/[page]/opengraph-image.tsx b/app/[locale]/shop-list/opengraph-image.tsx similarity index 100% rename from app/[locale]/shops/[page]/opengraph-image.tsx rename to app/[locale]/shop-list/opengraph-image.tsx diff --git a/app/[locale]/shops/[page]/page.tsx b/app/[locale]/shop-list/page.tsx similarity index 71% rename from app/[locale]/shops/[page]/page.tsx rename to app/[locale]/shop-list/page.tsx index 993f57965..99d7c8f2d 100644 --- a/app/[locale]/shops/[page]/page.tsx +++ b/app/[locale]/shop-list/page.tsx @@ -1,5 +1,6 @@ import type { Metadata } from 'next'; +import LogoNamemark from 'components/icons/namemark'; import { SupportedLocale } from 'components/layout/navbar/language-control'; import Prose from 'components/prose'; import { getPage } from 'lib/shopify'; @@ -13,9 +14,9 @@ export const revalidate = 43200; // 12 hours in seconds export async function generateMetadata({ params }: { - params: { page: string; locale: SupportedLocale }; + params: { locale?: SupportedLocale }; }): 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(); @@ -30,17 +31,16 @@ export async function generateMetadata({ }; } -export default async function Page({ - params -}: { - params: { page: string; locale: SupportedLocale }; -}) { - const page = await getPage({ handle: params.page, language: params?.locale?.toUpperCase() }); +export default async function Page({ params }: { params: { locale?: SupportedLocale } }) { + const page = await getPage({ handle: 'shop-list', language: params?.locale?.toUpperCase() }); if (!page) return notFound(); return ( <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 /> <h2 className="mb-8 text-3xl font-medium">{page.title}</h2> <Prose className="mb-8" html={page.body as string} /> diff --git a/components/grid/three-items.tsx b/components/grid/three-items.tsx index 20492d1e5..ba4c02fa5 100644 --- a/components/grid/three-items.tsx +++ b/components/grid/three-items.tsx @@ -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. const homepageItems = await getCollectionProducts({ collection: 'hidden-homepage-featured-items', diff --git a/components/layout/footer-menu.tsx b/components/layout/footer-menu.tsx index 4f6387edd..e9f697165 100644 --- a/components/layout/footer-menu.tsx +++ b/components/layout/footer-menu.tsx @@ -1,46 +1,88 @@ 'use client'; -import clsx from 'clsx'; -import { Menu } from 'lib/shopify/types'; +import { useTranslations } from 'next-intl'; import Link from 'next/link'; -import { usePathname } from 'next/navigation'; -import { useEffect, useState } from 'react'; -const FooterMenuItem = ({ item }: { item: Menu }) => { - const pathname = usePathname(); - const [active, setActive] = useState(pathname === item.path); - - useEffect(() => { - setActive(pathname === item.path); - }, [pathname, item.path]); +export default function FooterMenu() { + const t = useTranslations('Index'); return ( - <li> - <Link - href={item.path} - className={clsx( - 'block p-2 text-lg underline-offset-4 hover:text-black hover:underline dark:hover:text-neutral-300 md:inline-block md:text-sm', - { - 'text-black dark:text-neutral-300': active - } - )} - > - {item.title} - </Link> - </li> - ); -}; + <div className="hidden md:grid md:w-full md:grid-cols-2"> + <div className="col-span-1"> + <div className="mb-4 font-serif text-base underline">{t('menu.title')}</div> + <nav className="font-multilingual flex flex-col space-y-2 text-left text-base font-normal"> + <div> + <Link href="/products" className="transition-opacity duration-150 hover:opacity-50"> + {t('menu.products')} + </Link> + </div> -export default function FooterMenu({ menu }: { menu: Menu[] }) { - if (!menu.length) return null; + <div> + <Link href="/shop-list" className="transition-opacity duration-150 hover:opacity-50"> + {t('menu.shops')} + </Link> + </div> - return ( - <nav> - <ul> - {menu.map((item: Menu) => { - return <FooterMenuItem key={item.title} item={item} />; - })} - </ul> - </nav> + <div> + <Link href="/about" className="transition-opacity duration-150 hover:opacity-50"> + {t('menu.about')} + </Link> + </div> + + <div> + <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> ); } diff --git a/components/layout/footer.tsx b/components/layout/footer.tsx index 489250f7c..2759a93c8 100644 --- a/components/layout/footer.tsx +++ b/components/layout/footer.tsx @@ -1,4 +1,5 @@ -import { getMenu } from 'lib/shopify'; +import clsx from 'clsx'; +import FooterMenu from './footer-menu'; import NewsletterFooter from './newsletter-footer'; const { COMPANY_NAME, SITE_NAME } = process.env; @@ -6,16 +7,24 @@ const { COMPANY_NAME, SITE_NAME } = process.env; 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 copyrightName = COMPANY_NAME || SITE_NAME || ''; return ( - <footer className="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 className="flex flex-row justify-between"> + <footer className="px-6 text-sm"> + <div + 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 /> </div> + <div className="hidden md:block md:w-1/3"> + <FooterMenu /> + </div> </div> <div> <div className="mx-auto flex w-full max-w-7xl flex-col items-center gap-1 pb-12 md:flex-row"> diff --git a/components/layout/menu/modal.tsx b/components/layout/menu/modal.tsx index bee33646c..b84f50b2c 100644 --- a/components/layout/menu/modal.tsx +++ b/components/layout/menu/modal.tsx @@ -68,7 +68,7 @@ export function MenuModal() { <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-col space-y-4 px-6 text-right"> + <nav className="flex flex-col space-y-4 px-6 text-right"> <div> <Link href="/products" @@ -80,7 +80,7 @@ export function MenuModal() { <div> <Link - href="/shops" + href="/shop-list" className="font-serif text-4xl font-normal transition-opacity duration-150 hover:opacity-50" > {t('menu.shops')} @@ -140,7 +140,7 @@ export function MenuModal() { {t('menu.contact')} </Link> </div> - </div> + </nav> </div> </div> </Dialog.Panel> diff --git a/components/layout/navbar/language-control.tsx b/components/layout/navbar/language-control.tsx index 0391c9de3..13457f763 100644 --- a/components/layout/navbar/language-control.tsx +++ b/components/layout/navbar/language-control.tsx @@ -4,7 +4,7 @@ import clsx from 'clsx'; import Link from 'next-intl/link'; 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> { const index = arr.indexOf(value); diff --git a/components/layout/newsletter-footer.tsx b/components/layout/newsletter-footer.tsx index c2ff42c2f..dd5a0ec1b 100644 --- a/components/layout/newsletter-footer.tsx +++ b/components/layout/newsletter-footer.tsx @@ -6,13 +6,13 @@ export default function NewsletterSignup() { const t = useTranslations('Index'); return ( - <div> - <div className="flex flex-row items-baseline justify-between space-x-6"> + <div className="max-w-xl"> + <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> <div className="font-multilingual">{t('footer.newsletter.promo')}</div> </div> <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}&id=${process?.env?.NEXT_PUBLIC_MAILCHIMP_LIST_ID}`} method="post" name="mc-embedded-subscribe-form" diff --git a/components/layout/shoplist.tsx b/components/layout/shoplist.tsx index f04b24b9f..4164ef03e 100644 --- a/components/layout/shoplist.tsx +++ b/components/layout/shoplist.tsx @@ -15,7 +15,7 @@ export default function Shoplist() { </div> <div className="grid w-full grid-cols-2 gap-px"> <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" > <div>{t('shops.hokkaido')}</div> @@ -27,7 +27,7 @@ export default function Shoplist() { </div> </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" > <div>{t('shops.kanto')}</div> @@ -39,7 +39,7 @@ export default function Shoplist() { </div> </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" > <div>{t('shops.chubu')}</div> @@ -51,7 +51,7 @@ export default function Shoplist() { </div> </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" > <div>{t('shops.kinki')}</div> @@ -63,7 +63,7 @@ export default function Shoplist() { </div> </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" > <div>{t('shops.chugoku')}</div> @@ -75,7 +75,7 @@ export default function Shoplist() { </div> </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" > <div>{t('shops.kyushu')}</div> diff --git a/messages/en.json b/messages/en.json index 0a471d529..8b53bda98 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1,6 +1,7 @@ { "Index": { "menu": { + "title": "menu", "products": "products", "shops": "shop list", "about": "about narai", @@ -10,6 +11,13 @@ "company": "company", "contact": "contact" }, + "shopping-guide": { + "title": "shopping guide", + "terms": "terms of use", + "legal": "legal notice", + "privacy": "privacy policy", + "contact": "contact" + }, "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.", diff --git a/messages/index.ts b/messages/index.ts index 9644bd93f..c30991df6 100644 --- a/messages/index.ts +++ b/messages/index.ts @@ -1,5 +1,5 @@ +import { SupportedLocale } from 'components/layout/navbar/language-control'; import 'server-only'; -import type { Locale } from '../i18n-config'; // We enumerate all dictionaries here for better linting and typescript support // We also get the default import for cleaner types @@ -8,5 +8,5 @@ const dictionaries = { ja: () => import('./ja.json').then((module) => module.default) }; -export const getDictionary = async (locale: Locale) => +export const getDictionary = async (locale: SupportedLocale) => dictionaries[locale]?.() ?? dictionaries.en(); diff --git a/messages/ja.json b/messages/ja.json index d1f23d494..d3d71c3c3 100644 --- a/messages/ja.json +++ b/messages/ja.json @@ -1,6 +1,7 @@ { "Index": { "menu": { + "title": "menu", "products": "商品", "shops": "取り扱い店", "about": "naraiについて", @@ -10,6 +11,13 @@ "company": "会社概要", "contact": "contact" }, + "shopping-guide": { + "title": "shopping guide", + "terms": "利用規約", + "legal": "特定商取引法表記", + "privacy": "プライバシーポリシー", + "contact": "お問い合わせ" + }, "newsletter": { "title": "newsletter", "description": "ニュースレターにご登録いただくと、初回送料無料クーポン、購読者限定の情報やペアリングディナーなどのご案内をお送りさせていただきます。", diff --git a/tailwind.config.js b/tailwind.config.js index 82f5675a3..dd00f7c64 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -13,7 +13,7 @@ module.exports = { fontFamily: { serif: ['var(--font-alpina)', 'serif'], title: ['var(--font-cinzel)', 'serif'], - japan: ['var(--font-noto)', 'sans-serif'] + japan: ['var(--font-noto)', 'serif'] }, aspectRatio: { tall: '596 / 845'