Merge pull request #6 from zenzen-sol/sol/preview
chore: Merge preview -> narai
BIN
.yarn/cache/b4a-npm-1.6.4-080bcba845-81b086f9af.zip
vendored
Normal file
BIN
.yarn/cache/base64-js-npm-1.5.1-b2f7275641-669632eb37.zip
vendored
Normal file
BIN
.yarn/cache/bl-npm-4.1.0-7f94cdcf3f-9e8521fa7e.zip
vendored
Normal file
BIN
.yarn/cache/buffer-npm-5.7.1-513ef8259e-e2cf8429e1.zip
vendored
Normal file
BIN
.yarn/cache/chownr-npm-1.1.4-5bd400ab08-115648f8eb.zip
vendored
Normal file
BIN
.yarn/cache/color-npm-4.2.3-4a23227581-0579629c02.zip
vendored
Normal file
BIN
.yarn/cache/color-string-npm-1.9.1-dc020e56be-c13fe7cff7.zip
vendored
Normal file
BIN
.yarn/cache/decompress-response-npm-6.0.0-359de2878c-d377cf47e0.zip
vendored
Normal file
BIN
.yarn/cache/deep-extend-npm-0.6.0-e182924219-7be7e5a8d4.zip
vendored
Normal file
BIN
.yarn/cache/detect-libc-npm-2.0.2-03afa59137-2b2cd3649b.zip
vendored
Normal file
BIN
.yarn/cache/end-of-stream-npm-1.4.4-497fc6dee1-530a5a5a1e.zip
vendored
Normal file
BIN
.yarn/cache/expand-template-npm-2.0.3-80de959306-588c198472.zip
vendored
Normal file
BIN
.yarn/cache/fast-fifo-npm-1.3.2-391cc25df4-6bfcba3e4d.zip
vendored
Normal file
BIN
.yarn/cache/fs-constants-npm-1.0.0-59576b2177-18f5b71837.zip
vendored
Normal file
BIN
.yarn/cache/github-from-package-npm-0.0.0-519f80c9a1-14e448192a.zip
vendored
Normal file
BIN
.yarn/cache/ieee754-npm-1.2.1-fb63b3caeb-5144c0c981.zip
vendored
Normal file
BIN
.yarn/cache/ini-npm-1.3.8-fb5040b4c0-dfd98b0ca3.zip
vendored
Normal file
BIN
.yarn/cache/is-arrayish-npm-0.3.2-f856180f79-977e64f54d.zip
vendored
Normal file
BIN
.yarn/cache/mimic-response-npm-3.1.0-a4a24b4e96-25739fee32.zip
vendored
Normal file
BIN
.yarn/cache/mkdirp-classic-npm-0.5.3-3b5c991910-3f4e088208.zip
vendored
Normal file
BIN
.yarn/cache/napi-build-utils-npm-1.0.2-892e4bba56-06c14271ee.zip
vendored
Normal file
BIN
.yarn/cache/node-abi-npm-3.47.0-9d2a86a633-ff8498dcd4.zip
vendored
Normal file
BIN
.yarn/cache/node-addon-api-npm-6.1.0-634c545b39-3a539510e6.zip
vendored
Normal file
BIN
.yarn/cache/prebuild-install-npm-7.1.1-cb9fc9d341-dbf96d0146.zip
vendored
Normal file
BIN
.yarn/cache/pump-npm-3.0.0-0080bf6a7a-e42e9229fb.zip
vendored
Normal file
BIN
.yarn/cache/queue-tick-npm-1.0.1-10bd6eaf3d-57c3292814.zip
vendored
Normal file
BIN
.yarn/cache/rc-npm-1.2.8-d6768ac936-2e26e052f8.zip
vendored
Normal file
BIN
.yarn/cache/sharp-npm-0.32.5-f82a9875e0-3cd6dc037c.zip
vendored
Normal file
BIN
.yarn/cache/simple-concat-npm-1.0.1-48df70de29-4d211042cc.zip
vendored
Normal file
BIN
.yarn/cache/simple-get-npm-4.0.1-fa2a97645d-e4132fd27c.zip
vendored
Normal file
BIN
.yarn/cache/simple-swizzle-npm-0.2.2-8dee37fad1-a7f3f2ab5c.zip
vendored
Normal file
BIN
.yarn/cache/streamx-npm-2.15.1-d5dbdeeb03-6f2b4fed68.zip
vendored
Normal file
BIN
.yarn/cache/strip-json-comments-npm-2.0.1-e7883b2d04-1074ccb632.zip
vendored
Normal file
BIN
.yarn/cache/tar-fs-npm-2.1.1-e374d3b7a2-f5b9a70059.zip
vendored
Normal file
BIN
.yarn/cache/tar-fs-npm-3.0.4-fa3da2e321-dcf4054f9e.zip
vendored
Normal file
BIN
.yarn/cache/tar-stream-npm-2.2.0-884c79b510-699831a8b9.zip
vendored
Normal file
BIN
.yarn/cache/tar-stream-npm-3.1.6-ce3ac17e49-f3627f9185.zip
vendored
Normal file
BIN
.yarn/cache/tunnel-agent-npm-0.6.0-64345ab7eb-05f6510358.zip
vendored
Normal file
@ -112,6 +112,9 @@ export default function AboutNaraiDetail({ awards }: { awards: string }) {
|
||||
<h2 className="max-w-title text-3xl leading-normal">
|
||||
{t('about.materials.water.title')}
|
||||
</h2>
|
||||
<h2 className="max-w-title text-3xl leading-normal">
|
||||
{t('about.materials.water.subtitle')}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="md:w-1/2">
|
||||
<p className="text-base leading-relaxed">{t('about.materials.water.body')}</p>
|
||||
@ -132,6 +135,9 @@ export default function AboutNaraiDetail({ awards }: { awards: string }) {
|
||||
<h2 className="max-w-title text-3xl leading-normal">
|
||||
{t('about.materials.rice.title')}
|
||||
</h2>
|
||||
<h2 className="max-w-title text-3xl leading-normal">
|
||||
{t('about.materials.rice.subtitle')}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="md:w-1/2">
|
||||
<p className="text-base leading-relaxed">{t('about.materials.rice.body')}</p>
|
||||
@ -152,6 +158,9 @@ export default function AboutNaraiDetail({ awards }: { awards: string }) {
|
||||
<h2 className="max-w-title text-3xl leading-normal">
|
||||
{t('about.materials.koji.title')}
|
||||
</h2>
|
||||
<h2 className="max-w-title text-3xl leading-normal">
|
||||
{t('about.materials.koji.subtitle')}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="md:w-1/2">
|
||||
<p className="text-base leading-relaxed">{t('about.materials.koji.body')}</p>
|
||||
@ -165,7 +174,7 @@ export default function AboutNaraiDetail({ awards }: { awards: string }) {
|
||||
<div className="border-t border-white/20"></div>
|
||||
</div>
|
||||
<div className="font-multilingual max-w-xl font-extralight">
|
||||
<p className="text-lg">{t('about.irie.title')}</p>
|
||||
<p className="pb-4 text-xl">{t('about.irie.title')}</p>
|
||||
<p className="text-lg leading-relaxed">{t('about.irie.body')}</p>
|
||||
</div>
|
||||
|
||||
|
@ -2,7 +2,8 @@ import Footer from 'components/layout/footer';
|
||||
import { SupportedLocale } from 'components/layout/navbar/language-control';
|
||||
|
||||
import Navbar from 'components/layout/navbar';
|
||||
import { getCart, getPage } from 'lib/shopify';
|
||||
import { getCart, getPage, getProduct } from 'lib/shopify';
|
||||
import { Product } from 'lib/shopify/types';
|
||||
import { cookies } from 'next/headers';
|
||||
import { Suspense } from 'react';
|
||||
import AboutNaraiDetail from './about-narai-detail';
|
||||
@ -26,11 +27,16 @@ export default async function Page({ params }: { params: { locale?: SupportedLoc
|
||||
cart = await getCart(cartId);
|
||||
}
|
||||
|
||||
const promotedItem: Product | undefined = await getProduct({
|
||||
handle: 'gift-bag-and-postcard-set',
|
||||
language: params?.locale?.toUpperCase()
|
||||
});
|
||||
|
||||
const awardsPage = await getPage({ handle: 'awards', language: params?.locale?.toUpperCase() });
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar cart={cart} locale={params?.locale} compact />
|
||||
<Navbar cart={cart} locale={params?.locale} compact promotedItem={promotedItem} />
|
||||
<div className="pt-24 md:pt-32">
|
||||
<AboutNaraiDetail awards={awardsPage.body} />
|
||||
</div>
|
||||
|
@ -2,7 +2,8 @@ import Footer from 'components/layout/footer';
|
||||
import { SupportedLocale } from 'components/layout/navbar/language-control';
|
||||
|
||||
import Navbar from 'components/layout/navbar';
|
||||
import { getCart } from 'lib/shopify';
|
||||
import { getCart, getProduct } from 'lib/shopify';
|
||||
import { Product } from 'lib/shopify/types';
|
||||
import { cookies } from 'next/headers';
|
||||
import { Suspense } from 'react';
|
||||
import SagyobarDetail from './sagyobar-detail';
|
||||
@ -28,9 +29,14 @@ export default async function Page({ params }: { params: { locale?: SupportedLoc
|
||||
cart = await getCart(cartId);
|
||||
}
|
||||
|
||||
const promotedItem: Product | undefined = await getProduct({
|
||||
handle: 'gift-bag-and-postcard-set',
|
||||
language: params?.locale?.toUpperCase()
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar cart={cart} locale={params?.locale} compact />
|
||||
<Navbar cart={cart} locale={params?.locale} compact promotedItem={promotedItem} />
|
||||
<div className="pt-12">
|
||||
<SagyobarDetail />
|
||||
</div>
|
||||
|
@ -62,7 +62,9 @@ export default function SagyobarDetail() {
|
||||
|
||||
<div className="flex-row justify-end md:flex">
|
||||
<div className="md:md:w-1/2">
|
||||
<p className="text-base leading-loose">{t('bar.002')}</p>
|
||||
<p className="font-multilingual text-base font-extralight leading-loose">
|
||||
{t('bar.002')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -97,11 +99,11 @@ export default function SagyobarDetail() {
|
||||
<p className="text-xl">{t('bar.access.title')}</p>
|
||||
</div>
|
||||
<p className="text-[15px] leading-relaxed">{t('bar.access.para001')}</p>
|
||||
<p className="text-[15px] leading-relaxed">{t('bar.access.para002')}</p>
|
||||
<p className="pt-6 text-[15px] leading-relaxed">{t('bar.access.para002')}</p>
|
||||
<p className="text-[15px] leading-relaxed">{t('bar.access.para003')}</p>
|
||||
<p className="text-[15px] leading-relaxed">{t('bar.access.para004')}</p>
|
||||
<p className="text-[15px] leading-relaxed">{t('bar.access.para005')}</p>
|
||||
<p className="text-[15px] leading-relaxed">{t('bar.access.para006')}</p>
|
||||
<p className="pt-6 text-[15px] leading-relaxed">{t('bar.access.para006')}</p>
|
||||
<p className="text-[15px] leading-relaxed">{t('bar.access.para007')}</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -127,7 +129,9 @@ export default function SagyobarDetail() {
|
||||
<p className="pb-6 text-[15px] leading-relaxed">
|
||||
<Link
|
||||
href="https://cdn.shopify.com/s/files/1/0578/5570/5261/files/sagyobar_menu.pdf?v=1692861259"
|
||||
className="transition-opacity duration-150 hover:opacity-90"
|
||||
className="underline transition-opacity duration-150 hover:opacity-60"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{t('bar.menu.para001')}
|
||||
</Link>
|
||||
@ -140,8 +144,9 @@ export default function SagyobarDetail() {
|
||||
<div className="border-t border-white/20"></div>
|
||||
</div>
|
||||
<div className="font-multilingual max-w-xl font-extralight">
|
||||
<p className="text-[15px]">{t('bar.clerk.title')}</p>
|
||||
<p className="text-[15px] leading-relaxed">{t('bar.clerk.body')}</p>
|
||||
<p className="pb-4 text-xl">{t('bar.clerk.title')}</p>
|
||||
<p className="text-[15px] leading-relaxed">{t('bar.clerk.001')}</p>
|
||||
<p className="text-[15px] leading-relaxed">{t('bar.clerk.002')}</p>
|
||||
</div>
|
||||
|
||||
<Image
|
||||
|
@ -1,7 +1,12 @@
|
||||
'use client';
|
||||
import CompanyImage001 from '@images/company-images/irie.webp';
|
||||
import CompanyImage002 from '@images/company-images/kou.webp';
|
||||
import CompanyImage003 from '@images/company-images/yamano.webp';
|
||||
import AlisaImage from '@images/company-images/sb_profile_photo_alisa.webp';
|
||||
import IrieImage from '@images/company-images/sb_profile_photo_irie.webp';
|
||||
import SundbergImage from '@images/company-images/sb_profile_photo_kou.webp';
|
||||
import MasaImage from '@images/company-images/sb_profile_photo_masa.webp';
|
||||
import NishikawaImage from '@images/company-images/sb_profile_photo_nishikawa.webp';
|
||||
import ShinyaImage from '@images/company-images/sb_profile_photo_shinya.webp';
|
||||
import TakaImage from '@images/company-images/sb_profile_photo_taka.webp';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import VideoPlayer from 'components/video/video-player';
|
||||
import { useTranslations } from 'next-intl';
|
||||
@ -26,7 +31,9 @@ export default function CompanyDetail() {
|
||||
'mx-auto max-w-4xl'
|
||||
)}
|
||||
>
|
||||
<h2 className="mx-auto max-w-sm text-center text-4xl md:text-4xl">{t('company.title')}</h2>
|
||||
<h2 className="font-multilingual mx-auto max-w-sm text-center text-4xl md:text-4xl">
|
||||
{t('company.title')}
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<h3 className="text-2xl">{t('company.subtitle001')}</h3>
|
||||
@ -70,83 +77,101 @@ export default function CompanyDetail() {
|
||||
<div className="font-multilingual col-span-1 py-4 text-base font-extralight">
|
||||
<div className="relative aspect-square">
|
||||
<Image
|
||||
src={CompanyImage001}
|
||||
src={IrieImage}
|
||||
priority={true}
|
||||
alt="A picture of Irie Masayuki."
|
||||
className={clsx('h-full w-full object-cover')}
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-2">{t('company.irie.japanese')}</div>
|
||||
<div className="pt-2 font-japan font-extralight">{t('company.irie.japanese')}</div>
|
||||
<div className="pb-4">{t('company.irie.english')}</div>
|
||||
<div className="text-sm">{t('company.irie.role')}</div>
|
||||
</div>
|
||||
|
||||
<div className="font-multilingual col-span-1 py-4 text-lg font-extralight">
|
||||
<div className="font-multilingual col-span-1 py-4 text-base font-extralight">
|
||||
<div className="relative aspect-square">
|
||||
<Image
|
||||
src={CompanyImage001}
|
||||
src={NishikawaImage}
|
||||
priority={true}
|
||||
alt="A picture of Masataka Nishikawa."
|
||||
className={clsx('h-full w-full object-cover')}
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-2">{t('company.nishikawa.japanese')}</div>
|
||||
<div className="pt-2 font-japan font-extralight">
|
||||
{t('company.nishikawa.japanese')}
|
||||
</div>
|
||||
<div className="pb-4">{t('company.nishikawa.english')}</div>
|
||||
<div className="text-sm">{t('company.nishikawa.role')}</div>
|
||||
</div>
|
||||
|
||||
<div className="font-multilingual col-span-1 py-4 text-lg font-extralight">
|
||||
<div className="font-multilingual col-span-1 py-4 text-base font-extralight">
|
||||
<div className="relative aspect-square">
|
||||
<Image
|
||||
src={CompanyImage002}
|
||||
src={SundbergImage}
|
||||
priority={true}
|
||||
alt="A picture of Kou Sundberg."
|
||||
className={clsx('h-full w-full object-cover')}
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-2">{t('company.sundberg.japanese')}</div>
|
||||
<div className="pt-2 font-japan font-extralight">
|
||||
{t('company.sundberg.japanese')}
|
||||
</div>
|
||||
<div className="pb-4">{t('company.sundberg.english')}</div>
|
||||
<div className="text-sm">{t('company.sundberg.role')}</div>
|
||||
</div>
|
||||
|
||||
<div className="font-multilingual col-span-1 py-4 text-lg font-extralight">
|
||||
<div className="font-multilingual col-span-1 py-4 text-base font-extralight">
|
||||
<div className="relative aspect-square">
|
||||
<Image
|
||||
src={CompanyImage003}
|
||||
src={MasaImage}
|
||||
priority={true}
|
||||
alt="A picture of Masanori Komatsu."
|
||||
className={clsx('h-full w-full object-cover')}
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-2 font-japan font-extralight">{t('company.masa.japanese')}</div>
|
||||
<div className="pb-4">{t('company.masa.english')}</div>
|
||||
<div className="text-sm">{t('company.masa.role')}</div>
|
||||
</div>
|
||||
|
||||
<div className="font-multilingual col-span-1 py-4 text-base font-extralight">
|
||||
<div className="relative aspect-square">
|
||||
<Image
|
||||
src={TakaImage}
|
||||
priority={true}
|
||||
alt="A picture of Takatoshi Yamano."
|
||||
className={clsx('h-full w-full object-cover')}
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-2">{t('company.yamano.japanese')}</div>
|
||||
<div className="pt-2 font-japan font-extralight">{t('company.yamano.japanese')}</div>
|
||||
<div className="pb-4">{t('company.yamano.english')}</div>
|
||||
<div className="text-sm">{t('company.yamano.role')}</div>
|
||||
</div>
|
||||
|
||||
<div className="font-multilingual col-span-1 py-4 text-lg font-extralight">
|
||||
<div className="font-multilingual col-span-1 py-4 text-base font-extralight">
|
||||
<div className="relative aspect-square">
|
||||
<Image
|
||||
src={CompanyImage001}
|
||||
src={AlisaImage}
|
||||
priority={true}
|
||||
alt="A picture of Alisa Yoshida."
|
||||
className={clsx('h-full w-full object-cover')}
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-2">{t('company.yoshida.japanese')}</div>
|
||||
<div className="pt-2 font-japan font-extralight">{t('company.yoshida.japanese')}</div>
|
||||
<div className="pb-4">{t('company.yoshida.english')}</div>
|
||||
<div className="text-sm">{t('company.yoshida.role')}</div>
|
||||
</div>
|
||||
|
||||
<div className="font-multilingual col-span-1 py-4 text-lg font-extralight">
|
||||
<div className="font-multilingual col-span-1 py-4 text-base font-extralight">
|
||||
<div className="relative aspect-square">
|
||||
<Image
|
||||
src={CompanyImage001}
|
||||
src={ShinyaImage}
|
||||
priority={true}
|
||||
alt="A picture of Shinya Ikegaya."
|
||||
className={clsx('h-full w-full object-cover')}
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-2">{t('company.ikegaya.japanese')}</div>
|
||||
<div className="pt-2 font-japan font-extralight">{t('company.ikegaya.japanese')}</div>
|
||||
<div className="pb-4">{t('company.ikegaya.english')}</div>
|
||||
<div className="text-sm">{t('company.ikegaya.role')}</div>
|
||||
</div>
|
||||
|
@ -2,7 +2,8 @@ import Footer from 'components/layout/footer';
|
||||
import { SupportedLocale } from 'components/layout/navbar/language-control';
|
||||
|
||||
import Navbar from 'components/layout/navbar';
|
||||
import { getCart } from 'lib/shopify';
|
||||
import { getCart, getProduct } from 'lib/shopify';
|
||||
import { Product } from 'lib/shopify/types';
|
||||
import { cookies } from 'next/headers';
|
||||
import { Suspense } from 'react';
|
||||
import CompanyDetail from './company-detail';
|
||||
@ -28,9 +29,14 @@ export default async function Page({ params }: { params: { locale?: SupportedLoc
|
||||
cart = await getCart(cartId);
|
||||
}
|
||||
|
||||
const promotedItem: Product | undefined = await getProduct({
|
||||
handle: 'gift-bag-and-postcard-set',
|
||||
language: params?.locale?.toUpperCase()
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar cart={cart} locale={params?.locale} compact />
|
||||
<Navbar cart={cart} locale={params?.locale} compact promotedItem={promotedItem} />
|
||||
<div className="pt-12">
|
||||
<CompanyDetail />
|
||||
</div>
|
||||
|
@ -28,22 +28,25 @@ export default function ConceptDetail() {
|
||||
)}
|
||||
>
|
||||
<div className="md:w-1/2">
|
||||
<div className="flex flex-col space-y-6">
|
||||
<h2 className="max-w-sm pb-12 text-4xl md:text-5xl">{t('concept.title')}</h2>
|
||||
<p className="font-multilingual text-base font-extralight">
|
||||
{t('concept.para001')} {t('concept.para002')} {t('concept.para003')}
|
||||
<span>{t('concept.para001')}</span> <span>{t('concept.para002')}</span>{' '}
|
||||
<span>{t('concept.para003')}</span>
|
||||
</p>
|
||||
<div className="font-multilingual pt-24 font-extralight">
|
||||
<div className="font-multilingual pt-6 font-extralight">
|
||||
<p className="pb-6 text-xl font-normal">{t('concept.subtitle001')}</p>
|
||||
<p className="pb-24 text-base leading-relaxed">{t('concept.para004')}</p>
|
||||
<p className="pb-6 text-xl font-normal">{t('concept.subtitle002')}</p>
|
||||
<p className="pb-12 text-base leading-relaxed">{t('concept.para004')}</p>
|
||||
<p className="pb-4 text-base leading-relaxed">{t('concept.para005')}</p>
|
||||
<p className="pb-4 text-base leading-relaxed">{t('concept.para006')}</p>
|
||||
<p className="pb-4 text-base leading-relaxed">
|
||||
{t('concept.para006')} {t('concept.para007')} {t('concept.para008')}
|
||||
{t('concept.para007')} {t('concept.para008')}
|
||||
</p>
|
||||
<p className="pb-4 text-base leading-relaxed">{t('concept.para009')}</p>
|
||||
<p className="text-base leading-relaxed">{t('concept.para010')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row items-start justify-end md:w-1/2">
|
||||
<div className="pb-24">
|
||||
<Logo className="h-32 w-48" />
|
||||
|
@ -2,7 +2,8 @@ import Footer from 'components/layout/footer';
|
||||
import { SupportedLocale } from 'components/layout/navbar/language-control';
|
||||
|
||||
import Navbar from 'components/layout/navbar';
|
||||
import { getCart } from 'lib/shopify';
|
||||
import { getCart, getProduct } from 'lib/shopify';
|
||||
import { Product } from 'lib/shopify/types';
|
||||
import { cookies } from 'next/headers';
|
||||
import { Suspense } from 'react';
|
||||
import ConceptDetail from './concept-detail';
|
||||
@ -28,9 +29,14 @@ export default async function Page({ params }: { params: { locale?: SupportedLoc
|
||||
cart = await getCart(cartId);
|
||||
}
|
||||
|
||||
const promotedItem: Product | undefined = await getProduct({
|
||||
handle: 'gift-bag-and-postcard-set',
|
||||
language: params?.locale?.toUpperCase()
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar cart={cart} locale={params?.locale} compact />
|
||||
<Navbar cart={cart} locale={params?.locale} compact promotedItem={promotedItem} />
|
||||
<div className="pt-12">
|
||||
<ConceptDetail />
|
||||
</div>
|
||||
|
@ -9,101 +9,131 @@ export default function Disclosures() {
|
||||
<>
|
||||
<div className="mx-auto font-serif">
|
||||
<h4 className="mx-auto max-w-4xl text-2xl font-extrabold tracking-tight sm:text-3xl">
|
||||
{t('disclosurePage.title')}
|
||||
{t('disclosure-page.title')}
|
||||
</h4>
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<div className="font-multilingual my-12">
|
||||
<section className="font-multilingual mb-36 mt-12 flex flex-col space-y-6 text-base md:space-y-3">
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:space-x-3 md:space-y-0">
|
||||
<div className="md:w-1/3">{t('disclosurePage.distributor.label')}</div>
|
||||
<div className="md:w-1/3">{t('disclosure-page.distributor.label')}</div>
|
||||
<div className="md:w-2/3">
|
||||
<div>{t('disclosurePage.distributor.value')}</div>
|
||||
<div>{t('disclosure-page.distributor.value')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:space-x-3 md:space-y-0">
|
||||
<div className="md:w-1/3">{t('disclosurePage.representative.label')}</div>
|
||||
<div className="md:w-1/3">{t('disclosure-page.representative.label')}</div>
|
||||
<div className="md:w-2/3">
|
||||
<div>{t('disclosurePage.representative.value')}</div>
|
||||
<div>{t('disclosure-page.representative.value')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:space-x-3 md:space-y-0">
|
||||
<div className="md:w-1/3">{t('disclosurePage.address.label')}</div>
|
||||
<div className="md:w-1/3">{t('disclosure-page.address.label')}</div>
|
||||
<div className="md:w-2/3">
|
||||
<div>{t('disclosurePage.address.one')}</div>
|
||||
<div>{t('disclosurePage.address.two')}</div>
|
||||
<div>{t('disclosure-page.address.one')}</div>
|
||||
<div>{t('disclosure-page.address.two')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:space-x-3 md:space-y-0">
|
||||
<div className="md:w-1/3">{t('disclosurePage.phone.label')}</div>
|
||||
<div className="md:w-1/3">{t('disclosure-page.phone.label')}</div>
|
||||
<div className="md:w-2/3">
|
||||
<div>{t('disclosurePage.phone.value')}</div>
|
||||
<div>{t('disclosure-page.phone.value')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:space-x-3 md:space-y-0">
|
||||
<div className="md:w-1/3">{t('disclosurePage.email.label')}</div>
|
||||
<div className="md:w-1/3">{t('disclosure-page.email.label')}</div>
|
||||
<div className="md:w-2/3">
|
||||
<Link
|
||||
href={`mailto:${t('disclosurePage.homepage.value')}`}
|
||||
href={`mailto:${t('disclosure-page.homepage.value')}`}
|
||||
className="transition-opacity duration-150 hover:opacity-90"
|
||||
>
|
||||
<div>{t('disclosurePage.email.value')}</div>
|
||||
<div>{t('disclosure-page.email.value')}</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:space-x-3 md:space-y-0">
|
||||
<div className="md:w-1/3">
|
||||
<div>{t('disclosurePage.homepage.label')}</div>
|
||||
<div>{t('disclosure-page.homepage.label')}</div>
|
||||
</div>
|
||||
<div className="md:w-2/3">
|
||||
<div></div>
|
||||
<Link
|
||||
href={t('disclosurePage.homepage.value')}
|
||||
href={t('disclosure-page.homepage.value')}
|
||||
className="transition-opacity duration-150 hover:opacity-90"
|
||||
>
|
||||
{t('disclosurePage.homepage.value')}
|
||||
{t('disclosure-page.homepage.value')}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:space-x-3 md:space-y-0">
|
||||
<div className="md:w-1/3">{t('disclosurePage.price.label')}</div>
|
||||
<div className="md:w-1/3">{t('disclosure-page.price.label')}</div>
|
||||
<div className="md:w-2/3">
|
||||
<div>{t('disclosurePage.price.value')}</div>
|
||||
<div>{t('disclosure-page.price.value')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:space-x-3 md:space-y-0">
|
||||
<div className="md:w-1/3">{t('disclosurePage.otherCharges.label')}</div>
|
||||
<div className="md:w-1/3">{t('disclosure-page.paymentMethod.label')}</div>
|
||||
<div className="md:w-2/3">
|
||||
<div>{t('disclosurePage.otherCharges.value')}</div>
|
||||
<div>{t('disclosure-page.paymentMethod.value')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:space-x-3 md:space-y-0">
|
||||
<div className="md:w-1/3">{t('disclosurePage.paymentMethod.label')}</div>
|
||||
<div className="md:w-1/3">{t('disclosure-page.paymentPeriod.label')}</div>
|
||||
<div className="md:w-2/3">
|
||||
<div>{t('disclosurePage.paymentMethod.value')}</div>
|
||||
<div>{t('disclosure-page.paymentPeriod.value')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:space-x-3 md:space-y-0">
|
||||
<div className="md:w-1/3">{t('disclosurePage.paymentPeriod.label')}</div>
|
||||
<div className="md:w-1/3">{t('disclosure-page.delivery.label')}</div>
|
||||
<div className="md:w-2/3">
|
||||
<div>{t('disclosurePage.paymentPeriod.value')}</div>
|
||||
<div>{t('disclosure-page.delivery.value')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:space-x-3 md:space-y-0">
|
||||
<div className="md:w-1/3">{t('disclosurePage.delivery.label')}</div>
|
||||
<div className="md:w-1/3">{t('disclosure-page.returnsAndExchanges.label')}</div>
|
||||
<div className="md:w-2/3">
|
||||
<div>{t('disclosurePage.delivery.value')}</div>
|
||||
<div>{t('disclosure-page.returnsAndExchanges.one')}</div>
|
||||
<div className="pt-4">{t('disclosure-page.returnsAndExchanges.two')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-4 pt-12">
|
||||
<h2 className="font-multilingual text-[30px] font-extralight">
|
||||
{t('disclosure-page.legal.title')}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2 pt-6 md:flex-row md:space-x-3 md:space-y-0">
|
||||
<div className="md:w-1/3">{t('disclosure-page.legal.001.label')}</div>
|
||||
<div className="md:w-2/3">
|
||||
<div>{t('disclosure-page.legal.001.value')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:space-x-3 md:space-y-0">
|
||||
<div className="md:w-1/3">{t('disclosurePage.returnsAndExchanges.label')}</div>
|
||||
<div className="md:w-1/3">{t('disclosure-page.legal.002.label')}</div>
|
||||
<div className="md:w-2/3">
|
||||
<div>{t('disclosurePage.returnsAndExchanges.one')}</div>
|
||||
<div className="pt-4">{t('disclosurePage.returnsAndExchanges.two')}</div>
|
||||
<div>{t('disclosure-page.legal.002.value')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:space-x-3 md:space-y-0">
|
||||
<div className="md:w-1/3">{t('disclosure-page.legal.003.label')}</div>
|
||||
<div className="md:w-2/3">
|
||||
<div>{t('disclosure-page.legal.003.value')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:space-x-3 md:space-y-0">
|
||||
<div className="md:w-1/3">{t('disclosure-page.legal.004.label')}</div>
|
||||
<div className="md:w-2/3">
|
||||
<div>{t('disclosure-page.legal.004.value')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:space-x-3 md:space-y-0">
|
||||
<div className="md:w-1/3">{t('disclosure-page.legal.005.label')}</div>
|
||||
<div className="md:w-2/3">
|
||||
<div>{t('disclosure-page.legal.005.value')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-8">
|
||||
<Link href="/" className="transition-opacity duration-150 hover:opacity-90">
|
||||
⇠ {t('disclosurePage.return')}
|
||||
⇠ {t('disclosure-page.return')}
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -2,7 +2,8 @@ import Footer from 'components/layout/footer';
|
||||
import { SupportedLocale } from 'components/layout/navbar/language-control';
|
||||
|
||||
import Navbar from 'components/layout/navbar';
|
||||
import { getCart } from 'lib/shopify';
|
||||
import { getCart, getProduct } from 'lib/shopify';
|
||||
import { Product } from 'lib/shopify/types';
|
||||
import { cookies } from 'next/headers';
|
||||
import { Suspense } from 'react';
|
||||
import Disclosures from './disclosures';
|
||||
@ -32,9 +33,14 @@ export default async function DisclosuresPage({
|
||||
cart = await getCart(cartId);
|
||||
}
|
||||
|
||||
const promotedItem: Product | undefined = await getProduct({
|
||||
handle: 'gift-bag-and-postcard-set',
|
||||
language: locale?.toUpperCase()
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar cart={cart} locale={locale} compact />
|
||||
<Navbar cart={cart} locale={locale} compact promotedItem={promotedItem} />
|
||||
<div className="py-24 md:py-48">
|
||||
<Disclosures />
|
||||
</div>
|
||||
|
@ -20,7 +20,8 @@ import SagyobarPreview from 'components/layout/sagyobar-preview';
|
||||
import Shoplist from 'components/layout/shoplist';
|
||||
import StoriesPreview from 'components/layout/stories-preview';
|
||||
import { BLOG_HANDLE } from 'lib/constants';
|
||||
import { getCart } from 'lib/shopify';
|
||||
import { getCart, getProduct } from 'lib/shopify';
|
||||
import { Product } from 'lib/shopify/types';
|
||||
import { cookies } from 'next/headers';
|
||||
import Image from 'next/image';
|
||||
import { Suspense } from 'react';
|
||||
@ -48,9 +49,14 @@ export default async function HomePage({
|
||||
cart = await getCart(cartId);
|
||||
}
|
||||
|
||||
const promotedItem: Product | undefined = await getProduct({
|
||||
handle: 'gift-bag-and-postcard-set',
|
||||
language: locale?.toUpperCase()
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar cart={cart} locale={locale} />
|
||||
<Navbar cart={cart} locale={locale} promotedItem={promotedItem} />
|
||||
<div className="pt-12 md:pt-48">
|
||||
<HomepageProducts lang={locale} />
|
||||
</div>
|
||||
@ -145,7 +151,7 @@ export default async function HomePage({
|
||||
</div>
|
||||
|
||||
<Suspense>
|
||||
<Footer cart={cart} />
|
||||
<Footer cart={cart} promotedItem={promotedItem} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
|
@ -2,7 +2,8 @@ import Footer from 'components/layout/footer';
|
||||
import { SupportedLocale } from 'components/layout/navbar/language-control';
|
||||
|
||||
import Navbar from 'components/layout/navbar';
|
||||
import { getCart } from 'lib/shopify';
|
||||
import { getCart, getProduct } from 'lib/shopify';
|
||||
import { Product } from 'lib/shopify/types';
|
||||
import { cookies } from 'next/headers';
|
||||
import { Suspense } from 'react';
|
||||
import PrivacyPolicy from './privacy-policy';
|
||||
@ -32,9 +33,14 @@ export default async function PrivacyPage({
|
||||
cart = await getCart(cartId);
|
||||
}
|
||||
|
||||
const promotedItem: Product | undefined = await getProduct({
|
||||
handle: 'gift-bag-and-postcard-set',
|
||||
language: locale?.toUpperCase()
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar cart={cart} locale={locale} compact />
|
||||
<Navbar cart={cart} locale={locale} compact promotedItem={promotedItem} />
|
||||
<div className="py-24 md:py-48">
|
||||
<PrivacyPolicy />
|
||||
</div>
|
||||
|
@ -6,8 +6,8 @@ export default function PrivacyPolicy() {
|
||||
const t = useTranslations('Index');
|
||||
return (
|
||||
<>
|
||||
<div className="mx-auto max-w-3xl text-white">
|
||||
<div className="text-center font-serif text-3xl font-bold leading-tight text-white md:mb-16 md:text-4xl">
|
||||
<div className="mx-auto max-w-3xl font-serif text-white">
|
||||
<div className="text-center text-3xl font-bold leading-tight text-white md:mb-16 md:text-4xl">
|
||||
{t('privacy.title')}
|
||||
</div>
|
||||
<div className="mb-24 text-lg leading-normal">
|
||||
@ -16,7 +16,7 @@ export default function PrivacyPolicy() {
|
||||
<p>{t('privacy.pleaseRead')}</p>
|
||||
<p className="mt-4">{t('privacy.usedFor')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
{t('privacy.definitions.title')}
|
||||
</div>
|
||||
<div>
|
||||
@ -37,13 +37,13 @@ export default function PrivacyPolicy() {
|
||||
<p className="mt-4">{t('privacy.definitions.usageData')}</p>
|
||||
<p className="mt-4">{t('privacy.definitions.you')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
{t('privacy.collectingAndUsing.title')}
|
||||
</div>
|
||||
<div>
|
||||
<p>{t('privacy.collectingAndUsing.inScope')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
{t('privacy.usageData.title')}
|
||||
</div>
|
||||
<div>
|
||||
@ -52,7 +52,7 @@ export default function PrivacyPolicy() {
|
||||
<p className="mt-4">{t('privacy.usageData.deviceInfo')}</p>
|
||||
</div>
|
||||
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
{t('privacy.cookies.title')}
|
||||
</div>
|
||||
<div>
|
||||
@ -60,7 +60,7 @@ export default function PrivacyPolicy() {
|
||||
<p className="mt-4">{t('privacy.cookies.doNotAccept')}</p>
|
||||
</div>
|
||||
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
{t('privacy.personal.title')}
|
||||
</div>
|
||||
<div>
|
||||
@ -80,20 +80,20 @@ export default function PrivacyPolicy() {
|
||||
<li className="mt-4">{t('privacy.personal.sharing.socialMedia')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
{t('privacy.personal.retention.title')}
|
||||
</div>
|
||||
<div>
|
||||
<p>{t('privacy.personal.retention.asNecessary')}</p>
|
||||
<p className="mt-4">{t('privacy.personal.retention.internalAnalysis')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
{t('privacy.personal.transfer.title')}
|
||||
</div>
|
||||
<div>
|
||||
<p>{t('privacy.personal.transfer.transborder')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
{t('privacy.personal.disclosure.title')}
|
||||
</div>
|
||||
<div>
|
||||
@ -102,7 +102,7 @@ export default function PrivacyPolicy() {
|
||||
<p className="mt-4">{t('privacy.personal.disclosure.goodFaith')}</p>
|
||||
<p className="mt-4">{t('privacy.personal.disclosure.noGuarantee')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
{t('privacy.personal.processing.title')}
|
||||
</div>
|
||||
<div>
|
||||
@ -119,34 +119,34 @@ export default function PrivacyPolicy() {
|
||||
<p className="mt-4">{t('privacy.personal.processing.creditCards')}</p>
|
||||
<p className="mt-4">{t('privacy.personal.processing.cookies')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
{t('privacy.personal.mobile.title')}
|
||||
</div>
|
||||
<div>
|
||||
<p>{t('privacy.personal.mobile.optOut')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
{t('privacy.personal.doNotTrack.title')}
|
||||
</div>
|
||||
<div>
|
||||
<p>{t('privacy.personal.doNotTrack.noResponse')}</p>
|
||||
<p className="mt-4">{t('privacy.personal.doNotTrack.browser')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
{t('privacy.personal.links.title')}
|
||||
</div>
|
||||
<div>
|
||||
<p>{t('privacy.personal.links.external')}</p>
|
||||
<p className="mt-4">{t('privacy.personal.links.disclaimer')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
{t('privacy.changes.title')}
|
||||
</div>
|
||||
<div>
|
||||
<p>{t('privacy.changes.updates')}</p>
|
||||
<p className="mt-4">{t('privacy.changes.review')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-12 md:text-2xl">
|
||||
{t('privacy.contactUs.title')}
|
||||
</div>
|
||||
<div>
|
||||
|
@ -3,12 +3,13 @@ import { notFound } from 'next/navigation';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { AddToCart } from 'components/cart/add-to-cart';
|
||||
import { AddManyToCart } from 'components/cart/add-many-to-cart';
|
||||
import { GridTileImage } from 'components/grid/tile';
|
||||
import Label from 'components/label';
|
||||
import { SupportedLocale } from 'components/layout/navbar/language-control';
|
||||
import Price from 'components/price';
|
||||
import { ProductDescription } from 'components/product/product-description';
|
||||
import { ProductTastingNotes } from 'components/product/tasting-notes';
|
||||
import { VariantSelector } from 'components/product/variant-selector';
|
||||
import { HIDDEN_PRODUCT_TAG } from 'lib/constants';
|
||||
import { getProduct, getProductRecommendations } from 'lib/shopify';
|
||||
@ -70,6 +71,7 @@ export default async function ProductPage({
|
||||
handle: params.handle,
|
||||
language: params?.locale?.toUpperCase()
|
||||
});
|
||||
|
||||
let otherImages: MediaImage[] = [];
|
||||
if (!!product) {
|
||||
otherImages = product.images
|
||||
@ -109,7 +111,7 @@ export default async function ProductPage({
|
||||
<div className="relative h-full w-full">
|
||||
<Image
|
||||
src={product.featuredImage?.url}
|
||||
alt={product.featuredImage?.altText}
|
||||
alt={product.featuredImage?.altText || product.id}
|
||||
height={product.featuredImage.height}
|
||||
width={product.featuredImage.width}
|
||||
className="h-full w-full object-cover"
|
||||
@ -118,12 +120,12 @@ export default async function ProductPage({
|
||||
|
||||
<div className="flex flex-col space-y-6 px-6 md:flex-row md:space-x-6 md:space-y-0">
|
||||
<div className="md:w-1/2">
|
||||
<h1 className="font-multilingual mb-2 text-5xl">{product.title}</h1>
|
||||
<h1 className="mb-2 font-serif text-[50px] font-bold">{product.title}</h1>
|
||||
</div>
|
||||
<div className="md:w-1/2">
|
||||
<div className="flex flex-col space-y-6">
|
||||
<div className="mb-6 flex flex-col border-t border-white/20 pt-6">
|
||||
<div className="font-multilingual mr-auto flex w-auto flex-row items-end space-x-4 text-4xl text-white">
|
||||
<div className="font-multilingual mr-auto flex w-auto flex-row items-end space-x-4 text-2xl text-white md:text-4xl">
|
||||
<Price
|
||||
amount={product.priceRange.maxVariantPrice.amount}
|
||||
currencyCode={product.priceRange.maxVariantPrice.currencyCode}
|
||||
@ -135,7 +137,8 @@ export default async function ProductPage({
|
||||
<div className="max-w-sm">
|
||||
<VariantSelector options={product.options} variants={product.variants} />
|
||||
|
||||
<AddToCart
|
||||
<AddManyToCart
|
||||
quantity={1}
|
||||
variants={product.variants}
|
||||
availableForSale={product.availableForSale}
|
||||
/>
|
||||
@ -148,6 +151,16 @@ export default async function ProductPage({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-base p-12 text-dark">
|
||||
<ProductTastingNotes product={product} />
|
||||
</div>
|
||||
|
||||
{!!product?.galleryIntro?.value && (
|
||||
<div className="font-multilingual flex w-full flex-row justify-end whitespace-pre-line">
|
||||
<div className="md:w-1/2">{product.galleryIntro.value}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
{!!otherImages &&
|
||||
otherImages?.length > 0 &&
|
||||
@ -174,6 +187,13 @@ export default async function ProductPage({
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{!!product?.lower?.value && (
|
||||
<div className="font-multilingual flex w-full flex-row justify-end whitespace-pre-line">
|
||||
<div className="md:w-1/2">{product.lower.value}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Suspense>
|
||||
<RelatedProducts id={product.id} />
|
||||
</Suspense>
|
||||
@ -190,8 +210,8 @@ async function RelatedProducts({ id }: { id: string }) {
|
||||
|
||||
return (
|
||||
<div className="border-t border-white/20 px-6 py-12 md:py-24">
|
||||
<h2 className="font-multilingual pb-8 text-2xl">other products</h2>
|
||||
<ul className="flex w-full gap-4 overflow-x-auto pt-1">
|
||||
<h2 className="pb-8 font-japan text-[20px]">other products</h2>
|
||||
<ul className="flex w-full gap-12 overflow-x-auto pt-1">
|
||||
{relatedProducts.map((product) => (
|
||||
<li
|
||||
key={product.handle}
|
||||
@ -214,7 +234,7 @@ async function RelatedProducts({ id }: { id: string }) {
|
||||
sizes="(min-width: 1024px) 20vw, (min-width: 768px) 25vw, (min-width: 640px) 33vw, (min-width: 475px) 50vw, 100vw"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="pt-3">
|
||||
<Label
|
||||
title={product.title as string}
|
||||
amount={product.priceRange.maxVariantPrice.amount}
|
||||
|
@ -3,7 +3,8 @@ import { SupportedLocale } from 'components/layout/navbar/language-control';
|
||||
|
||||
import { ProductGrid } from 'components/grid/product-grid';
|
||||
import Navbar from 'components/layout/navbar';
|
||||
import { getCart } from 'lib/shopify';
|
||||
import { getCart, getProduct } from 'lib/shopify';
|
||||
import { Product } from 'lib/shopify/types';
|
||||
import { cookies } from 'next/headers';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
@ -32,9 +33,14 @@ export default async function ProductPage({
|
||||
cart = await getCart(cartId);
|
||||
}
|
||||
|
||||
const promotedItem: Product | undefined = await getProduct({
|
||||
handle: 'gift-bag-and-postcard-set',
|
||||
language: locale?.toUpperCase()
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar cart={cart} locale={locale} compact />
|
||||
<Navbar cart={cart} locale={locale} compact promotedItem={promotedItem} />
|
||||
<div className="py-24 md:py-48">
|
||||
<ProductGrid lang={locale} />
|
||||
</div>
|
||||
|
@ -4,7 +4,8 @@ import Footer from 'components/layout/footer';
|
||||
import Navbar from 'components/layout/navbar';
|
||||
import { SupportedLocale } from 'components/layout/navbar/language-control';
|
||||
import Prose from 'components/prose';
|
||||
import { getCart, getPage } from 'lib/shopify';
|
||||
import { getCart, getPage, getProduct } from 'lib/shopify';
|
||||
import { Product } from 'lib/shopify/types';
|
||||
import { cookies } from 'next/headers';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { Suspense } from 'react';
|
||||
@ -46,12 +47,19 @@ export default async function Page({ params }: { params: { locale?: SupportedLoc
|
||||
|
||||
if (!page) return notFound();
|
||||
|
||||
const promotedItem: Product | undefined = await getProduct({
|
||||
handle: 'gift-bag-and-postcard-set',
|
||||
language: params?.locale?.toUpperCase()
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar cart={cart} locale={params?.locale} compact />
|
||||
<Navbar cart={cart} locale={params?.locale} compact showTop promotedItem={promotedItem} />
|
||||
<div className="mx-auto max-w-xl px-6 pb-24 pt-12 md:pb-48 md:pt-24">
|
||||
<div className="pb-12">
|
||||
<ShopsNav />
|
||||
<h2 className="font-multilingual mb-8 text-3xl font-medium">{page.title}</h2>
|
||||
</div>
|
||||
{/* <h2 className="font-multilingual mb-8 text-3xl font-medium">{page.title}</h2> */}
|
||||
<Prose html={page.body as string} />
|
||||
</div>
|
||||
|
||||
|
@ -8,10 +8,10 @@ export default function ShopsNav() {
|
||||
return (
|
||||
<div>
|
||||
<div className="font-multilingual flex flex-row items-baseline space-x-6 pb-12">
|
||||
<Link href="/#shops">
|
||||
<span className="flex flex-row items-center space-x-1.5">
|
||||
<Link href="/#shops" className="transition-opacity duration-150 hover:opacity-60">
|
||||
<span className="flex flex-row items-center space-x-4">
|
||||
<span>←</span>
|
||||
<span>{t('shops.all')}</span>
|
||||
<span>{t('shops.top')}</span>
|
||||
</span>
|
||||
</Link>
|
||||
<div>|</div>
|
||||
|
@ -2,7 +2,8 @@ import Footer from 'components/layout/footer';
|
||||
import { SupportedLocale } from 'components/layout/navbar/language-control';
|
||||
|
||||
import Navbar from 'components/layout/navbar';
|
||||
import { getCart } from 'lib/shopify';
|
||||
import { getCart, getProduct } from 'lib/shopify';
|
||||
import { Product } from 'lib/shopify/types';
|
||||
import { cookies } from 'next/headers';
|
||||
import { ReactNode, Suspense } from 'react';
|
||||
|
||||
@ -31,9 +32,14 @@ export default async function BlogLayout({
|
||||
cart = await getCart(cartId);
|
||||
}
|
||||
|
||||
const promotedItem: Product | undefined = await getProduct({
|
||||
handle: 'gift-bag-and-postcard-set',
|
||||
language: locale?.toUpperCase()
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar cart={cart} locale={locale} compact />
|
||||
<Navbar cart={cart} locale={locale} compact promotedItem={promotedItem} />
|
||||
{children}
|
||||
<Suspense>
|
||||
<Footer cart={cart} />
|
||||
|
@ -4,7 +4,8 @@ import { SupportedLocale } from 'components/layout/navbar/language-control';
|
||||
import Navbar from 'components/layout/navbar';
|
||||
import StoriesDetail from 'components/layout/stories-detail';
|
||||
import { BLOG_HANDLE } from 'lib/constants';
|
||||
import { getCart } from 'lib/shopify';
|
||||
import { getCart, getProduct } from 'lib/shopify';
|
||||
import { Product } from 'lib/shopify/types';
|
||||
import { cookies } from 'next/headers';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
@ -31,9 +32,14 @@ export default async function StoriesPage({
|
||||
cart = await getCart(cartId);
|
||||
}
|
||||
|
||||
const promotedItem: Product | undefined = await getProduct({
|
||||
handle: 'gift-bag-and-postcard-set',
|
||||
language: locale?.toUpperCase()
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar cart={cart} locale={locale} compact />
|
||||
<Navbar cart={cart} locale={locale} compact promotedItem={promotedItem} />
|
||||
<div className="py-24 md:py-48">
|
||||
<StoriesDetail handle={BLOG_HANDLE} locale={locale} />
|
||||
</div>
|
||||
|
@ -2,7 +2,8 @@ import Footer from 'components/layout/footer';
|
||||
import { SupportedLocale } from 'components/layout/navbar/language-control';
|
||||
|
||||
import Navbar from 'components/layout/navbar';
|
||||
import { getCart } from 'lib/shopify';
|
||||
import { getCart, getProduct } from 'lib/shopify';
|
||||
import { Product } from 'lib/shopify/types';
|
||||
import { cookies } from 'next/headers';
|
||||
import { Suspense } from 'react';
|
||||
import TermsOfUse from './terms-of-use';
|
||||
@ -30,9 +31,14 @@ export default async function TermsPage({
|
||||
cart = await getCart(cartId);
|
||||
}
|
||||
|
||||
const promotedItem: Product | undefined = await getProduct({
|
||||
handle: 'gift-bag-and-postcard-set',
|
||||
language: locale?.toUpperCase()
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar cart={cart} locale={locale} compact />
|
||||
<Navbar cart={cart} locale={locale} compact promotedItem={promotedItem} />
|
||||
<div className="py-24 md:py-48">
|
||||
<TermsOfUse />
|
||||
</div>
|
||||
|
@ -12,7 +12,7 @@ export default function TermsOfUse() {
|
||||
</div>
|
||||
<div className="mb-24 text-lg leading-normal">
|
||||
<div className="mt-4 text-center">{t('terms.pleaseRead')}</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
{t('terms.definitions.title')}
|
||||
</div>
|
||||
<div>
|
||||
@ -29,7 +29,7 @@ export default function TermsOfUse() {
|
||||
<p className="mt-4">{t('terms.definitions.socialMedia')}</p>
|
||||
<p className="mt-4">{t('terms.definitions.you')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
{t('terms.acknowledgment.title')}
|
||||
</div>
|
||||
<div>
|
||||
@ -39,7 +39,7 @@ export default function TermsOfUse() {
|
||||
<p className="mt-4">{t('terms.acknowledgment.age')}</p>
|
||||
<p className="mt-4">{t('terms.acknowledgment.privacy')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
{t('terms.orders.title')}
|
||||
</div>
|
||||
<div>
|
||||
@ -59,27 +59,27 @@ export default function TermsOfUse() {
|
||||
<li className="mt-4">{t('terms.orders.returns.exceptions.digital')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
{t('terms.inaccuracies.title')}
|
||||
</div>
|
||||
<div>
|
||||
<p>{t('terms.inaccuracies.errorsPossible')}</p>
|
||||
<p className="mt-4">{t('terms.inaccuracies.noGuarantees')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
{t('terms.prices.title')}
|
||||
</div>
|
||||
<div>
|
||||
<p>{t('terms.prices.changes')}</p>
|
||||
<p className="mt-4">{t('terms.prices.payments')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
{t('terms.promotions.title')}
|
||||
</div>
|
||||
<div>
|
||||
<p>{t('terms.promotions.conflicts')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
{t('terms.ip.title')}
|
||||
</div>
|
||||
<div>
|
||||
@ -88,13 +88,13 @@ export default function TermsOfUse() {
|
||||
<p className="mt-4">{t('terms.ip.trademarks')}</p>
|
||||
<p className="mt-4">{t('terms.ip.assignment')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
{t('terms.thirdPartyLinks.title')}
|
||||
</div>
|
||||
<div>
|
||||
<p>{t('terms.thirdPartyLinks.noControl')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
{t('terms.liability.title')}
|
||||
</div>
|
||||
<div>
|
||||
@ -102,14 +102,14 @@ export default function TermsOfUse() {
|
||||
<p className="mt-4">{t('terms.liability.noSpecialDamages')}</p>
|
||||
<p className="mt-4">{t('terms.liability.noSpecialDamages')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
{t('terms.disclaimer.title')}
|
||||
</div>
|
||||
<div>
|
||||
<p>{t('terms.disclaimer.asIs')}</p>
|
||||
<p className="mt-4">{t('terms.disclaimer.conflicts')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
{t('terms.misc.title')}
|
||||
</div>
|
||||
<div>
|
||||
@ -119,7 +119,7 @@ export default function TermsOfUse() {
|
||||
<p className="mt-4">{t('terms.misc.translation')}</p>
|
||||
<p className="mt-4">{t('terms.misc.changes')}</p>
|
||||
</div>
|
||||
<div className="my-8 text-center font-serif text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
<div className="my-8 text-center text-xl font-bold text-white md:my-16 md:text-2xl">
|
||||
{t('terms.contactUs.title')}
|
||||
</div>
|
||||
<div className="mb-12">
|
||||
|
@ -28,6 +28,37 @@ export const addItem = async (variantId: string | undefined): Promise<String | u
|
||||
}
|
||||
};
|
||||
|
||||
export const addItems = async ({
|
||||
variantId,
|
||||
quantity = 1
|
||||
}: {
|
||||
variantId: string | undefined;
|
||||
quantity: number;
|
||||
}): Promise<String | undefined> => {
|
||||
let cartId = cookies().get('cartId')?.value;
|
||||
let cart;
|
||||
|
||||
if (cartId) {
|
||||
cart = await getCart(cartId);
|
||||
}
|
||||
|
||||
if (!cartId || !cart) {
|
||||
cart = await createCart();
|
||||
cartId = cart.id;
|
||||
cookies().set('cartId', cartId);
|
||||
}
|
||||
|
||||
if (!variantId) {
|
||||
return 'Missing product variant ID';
|
||||
}
|
||||
|
||||
try {
|
||||
await addToCart(cartId, [{ merchandiseId: variantId, quantity }]);
|
||||
} catch (e) {
|
||||
return quantity === 1 ? 'Error adding item to cart' : 'Error adding items to cart';
|
||||
}
|
||||
};
|
||||
|
||||
export const removeItem = async (lineId: string): Promise<String | undefined> => {
|
||||
const cartId = cookies().get('cartId')?.value;
|
||||
|
||||
|
114
components/cart/add-many-to-cart.tsx
Normal file
@ -0,0 +1,114 @@
|
||||
'use client';
|
||||
|
||||
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline';
|
||||
import clsx from 'clsx';
|
||||
import LoadingDots from 'components/loading-dots';
|
||||
import { ProductVariant } from 'lib/shopify/types';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useState, useTransition } from 'react';
|
||||
import { addItems } from './actions';
|
||||
|
||||
export function AddManyToCart({
|
||||
quantity = 1,
|
||||
variants,
|
||||
availableForSale
|
||||
}: {
|
||||
quantity: number;
|
||||
variants: ProductVariant[];
|
||||
availableForSale: boolean;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const t = useTranslations('Index');
|
||||
|
||||
const [currentQuantity, setCurrentQuantity] = useState<number>(quantity);
|
||||
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const defaultVariantId = variants.length === 1 ? variants[0]?.id : undefined;
|
||||
const variant = variants.find((variant: ProductVariant) =>
|
||||
variant.selectedOptions.every(
|
||||
(option) => option.value === searchParams.get(option.name.toLowerCase())
|
||||
)
|
||||
);
|
||||
const selectedVariantId = variant?.id || defaultVariantId;
|
||||
const title = !availableForSale
|
||||
? t('cart.out-of-stock')
|
||||
: !selectedVariantId
|
||||
? t('cart.options')
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-y-2">
|
||||
<div className="font-multilingual flex flex-row items-center space-x-2 border border-white/50">
|
||||
<div className="px-3">{t('cart.quantity-label')}</div>
|
||||
<input
|
||||
value={currentQuantity}
|
||||
onChange={(e) => setCurrentQuantity(Number(e.target.value))}
|
||||
className={clsx(
|
||||
'w-auto grow bg-transparent px-2 py-3 text-right text-white',
|
||||
'outline-none focus:border-0 focus:outline-none focus:ring-0',
|
||||
'focus-visible:ring-none focus-visible:border-none focus-visible:outline-none',
|
||||
'focus-visible:ring-0 focus-visible:ring-offset-0',
|
||||
'active:border-none active:outline-none active:ring-0'
|
||||
)}
|
||||
/>
|
||||
<div className="flex h-full flex-col space-y-px">
|
||||
<button
|
||||
onClick={() => setCurrentQuantity(currentQuantity ? currentQuantity + 1 : 1)}
|
||||
className="grow px-2 py-0.5"
|
||||
>
|
||||
<span>
|
||||
<ChevronUpIcon className="h-4 w-4" />
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentQuantity(currentQuantity > 0 ? currentQuantity - 1 : 0)}
|
||||
className="grow px-2 py-0.5"
|
||||
>
|
||||
<span>
|
||||
<ChevronDownIcon className="h-4 w-4" />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Add items to cart"
|
||||
disabled={isPending || !availableForSale || !selectedVariantId}
|
||||
title={title}
|
||||
onClick={() => {
|
||||
// Safeguard in case someone messes with `disabled` in devtools.
|
||||
if (!availableForSale || !selectedVariantId) return;
|
||||
|
||||
startTransition(async () => {
|
||||
const error = await addItems({
|
||||
variantId: selectedVariantId,
|
||||
quantity: currentQuantity
|
||||
});
|
||||
|
||||
if (error) {
|
||||
// Trigger the error boundary in the root error.js
|
||||
throw new Error(error.toString());
|
||||
}
|
||||
|
||||
router.refresh();
|
||||
});
|
||||
}}
|
||||
className={clsx(
|
||||
'relative flex w-full items-center justify-center bg-white p-4 font-serif text-xl tracking-wide text-black hover:opacity-90',
|
||||
{
|
||||
'cursor-not-allowed opacity-60 hover:opacity-60':
|
||||
!availableForSale || !selectedVariantId,
|
||||
'cursor-not-allowed': isPending
|
||||
}
|
||||
)}
|
||||
>
|
||||
{!isPending ? (
|
||||
<span>{availableForSale ? t('cart.add') : t('cart.out-of-stock')}</span>
|
||||
) : (
|
||||
<LoadingDots className="my-3 bg-black" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -28,7 +28,7 @@ export default function DeleteItemButton({ item }: { item: CartItem }) {
|
||||
}}
|
||||
disabled={isPending}
|
||||
className={clsx(
|
||||
'ease flex h-[17px] w-[17px] items-center justify-center rounded-full bg-neutral-500 transition-all duration-200',
|
||||
'ease flex h-[17px] w-[17px] items-center justify-center rounded-full bg-dark transition-all duration-200',
|
||||
{
|
||||
'cursor-not-allowed px-0': isPending
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { getCart } from 'lib/shopify';
|
||||
import { SupportedLocale } from 'components/layout/navbar/language-control';
|
||||
import { getCart, getProduct } from 'lib/shopify';
|
||||
import { Product } from 'lib/shopify/types';
|
||||
import { cookies } from 'next/headers';
|
||||
import CartModal from './modal';
|
||||
|
||||
export default async function Cart() {
|
||||
export default async function Cart({ locale }: { locale?: SupportedLocale }) {
|
||||
const cartId = cookies().get('cartId')?.value;
|
||||
let cart;
|
||||
|
||||
@ -10,5 +12,10 @@ export default async function Cart() {
|
||||
cart = await getCart(cartId);
|
||||
}
|
||||
|
||||
return <CartModal cart={cart} />;
|
||||
const promotedItem: Product | undefined = await getProduct({
|
||||
handle: 'gift-bag-and-postcard-set',
|
||||
language: locale?.toUpperCase() || 'EN'
|
||||
});
|
||||
|
||||
return <CartModal cart={cart} promotedItem={promotedItem} />;
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ export function InlineAddToCart({
|
||||
});
|
||||
}}
|
||||
className={clsx(
|
||||
'p-4',
|
||||
'h-[56px] p-4',
|
||||
'relative flex w-full items-center justify-center',
|
||||
'border border-white/20 hover:border-white',
|
||||
'font-serif text-base tracking-wider text-white',
|
||||
@ -68,7 +68,7 @@ export function InlineAddToCart({
|
||||
{!isPending ? (
|
||||
<span>{availableForSale ? t('cart.add') : t('cart.out-of-stock')}</span>
|
||||
) : (
|
||||
<LoadingDots className="my-3 bg-white" />
|
||||
<LoadingDots className="bg-white" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
|
@ -1,28 +1,39 @@
|
||||
'use client';
|
||||
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
import { ShoppingCartIcon } from '@heroicons/react/24/outline';
|
||||
import { ShoppingBagIcon } from '@heroicons/react/24/outline';
|
||||
import { useAgeConfirmation } from 'app/hooks/use-age-confirmation';
|
||||
import Price from 'components/price';
|
||||
import AgeGateForm from 'components/product/age-gate-form';
|
||||
import { DEFAULT_OPTION } from 'lib/constants';
|
||||
import type { Cart } from 'lib/shopify/types';
|
||||
import type { Cart, Product } from 'lib/shopify/types';
|
||||
import { createUrl } from 'lib/utils';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { Fragment, useEffect, useRef, useState } from 'react';
|
||||
import AgeConfirmBeforeCheckout from './age-gate-confirm-before-checkout';
|
||||
import CloseCart from './close-cart';
|
||||
import DeleteItemButton from './delete-item-button';
|
||||
import EditItemQuantityButton from './edit-item-quantity-button';
|
||||
import OpenCart from './open-cart';
|
||||
import { PromotedCartItem } from './promoted-cart-item';
|
||||
|
||||
type MerchandiseSearchParams = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
||||
export default function CartModal({
|
||||
cart,
|
||||
promotedItem
|
||||
}: {
|
||||
cart: Cart | undefined;
|
||||
promotedItem?: Product;
|
||||
}) {
|
||||
const t = useTranslations('Index');
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [isConfirming, setIsConfirming] = useState<boolean>(false);
|
||||
const { ageConfirmed } = useAgeConfirmation();
|
||||
|
||||
const quantityRef = useRef(cart?.totalQuantity);
|
||||
const openCart = () => setIsOpen(true);
|
||||
const closeCart = () => setIsOpen(false);
|
||||
@ -38,15 +49,25 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
||||
// Always update the quantity reference
|
||||
quantityRef.current = cart?.totalQuantity;
|
||||
}
|
||||
|
||||
return () => {
|
||||
setIsConfirming(false);
|
||||
};
|
||||
}, [isOpen, cart?.totalQuantity, quantityRef]);
|
||||
|
||||
const checkoutWithAgeCheck = () => {
|
||||
setIsOpen(true);
|
||||
setIsConfirming(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button aria-label="Open cart" onClick={openCart}>
|
||||
<div>
|
||||
<button aria-label="Open cart" onClick={openCart} className="w-20">
|
||||
<OpenCart quantity={cart?.totalQuantity} />
|
||||
</button>
|
||||
<Transition show={isOpen}>
|
||||
<Dialog onClose={closeCart} className="relative z-50">
|
||||
<Dialog onClose={closeCart} className="relative z-50 w-0">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="transition-all ease-in-out duration-300"
|
||||
@ -69,17 +90,28 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
||||
>
|
||||
<Dialog.Panel className="fixed inset-y-0 right-0 flex h-full w-full flex-col border-l border-white/20 bg-dark p-6 font-sans text-white backdrop-blur-xl md:w-[390px]">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-lg font-semibold">Cart</p>
|
||||
<p className="text-lg font-semibold">Shopping bag</p>
|
||||
|
||||
<button aria-label="Close cart" onClick={closeCart}>
|
||||
<CloseCart />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{!!isConfirming && !!cart && cart?.checkoutUrl ? (
|
||||
<div className="animate-fadeIn">
|
||||
<AgeGateForm
|
||||
didCancel={() => setIsConfirming(false)}
|
||||
checkoutUrl={cart.checkoutUrl}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{!cart || cart.lines.length === 0 ? (
|
||||
<div className="mt-20 flex w-full flex-col items-center justify-center overflow-hidden">
|
||||
<ShoppingCartIcon className="h-16" />
|
||||
<p className="mt-6 text-center text-2xl font-bold">Your cart is empty.</p>
|
||||
<ShoppingBagIcon className="h-16" strokeWidth={1} />
|
||||
<p className="mt-6 text-center font-serif text-xl">
|
||||
Your shopping bag is empty.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-full flex-col justify-between overflow-hidden p-1">
|
||||
@ -127,7 +159,9 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
||||
{item.merchandise.product.title}
|
||||
</span>
|
||||
{item.merchandise.title !== DEFAULT_OPTION ? (
|
||||
<p className="text-sm text-white">{item.merchandise.title}</p>
|
||||
<p className="text-sm text-white">
|
||||
{item.merchandise.title}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</Link>
|
||||
@ -150,14 +184,16 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
<div className="py-4 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
{!!promotedItem && (
|
||||
<PromotedCartItem
|
||||
product={promotedItem}
|
||||
availableForSale={promotedItem.availableForSale}
|
||||
/>
|
||||
)}
|
||||
<div className="pb-4 pt-12 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<div className="mb-3 flex items-center justify-between border-b border-white/20 pb-1">
|
||||
<p>Taxes</p>
|
||||
<Price
|
||||
className="text-right text-base text-white"
|
||||
amount={cart.cost.totalTaxAmount.amount}
|
||||
currencyCode={cart.cost.totalTaxAmount.currencyCode}
|
||||
/>
|
||||
<p className="text-right text-white/50">Calculated at checkout</p>
|
||||
</div>
|
||||
<div className="mb-3 flex items-center justify-between border-b border-white/20 py-1">
|
||||
<p>Shipping</p>
|
||||
@ -172,15 +208,35 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<AgeConfirmBeforeCheckout checkoutUrl={cart.checkoutUrl}>
|
||||
{ageConfirmed ? (
|
||||
<>
|
||||
<Link
|
||||
href={cart.checkoutUrl}
|
||||
className="block w-full border border-white/20 bg-dark px-12 py-6 text-center font-sans font-medium uppercase tracking-wider text-white transition-colors duration-300 hover:bg-white hover:text-black"
|
||||
>
|
||||
{t('cart.proceed')}
|
||||
</AgeConfirmBeforeCheckout>
|
||||
</Link>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => checkoutWithAgeCheck()}
|
||||
className="block w-full border border-white/20 bg-dark px-12 py-6 text-center font-sans font-medium uppercase tracking-wider text-white transition-colors duration-300 hover:bg-white hover:text-black"
|
||||
>
|
||||
{t('cart.proceed')}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
95
components/cart/promoted-cart-item.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
'use client';
|
||||
|
||||
import { addItem } from 'components/cart/actions';
|
||||
import LoadingDots from 'components/loading-dots';
|
||||
import Price from 'components/price';
|
||||
import { Product, ProductVariant } from 'lib/shopify/types';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useTransition } from 'react';
|
||||
|
||||
export function PromotedCartItem({
|
||||
product,
|
||||
availableForSale
|
||||
}: {
|
||||
product: Product;
|
||||
availableForSale: boolean;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const defaultVariantId = product?.variants?.length === 1 ? product?.variants?.[0]?.id : undefined;
|
||||
const variant = product?.variants?.find((variant: ProductVariant) =>
|
||||
variant.selectedOptions.every(
|
||||
(option) => option.value === searchParams.get(option.name.toLowerCase())
|
||||
)
|
||||
);
|
||||
const selectedVariantId = variant?.id || defaultVariantId;
|
||||
const title = !availableForSale
|
||||
? 'Out of stock'
|
||||
: !selectedVariantId
|
||||
? 'Please select options'
|
||||
: undefined;
|
||||
|
||||
return product ? (
|
||||
<button
|
||||
aria-label="Add item to cart"
|
||||
disabled={isPending || !availableForSale || !selectedVariantId}
|
||||
title={title}
|
||||
onClick={() => {
|
||||
// Safeguard in case someone messes with `disabled` in devtools.
|
||||
if (!availableForSale || !selectedVariantId) return;
|
||||
|
||||
startTransition(async () => {
|
||||
const error = await addItem(selectedVariantId);
|
||||
|
||||
if (error) {
|
||||
// Trigger the error boundary in the root error.js
|
||||
throw new Error(error.toString());
|
||||
}
|
||||
|
||||
router.refresh();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<div key={product.handle}>
|
||||
<div className="flex flex-col space-y-2 border border-dark/10 border-y-white/20 p-2 transition-colors duration-150 hover:border-y-white/40 hover:bg-white/20">
|
||||
<div className="flex flex-row space-x-4">
|
||||
<div className="relative z-0 h-16 w-16 cursor-pointer overflow-hidden bg-white">
|
||||
<Link href={`/product/${product.handle}`} passHref>
|
||||
<Image
|
||||
width={150}
|
||||
height={150}
|
||||
src={product?.featuredImage?.url}
|
||||
alt={product?.featuredImage?.altText || product?.title}
|
||||
unoptimized
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col text-sm text-white">
|
||||
<div className="flex flex-col items-start justify-between space-y-2 text-sm">
|
||||
<span>{product.title}</span>
|
||||
<span>
|
||||
{!isPending ? (
|
||||
<Price
|
||||
className="flex-none"
|
||||
amount={product.priceRange.maxVariantPrice.amount}
|
||||
currencyCode={product.priceRange.maxVariantPrice.currencyCode}
|
||||
currencyCodeClassName="hidden @[275px]/label:inline"
|
||||
/>
|
||||
) : (
|
||||
<LoadingDots className="bg-white" />
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</button>
|
||||
) : null;
|
||||
}
|
@ -38,7 +38,7 @@ function HomepageProductsItem({ item, priority }: { item: Product; priority?: bo
|
||||
<div className="line-clamp-4 pt-2 font-extralight">
|
||||
<span>{item?.summary?.value}</span>{' '}
|
||||
<span className="ml-2 inline-flex flex-row items-center space-x-1 opacity-50 transition-opacity duration-150 group-hover:opacity-100">
|
||||
<span>Read more.</span>
|
||||
<span>read more.</span>
|
||||
<span>
|
||||
<ChevronRightIcon width={16} />
|
||||
</span>
|
||||
|
@ -15,10 +15,10 @@ const Label = ({
|
||||
return (
|
||||
<div className={clsx('@container/label')}>
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="mr-4 line-clamp-2 grow font-serif text-3xl tracking-wider md:text-4xl">
|
||||
<h3 className="mr-4 line-clamp-2 grow font-serif text-[28px] leading-tight tracking-wider md:text-[28px]">
|
||||
{title}
|
||||
</h3>
|
||||
<div className="font-multilingual flex flex-row items-center space-x-2 text-[17px]">
|
||||
<div className="font-multilingual flex flex-row items-center space-x-2 text-[14px] font-normal">
|
||||
<Price
|
||||
className="flex-none"
|
||||
amount={amount}
|
||||
|
@ -13,43 +13,43 @@ export default function FooterMenu() {
|
||||
<nav className="font-multilingual flex flex-col space-y-2 text-left text-sm font-extralight">
|
||||
<div>
|
||||
<Link href="/products" className="transition-opacity duration-150 hover:opacity-50">
|
||||
{t('menu.products')}
|
||||
{t('footer.menu.products')}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Link href="/shop-list" className="transition-opacity duration-150 hover:opacity-50">
|
||||
{t('menu.shops')}
|
||||
{t('footer.menu.shops')}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Link href="/about" className="transition-opacity duration-150 hover:opacity-50">
|
||||
{t('menu.about')}
|
||||
{t('footer.menu.about')}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Link href="/bar" className="transition-opacity duration-150 hover:opacity-50">
|
||||
{t('menu.bar')}
|
||||
{t('footer.menu.bar')}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Link href="/concept" className="transition-opacity duration-150 hover:opacity-50">
|
||||
{t('menu.concept')}
|
||||
{t('footer.menu.concept')}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{/* <div>
|
||||
<Link href="/stories" className="transition-opacity duration-150 hover:opacity-50">
|
||||
{t('menu.stories')}
|
||||
{t('footer.menu.stories')}
|
||||
</Link>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div>
|
||||
<Link href="/company" className="transition-opacity duration-150 hover:opacity-50">
|
||||
{t('menu.company')}
|
||||
{t('footer.menu.company')}
|
||||
</Link>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -3,14 +3,20 @@ import CartModal from 'components/cart/modal';
|
||||
import FacebookIcon from 'components/icons/facebook';
|
||||
import InstagramIcon from 'components/icons/instagram';
|
||||
import KanjiLogo from 'components/icons/kanji';
|
||||
import { Cart } from 'lib/shopify/types';
|
||||
import { Cart, Product } from 'lib/shopify/types';
|
||||
import Link from 'next/link';
|
||||
import FooterMenu from './footer-menu';
|
||||
import NewsletterFooter from './newsletter-footer';
|
||||
|
||||
const { COMPANY_NAME, SITE_NAME } = process.env;
|
||||
|
||||
export default async function Footer({ cart }: { cart?: Cart }) {
|
||||
export default async function Footer({
|
||||
cart,
|
||||
promotedItem
|
||||
}: {
|
||||
cart?: Cart;
|
||||
promotedItem?: Product;
|
||||
}) {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const copyrightDate = 2023 + (currentYear > 2023 ? `-${currentYear}` : '');
|
||||
const copyrightName = COMPANY_NAME || SITE_NAME || '';
|
||||
@ -38,7 +44,9 @@ export default async function Footer({ cart }: { cart?: Cart }) {
|
||||
<p className="font-serif text-xs font-semibold">suginomori brewery</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-start space-y-2">
|
||||
<p className="font-japan text-xl font-extralight">長野県塩尻市奈良井551-1</p>
|
||||
<p className="font-japan text-lg font-extralight md:text-xl">
|
||||
長野県塩尻市奈良井551-1
|
||||
</p>
|
||||
<p className="font-serif text-xs font-semibold">551-1 Narai, Shiojiri, Nagano</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -50,14 +58,16 @@ export default async function Footer({ cart }: { cart?: Cart }) {
|
||||
<Link href="/" className="transition-opacity duration-150 hover:opacity-90">
|
||||
<KanjiLogo className="h-64" />
|
||||
</Link>
|
||||
<div className="flex grow flex-col items-end space-y-6">
|
||||
<div className="flex grow flex-col space-y-6 md:items-end">
|
||||
<div className="flex flex-col items-start space-y-2">
|
||||
<p className="font-japan text-[22px] font-extralight">杉の森酒造</p>
|
||||
<p className="font-serif text-lg">suginomori brewery</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-start space-y-2">
|
||||
<p className="font-japan text-[17px] font-extralight">長野県塩尻市奈良井551-1</p>
|
||||
<p className="font-serif text-lg">551-1 Narai, Shiojiri, Nagano</p>
|
||||
<p className="font-japan text-sm font-extralight md:text-[17px]">
|
||||
長野県塩尻市奈良井551-1
|
||||
</p>
|
||||
<p className="font-serif text-sm md:text-lg">551-1 Narai, Shiojiri, Nagano</p>
|
||||
</div>
|
||||
<div className="flex flex-row justify-between space-x-4">
|
||||
<div className="flex flex-row items-center space-x-6">
|
||||
@ -69,7 +79,7 @@ export default async function Footer({ cart }: { cart?: Cart }) {
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right font-serif">
|
||||
<div className="font-serif md:text-right">
|
||||
<div>
|
||||
© {copyrightDate} {copyrightName}
|
||||
{copyrightName.length && !copyrightName.endsWith('.') ? '.' : ''}
|
||||
@ -85,12 +95,22 @@ export default async function Footer({ cart }: { cart?: Cart }) {
|
||||
|
||||
<div className="flex flex-col space-y-2 pt-24">
|
||||
<div className="flex flex-row justify-between space-x-4">
|
||||
<CartModal cart={cart} />
|
||||
<CartModal cart={cart} promotedItem={promotedItem} />
|
||||
<div className="flex flex-row items-center space-x-6">
|
||||
<Link href="https://www.instagram.com/suginomoribrewery/" className="group">
|
||||
<Link
|
||||
href="https://www.instagram.com/suginomoribrewery/"
|
||||
className="group"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<InstagramIcon className="h-6 stroke-transparent transition-all ease-in-out group-hover:scale-110" />
|
||||
</Link>
|
||||
<Link href="https://www.facebook.com/suginomoribrewery" className="group">
|
||||
<Link
|
||||
href="https://www.facebook.com/suginomoribrewery"
|
||||
className="group"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<FacebookIcon className="h-6 stroke-transparent transition-all ease-in-out group-hover:scale-110" />
|
||||
</Link>
|
||||
</div>
|
||||
|
@ -120,14 +120,14 @@ export function MenuModal({ scrolled }: { scrolled: boolean }) {
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{/* <div>
|
||||
<Link
|
||||
href="/stories"
|
||||
className="font-serif text-4xl font-normal transition-opacity duration-150 hover:opacity-50"
|
||||
>
|
||||
{t('menu.stories')}
|
||||
</Link>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div>
|
||||
<Link
|
||||
|
@ -1,13 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import { Transition } from '@headlessui/react';
|
||||
import { ChevronUpIcon } from '@heroicons/react/24/outline';
|
||||
import clsx from 'clsx';
|
||||
import CartModal from 'components/cart/modal';
|
||||
import OpenCart from 'components/cart/open-cart';
|
||||
import LogoNamemark from 'components/icons/namemark';
|
||||
import { Cart } from 'lib/shopify/types';
|
||||
import { Cart, Product } from 'lib/shopify/types';
|
||||
import Link from 'next/link';
|
||||
import { Suspense } from 'react';
|
||||
import { useInView } from 'react-intersection-observer';
|
||||
import { MenuModal } from '../menu/modal';
|
||||
import { LanguageControl, SupportedLocale } from './language-control';
|
||||
@ -15,11 +14,15 @@ import { LanguageControl, SupportedLocale } from './language-control';
|
||||
export default function Navbar({
|
||||
cart,
|
||||
locale,
|
||||
compact
|
||||
compact,
|
||||
showTop = false,
|
||||
promotedItem
|
||||
}: {
|
||||
cart?: Cart;
|
||||
locale?: SupportedLocale;
|
||||
compact?: boolean;
|
||||
showTop?: boolean;
|
||||
promotedItem?: Product;
|
||||
}) {
|
||||
const { ref, inView } = useInView({
|
||||
threshold: 0,
|
||||
@ -28,6 +31,19 @@ export default function Navbar({
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
{showTop && !inView && (
|
||||
<div className="fixed left-6 top-32 z-20 animate-fadeIn">
|
||||
<Link href="#" className="transition-opacity duration-150 hover:opacity-60">
|
||||
<span className="flex flex-row items-center space-x-2">
|
||||
<ChevronUpIcon
|
||||
className="h-6 w-6 stroke-subtle transition-colors duration-150 group-hover:stroke-white"
|
||||
strokeWidth={2}
|
||||
/>
|
||||
<span className="text-sm font-medium tracking-wider">TOP</span>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
<div className="fixed top-0 z-20 w-full bg-dark/90 backdrop-blur-sm">
|
||||
<Transition
|
||||
show={!!ref && !inView}
|
||||
@ -45,12 +61,10 @@ export default function Navbar({
|
||||
</Link>
|
||||
</div>
|
||||
<nav className="flex flex-row items-center space-x-4 px-6">
|
||||
<Suspense fallback={<OpenCart />}>
|
||||
<div className="flex flex-col-reverse items-center justify-center space-y-2 px-2 md:flex-row md:space-x-6">
|
||||
<CartModal cart={cart} />
|
||||
<CartModal cart={cart} promotedItem={promotedItem} />
|
||||
<MenuModal scrolled={!inView} />
|
||||
</div>
|
||||
</Suspense>
|
||||
</nav>
|
||||
</div>
|
||||
</Transition>
|
||||
@ -74,9 +88,7 @@ export default function Navbar({
|
||||
<LanguageControl lang={locale} />
|
||||
</div>
|
||||
<div className="flex flex-col-reverse items-center justify-center space-y-2 rounded md:flex-row md:space-x-6 md:space-y-0">
|
||||
<Suspense fallback={<OpenCart />}>
|
||||
<CartModal cart={cart} />
|
||||
</Suspense>
|
||||
<CartModal cart={cart} promotedItem={promotedItem} />
|
||||
<MenuModal scrolled={!inView} />
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -15,16 +15,15 @@ export default function NewsletterSignup() {
|
||||
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"
|
||||
target="_blank"
|
||||
name="mc-embedded-subscribe-footer-form"
|
||||
>
|
||||
<label htmlFor="email-address" className="sr-only">
|
||||
{t('newsletter.placeholder')}
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email-address"
|
||||
id="email-address"
|
||||
name="EMAIL"
|
||||
id="EMAIL"
|
||||
autoComplete="email"
|
||||
required
|
||||
className={clsx(
|
||||
|
@ -14,14 +14,13 @@ export default function NewsletterSignup() {
|
||||
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"
|
||||
target="_blank"
|
||||
>
|
||||
<label htmlFor="email-address" className="sr-only">
|
||||
{t('newsletter.placeholder')}
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email-address"
|
||||
name="EMAIL"
|
||||
id="email-address"
|
||||
autoComplete="email"
|
||||
required
|
||||
|
@ -3,7 +3,7 @@
|
||||
/* This example requires Tailwind CSS v2.0+ */
|
||||
import { FC, Fragment, useEffect, useRef, useState, useTransition } from 'react';
|
||||
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import { CheckIcon } from '@heroicons/react/24/outline';
|
||||
import { useAgeConfirmation } from 'app/hooks/use-age-confirmation';
|
||||
import clsx from 'clsx';
|
||||
@ -27,7 +27,7 @@ const AgeGateForm: FC<AgeGateFormProps> = ({ checkoutUrl, didCancel }) => {
|
||||
|
||||
const { onAgeConfirmed } = useAgeConfirmation();
|
||||
|
||||
const yearFieldRef = useRef(null);
|
||||
const yearFieldRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const minAge = 20;
|
||||
const maxAge = 130;
|
||||
@ -61,63 +61,41 @@ const AgeGateForm: FC<AgeGateFormProps> = ({ checkoutUrl, didCancel }) => {
|
||||
}
|
||||
}, [month, day, year]);
|
||||
|
||||
useEffect(() => {
|
||||
if (yearFieldRef) {
|
||||
console.debug('yearFieldRef', yearFieldRef?.current);
|
||||
yearFieldRef?.current?.focus();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Transition.Root show={true} as={Fragment}>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="fixed inset-0 z-50 overflow-y-auto"
|
||||
initialFocus={yearFieldRef}
|
||||
onClose={() => {}}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'flex min-h-screen items-end justify-center px-4 pb-20 pt-4 text-center sm:block sm:p-0'
|
||||
)}
|
||||
>
|
||||
<Transition show={true}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
enter="transition-all ease-in-out duration-300"
|
||||
enterFrom="translate-x-[-100%]"
|
||||
enterTo="translate-x-0"
|
||||
leave="transition-all ease-in-out duration-200"
|
||||
leaveFrom="translate-x-0"
|
||||
leaveTo="translate-x-[-100%]"
|
||||
>
|
||||
<Dialog.Overlay className="fixed inset-0 bg-dark/80 backdrop-blur-sm transition-opacity" />
|
||||
</Transition.Child>
|
||||
|
||||
{/* This element is to trick the browser into centering the modal contents. */}
|
||||
<span className="hidden sm:inline-block sm:h-screen sm:align-middle" aria-hidden="true">
|
||||
​
|
||||
</span>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<div className="inline-block space-y-6 overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left align-bottom text-dark shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6 sm:align-middle">
|
||||
<div className="grid h-full w-full grid-cols-1 place-content-center">
|
||||
<div className="mx-auto inline-block space-y-6 overflow-hidden rounded-lg py-12 text-left align-bottom text-white transition-all sm:my-8 sm:w-full sm:max-w-lg sm:py-6 sm:align-middle">
|
||||
<div>
|
||||
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100 dark:bg-green-900">
|
||||
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-900">
|
||||
<CheckIcon
|
||||
className="h-6 w-6 text-green-600 dark:text-green-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center sm:mt-5">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-dark dark:text-white"
|
||||
>
|
||||
<div className="pt-5 text-center">
|
||||
<h3 className="text-lg font-medium leading-6 text-white">
|
||||
{t('age-gate.title')}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
</h3>
|
||||
{/* <div className="mt-2">
|
||||
<p className="text-sm text-white">{t('age-gate.description')}</p>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@ -128,9 +106,10 @@ const AgeGateForm: FC<AgeGateFormProps> = ({ checkoutUrl, didCancel }) => {
|
||||
<div className="flex flex-col items-start space-y-1">
|
||||
<input
|
||||
type="text"
|
||||
className="w-full border bg-white p-2 text-center selection:bg-emerald-200"
|
||||
className="w-full border bg-transparent p-2 text-center text-white selection:bg-emerald-200 focus:ring-offset-0"
|
||||
ref={yearFieldRef}
|
||||
placeholder="YYYY"
|
||||
autoFocus
|
||||
maxLength={4}
|
||||
onChange={(e) => setYear(parseInt(e?.target?.value))}
|
||||
/>
|
||||
@ -140,7 +119,7 @@ const AgeGateForm: FC<AgeGateFormProps> = ({ checkoutUrl, didCancel }) => {
|
||||
<div className="flex flex-col items-start space-y-1">
|
||||
<input
|
||||
type="text"
|
||||
className="w-full border bg-white p-2 text-center selection:bg-emerald-200"
|
||||
className="w-full border bg-transparent p-2 text-center text-white selection:bg-emerald-200 focus:ring-offset-0"
|
||||
placeholder="MM"
|
||||
maxLength={2}
|
||||
onChange={(e) => setMonth(parseInt(e?.target?.value))}
|
||||
@ -150,7 +129,7 @@ const AgeGateForm: FC<AgeGateFormProps> = ({ checkoutUrl, didCancel }) => {
|
||||
<div className="flex flex-col items-start space-y-1">
|
||||
<input
|
||||
type="text"
|
||||
className="w-full border bg-white p-2 text-center selection:bg-emerald-200"
|
||||
className="w-full border bg-transparent p-2 text-center text-white selection:bg-emerald-200 focus:ring-offset-0"
|
||||
placeholder="DD"
|
||||
maxLength={2}
|
||||
onChange={(e) => setDay(parseInt(e?.target?.value))}
|
||||
@ -164,14 +143,11 @@ const AgeGateForm: FC<AgeGateFormProps> = ({ checkoutUrl, didCancel }) => {
|
||||
type="button"
|
||||
className={clsx(
|
||||
'inline-flex w-full justify-center',
|
||||
hasValidDate
|
||||
? 'border border-dark hover:border-dark/50'
|
||||
: 'border border-dark/50 hover:border-dark/20',
|
||||
'bg-white px-4 py-2',
|
||||
'text-base font-medium text-black',
|
||||
'shadow-sm transition-colors duration-300',
|
||||
'focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'disabled:cursor-not-allowed disabled:opacity-10',
|
||||
'disabled:border-dark/50 disabled:hover:border-dark/50',
|
||||
'sm:col-start-2 sm:text-sm'
|
||||
)}
|
||||
@ -184,13 +160,14 @@ const AgeGateForm: FC<AgeGateFormProps> = ({ checkoutUrl, didCancel }) => {
|
||||
type="button"
|
||||
className={clsx(
|
||||
'mt-3 inline-flex w-full justify-center',
|
||||
'border border-dark/50 hover:border-dark/20',
|
||||
'bg-white px-4 py-2',
|
||||
'border border-dark/10 hover:border-dark/30',
|
||||
'bg-white/30 px-4 py-2 text-white',
|
||||
'text-base font-medium',
|
||||
'text-black shadow-sm transition-all duration-300 hover:bg-white hover:bg-opacity-20',
|
||||
'focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2',
|
||||
'sm:col-start-1 sm:mt-0 sm:text-sm',
|
||||
'disabled:border-dark/50 disabled:hover:border-dark/50'
|
||||
'disabled:border-dark/20 disabled:hover:border-dark/20',
|
||||
'disabled:text-dark/30 disabled:hover:text-dark/30'
|
||||
)}
|
||||
onClick={() => cancel()}
|
||||
disabled={isPending}
|
||||
@ -199,10 +176,9 @@ const AgeGateForm: FC<AgeGateFormProps> = ({ checkoutUrl, didCancel }) => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
</Transition.Child>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -6,7 +6,7 @@ export function ProductDescription({ product }: { product: Product }) {
|
||||
<>
|
||||
{product.descriptionHtml ? (
|
||||
<Prose
|
||||
className="mb-6 text-lg leading-tight dark:text-white/[60%]"
|
||||
className="mb-6 text-base leading-tight dark:text-white/[60%]"
|
||||
html={product.descriptionHtml}
|
||||
/>
|
||||
) : null}
|
||||
|
35
components/product/tasting-notes.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { Product } from 'lib/shopify/types';
|
||||
import Image from 'next/image';
|
||||
|
||||
export function ProductTastingNotes({ product }: { product: Product }) {
|
||||
const notes = product?.notes?.value;
|
||||
const imageUrl = product?.notesImage?.reference?.image?.url;
|
||||
const imageWidth = product?.notesImage?.reference?.image?.width;
|
||||
const imageHeight = product?.notesImage?.reference?.image?.height;
|
||||
const imageAlt = product?.notesImage?.reference?.image?.altText;
|
||||
|
||||
if (!imageUrl && !imageWidth && !imageHeight) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-between space-y-6 px-6 md:flex-row md:items-end md:space-x-12 md:space-y-0">
|
||||
{!!notes ? (
|
||||
<div className="font-multilingual flex flex-col space-y-4 md:min-w-[120px] md:max-w-sm">
|
||||
<h2 className="text-[38px] leading-tight">tasting notes</h2>
|
||||
<div className="flex w-full flex-row justify-end whitespace-pre-line">{notes}</div>
|
||||
</div>
|
||||
) : null}
|
||||
{imageUrl && imageHeight && imageWidth && (
|
||||
<div className="md:w-2/3">
|
||||
<Image
|
||||
src={imageUrl}
|
||||
width={imageWidth}
|
||||
height={imageHeight}
|
||||
alt={imageAlt || imageUrl}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -11,15 +11,15 @@ const Prose: FunctionComponent<TextProps> = ({ html, className }) => {
|
||||
<div
|
||||
className={clsx(
|
||||
'prose text-lg leading-7',
|
||||
'font-multilingual text-[15px] font-extralight text-white',
|
||||
'prose-headings:mt-8 prose-headings:font-semibold prose-headings:tracking-wide prose-headings:text-white',
|
||||
'font-multilingual text-[15px] font-extralight text-current',
|
||||
'prose-headings:mt-8 prose-headings:font-semibold prose-headings:tracking-wide prose-headings:text-current',
|
||||
'prose-h1:text-5xl prose-h2:text-4xl prose-h3:text-3xl prose-h4:text-2xl prose-h5:text-xl prose-h6:text-lg',
|
||||
'prose-a:text-white/80 prose-a:underline hover:prose-a:text-white',
|
||||
'prose-strong:text-white',
|
||||
'prose-a:text-current/80 prose-a:underline hover:prose-a:text-current',
|
||||
'prose-strong:text-current',
|
||||
'prose-td:border-opacity-20 prose-td:py-4 prose-td:font-normal',
|
||||
'prose-tr:border-subtle',
|
||||
'prose-ol:mt-8 prose-ol:list-decimal prose-ol:pl-6 prose-ul:mt-8 prose-ul:list-disc prose-ul:pl-6',
|
||||
'dark:text-white dark:prose-headings:text-white dark:prose-a:text-white dark:prose-strong:text-white',
|
||||
'dark:text-current dark:prose-headings:text-current dark:prose-a:text-current dark:prose-strong:text-current',
|
||||
'!tracking-normal',
|
||||
className
|
||||
)}
|
||||
|
@ -10,7 +10,37 @@ const productFragment = /* GraphQL */ `
|
||||
description
|
||||
descriptionHtml
|
||||
summary: metafield(namespace: "custom", key: "product_summary") {
|
||||
key
|
||||
value
|
||||
type
|
||||
}
|
||||
galleryIntro: metafield(namespace: "custom", key: "product_gallery_intro") {
|
||||
key
|
||||
value
|
||||
type
|
||||
}
|
||||
lower: metafield(namespace: "custom", key: "product_lower_text") {
|
||||
key
|
||||
value
|
||||
type
|
||||
}
|
||||
notes: metafield(namespace: "custom", key: "tasting_notes_text") {
|
||||
key
|
||||
value
|
||||
type
|
||||
}
|
||||
notesImage: metafield(namespace: "custom", key: "tasting_notes_image") {
|
||||
key
|
||||
value
|
||||
type
|
||||
reference {
|
||||
... on MediaImage {
|
||||
id
|
||||
image {
|
||||
...image
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
options {
|
||||
id
|
||||
|
@ -149,6 +149,20 @@ export type ShopifyProduct = {
|
||||
summary: {
|
||||
value: string;
|
||||
};
|
||||
galleryIntro: {
|
||||
value: string;
|
||||
};
|
||||
lower: {
|
||||
value: string;
|
||||
};
|
||||
notes?: {
|
||||
value?: string;
|
||||
};
|
||||
notesImage?: {
|
||||
reference: {
|
||||
image: Image;
|
||||
};
|
||||
};
|
||||
options: ProductOption[];
|
||||
priceRange: {
|
||||
maxVariantPrice: Money;
|
||||
@ -168,6 +182,8 @@ export type ShopifyCartOperation = {
|
||||
};
|
||||
variables: {
|
||||
cartId: string;
|
||||
country?: string;
|
||||
language?: string;
|
||||
};
|
||||
};
|
||||
|
||||
@ -187,6 +203,8 @@ export type ShopifyAddToCartOperation = {
|
||||
merchandiseId: string;
|
||||
quantity: number;
|
||||
}[];
|
||||
country?: string;
|
||||
language?: string;
|
||||
};
|
||||
};
|
||||
|
||||
|
217
messages/en.json
@ -16,23 +16,23 @@
|
||||
},
|
||||
"shopping-guide": {
|
||||
"title": "shopping guide",
|
||||
"terms": "terms of use",
|
||||
"legal": "legal notice",
|
||||
"terms": "terms and conditions",
|
||||
"legal": "disclosures",
|
||||
"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.",
|
||||
"placeholder": "Email",
|
||||
"button": "Notify me"
|
||||
"description": "Subscribe to get access to exclusive information regarding events and pairing dinners.",
|
||||
"placeholder": "email",
|
||||
"button": "subscribe"
|
||||
},
|
||||
"home": {
|
||||
"previews": {
|
||||
"about-narai": {
|
||||
"title": "water of the mountains,",
|
||||
"subtitle": "sake of the skies",
|
||||
"body": "We brew our sake in one of the highest breweries in Japan, standing at an altitude of 940m and surrounded by an abundance of nature. While many breweries typically use a stable water source such as well water, narai uses the fresh water from the mountains, flowing from an altitude of over 1,000m. The water originates from a spring near the watershed of the Shinano River and Kiso River, and is characterized by its clarity and smooth, rounded texture brought about by a rare “water hardness” of less than 25."
|
||||
"body": "We brew our sake in one of the highest breweries in Japan, standing at an altitude of 940m and surrounded by an abundance of nature. While many breweries typically use a stable water source such as well water, narai uses the fresh water from the mountains, flowing from an altitude of over 1,000m. The water originates from a spring near the watershed of the Shinano River and Kiso River, and is characterized by its clarity and smooth, rounded texture, brought about by a rare water hardness of less than 25."
|
||||
},
|
||||
"location": {
|
||||
"title": "brewed in Narai-juku, Nagano",
|
||||
@ -52,49 +52,63 @@
|
||||
"concept": {
|
||||
"title": "beyond brewing",
|
||||
"subtitle": "",
|
||||
"body": "We are driven by our mission to preserve Japanese sake culture for future generations. To achieve this, we reexamine conventional practices of the sake industry and experiment with new endeavors. We are dedicated to exploring sake with a free and creative approach, going beyond brewing and spreading its charm to the world.",
|
||||
"body": "Driven by our mission to preserve Japanese sake culture for future generations, we reexamine conventional practices of the sake industry and experiment with new endeavors. We are dedicated to exploring sake with a free and creative approach, going beyond brewing and spreading its charm to the world.",
|
||||
"button": "concept"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shops": {
|
||||
"title": "Shop list",
|
||||
"top": "top",
|
||||
"title": "shop list",
|
||||
"subtitle": "",
|
||||
"all": "All shops",
|
||||
"hokkaido": "Hokkaido / North",
|
||||
"kanto": "Kanto",
|
||||
"chubu": "Chubu",
|
||||
"kinki": "Kinki",
|
||||
"chugoku": "Chugoku",
|
||||
"kyushu": "Kyushu"
|
||||
"hokkaido": "Hokkaido / Tohoku region",
|
||||
"kanto": "Kanto region",
|
||||
"chubu": "Chubu region",
|
||||
"kinki": "Kinki region",
|
||||
"chugoku": "Chugoku region",
|
||||
"kyushu": "Kyushu region"
|
||||
},
|
||||
"footer": {
|
||||
"newsletter": {
|
||||
"promo": "Get 10% off on your first order. Get discount."
|
||||
"promo": "Get access to exclusive information on events & pairing dinners."
|
||||
},
|
||||
"menu": {
|
||||
"title": "menu",
|
||||
"products": "products",
|
||||
"shops": "shop list",
|
||||
"about": "about narai",
|
||||
"bar": "sagyobar",
|
||||
"concept": "concept",
|
||||
"stories": "stories",
|
||||
"company": "company",
|
||||
"contact": "contact"
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"add": "add to cart",
|
||||
"out-of-stock": "out of stock",
|
||||
"title": "Shopping Bag",
|
||||
"subtitle": "Review your Order",
|
||||
"empty": "Your shopping bag is empty",
|
||||
"declinedCard": "We couldn't process the purchase. Please check your card information and try again.",
|
||||
"thankYou": "Thank you for your order.",
|
||||
"subtotal": "Subtotal",
|
||||
"taxes": "Taxes",
|
||||
"taxCalculation": "Calculated at checkout",
|
||||
"shipping": "Shipping",
|
||||
"shippingCalculation": "Calculated at checkout",
|
||||
"total": "Total",
|
||||
"proceed": "Proceed to Checkout",
|
||||
"continue": "Continue Shopping",
|
||||
"note": "Notes",
|
||||
"notePlaceholder": "Enter any notes you would like to include with your order",
|
||||
"addNote": "Add a note to your order",
|
||||
"editNote": "Edit note",
|
||||
"hideNote": "Hide",
|
||||
"saving": "Saving...",
|
||||
"options": "please select options",
|
||||
"quantity-label": "quantity",
|
||||
"title": "shopping bag",
|
||||
"subtitle": "review your order",
|
||||
"empty": "your shopping bag is empty",
|
||||
"declinedCard": "we couldn't process the purchase. Please check your card information and try again.",
|
||||
"thankYou": "thank you for your order.",
|
||||
"subtotal": "subtotal",
|
||||
"taxes": "taxes",
|
||||
"taxCalculation": "calculated at checkout",
|
||||
"shipping": "shipping",
|
||||
"shippingCalculation": "calculated at checkout",
|
||||
"total": "total",
|
||||
"proceed": "proceed to Checkout",
|
||||
"continue": "continue Shopping",
|
||||
"note": "notes",
|
||||
"notePlaceholder": "enter any notes you would like to include with your order",
|
||||
"addNote": "add a note to your order",
|
||||
"editNote": "edit note",
|
||||
"hideNote": "hide",
|
||||
"saving": "saving...",
|
||||
"addFeaturedProduct": "+ add"
|
||||
},
|
||||
"age-gate": {
|
||||
@ -116,10 +130,10 @@
|
||||
"002": {
|
||||
"title": "brewed in Narai-juku, Nagano.",
|
||||
"para001": "We brew our sake in one of the highest breweries in Japan, standing at an altitude of 940m and surrounded by a mountainous forest of Japanese cedar trees. In Narai-juku, winter temperatures drop nearly 20°C below zero, causing the mountain water to freeze. After the cold winter, the town gets enveloped in fresh greenery as the air turns more clear and pleasant. In autumn, the mountains are adorned with vibrant foliage. Blessed with an abundance of nature, it is a place to truly experience the changing of seasons.",
|
||||
"para002": "Could we express the beauty of the nature around us through our sake? Most sake breweries use large tanks (3,000L to 8,000L) suitable for mass production, often dividing tasks across a large team. At our brewery, we have scaled down to smaller tanks (900L to 1,800L) so that our single master brewer can meticulously and efficiently manage every step of the brewing process – enabling us to produce high-quality sake through small-lot production. While sake is typically produced in the winter, we have incorporated a year-round production method called “shiki-jozo,” or “four-seasons brewing,” so that we can reflect the changing seasons and beautiful nature of Nagano in our sake."
|
||||
"para002": "Most sake breweries use large tanks (3,000L to 8,000L) suitable for mass production, often dividing tasks across a large team. At our brewery, we have scaled down to smaller tanks (900L to 1,800L) so that our single master brewer can meticulously and efficiently manage every step of the brewing process – enabling us to produce high-quality sake through small-lot production. Further, while sake is typically produced in the winter, we have incorporated a year-round production method called “shiki-jozo,” or “four-seasons brewing,” so that we can reflect the changing seasons and beautiful nature of Nagano in our sake."
|
||||
},
|
||||
"003": {
|
||||
"title": "Tasai na Kuro. A Colorful Black. Our Brewing Philosophy.",
|
||||
"title": "A Colorful Black (“Tasai na Kuro”) – Our Brewing Philosophy",
|
||||
"para001": "At first glance, the words “colorful” and “black” together may sound contradictory. Yet the concept of “A Colorful Black” is not about one single color, but rather a mixture of colors, meticulously layered together one paint at a time. For each batch of narai, I imagine myself harmonizing the various elements of the sake’s flavor – richness and fullness, dryness and sweetness, aroma – carefully adjusting each element according to the conditions of the mountain water and rice of that particular batch. A sake that is well-balanced, yet feels like a burst of colors as it spreads across your palate… this is the kind of sake I hope to create. We hope you enjoy narai – “water of the mountains, sake of the skies” – a sake that expresses the beautiful nature of Nagano.",
|
||||
"master001": "Master Brewer",
|
||||
"master002": "Masayuki Irie"
|
||||
@ -129,18 +143,21 @@
|
||||
"subtitle": ""
|
||||
},
|
||||
"materials": {
|
||||
"title": "materials",
|
||||
"title": "the ingredients of narai",
|
||||
"subtitle": "",
|
||||
"water": {
|
||||
"title": "water - the foundation",
|
||||
"body": "While most breweries use well water with stable properties, narai uses natural mountain water flowing from an altitude of over 1,000 meters. This mountain water, which springs near the divide between the Shinano and Kiso Rivers, is one of the clearest in Japan with a hardness of less than 25 and a round, smooth texture."
|
||||
"title": "water -",
|
||||
"subtitle": "the foundation",
|
||||
"body": "While many breweries use a stable water source such as well water, narai uses the fresh mountain water of Kiso Valley, flowing from an altitude of over 1,000m. The water originates from a spring near the watershed of the Shinano River and Kiso River, and is characterized by its clarity and smooth, rounded texture brought about by a rare water hardness of less than 25."
|
||||
},
|
||||
"rice": {
|
||||
"title": "rice - the core",
|
||||
"title": "rice -",
|
||||
"subtitle": "the core",
|
||||
"body": "We use rice that has been carefully cultivated by Farm Ichimaru, a rice farm in Azumino, Nagano. We have a strong commitment to using locally grown rice. Our master brewer personally visits the farm, participates in rice planting, and collaborates with the farmers to carefully select the best rice for brewing narai."
|
||||
},
|
||||
"koji": {
|
||||
"title": "sake yeast & koji (fermentation) - the constituents",
|
||||
"title": "sake yeast & koji - ",
|
||||
"subtitle": "the constituents",
|
||||
"body": "We use Kyokai No.7 sake yeast from Nagano, which is known for its strong fermentability and ability to refine aromas elegantly. For the koji mold that converts rice into sugar, we select a strain that is well-balanced with high enzymatic activity."
|
||||
}
|
||||
},
|
||||
@ -152,12 +169,12 @@
|
||||
"bar": {
|
||||
"001": {
|
||||
"title": {
|
||||
"line001": "sagyobar : a brewery-operated bar & workspace",
|
||||
"line002": "",
|
||||
"line003": ""
|
||||
"line001": "sagyobar:",
|
||||
"line002": "a brewery-operated",
|
||||
"line003": "bar & workspace"
|
||||
},
|
||||
"para001": "Introducing sagyobar: a place where brewery operations (sagyo) and drinking (bar) come together. We have combined our brewery workspace, where we pack boxes and fulfill orders, with a place to enjoy sake served from our very own suginomori wagon.",
|
||||
"para002": "The renovated warehouse was originally used for storing equipment and business supplies left from the old Suginomori Shuzo, the precursor to suginomori brewery that was established in 1793. While maintaining its original structure, we have revamped its entrance and installed new systems such as lighting and air conditioning. We utilize “P-boxes” – containers used to transport sake bottles – that can be flexibly arranged to transform the space into standing bar."
|
||||
"para002": "The renovated warehouse was originally used for storing equipment and business supplies left from our predecessor, the old Suginomori Shuzo. While maintaining its original structure, we have revamped its entrance and installed new features such as lighting and air conditioning. We utilize “P-boxes” – containers used to transport sake bottles – that can be flexibly arranged to transform the space into a standing bar."
|
||||
},
|
||||
"002": "sagyobar is a place where visitors to Narai-juku can enjoy narai, freshly brewed at our brewery just a short walk away. We also plan to host music events, workshops, and other various activities, and turn sagyobar into a new tourist hub in Narai-juku.",
|
||||
"003": {
|
||||
@ -186,7 +203,8 @@
|
||||
},
|
||||
"clerk": {
|
||||
"title": "Banto (Head Clerk): Masaki Nishikawa",
|
||||
"body": "Nishikawa is engaged in manufacturing management, sales management, liquor tax management, general affairs and sales administration. A trustworthy and dependable presence, it is often said that “if you need anything, just ask Nishikawa.” Nishikawa joined Matsumoto Sake Brewery in Kyoto in 2006. After serving as head clerk there for many years, Nishikawa joined suginomori brewery in 2021. He strives to create an environment in which the brewer can fully concentrate on their craft."
|
||||
"001": "From Hiroshima. Nishikawa is engaged in manufacturing management, sales management, liquor tax management, general affairs and sales administration.",
|
||||
"002": "A trustworthy and dependable presence, it is often said that “if you need anything, just ask Nishikawa.” Nishikawa joined Matsumoto Sake Brewery in Kyoto in 2006. After serving as head clerk there for many years, Nishikawa joined suginomori brewery in 2021. He strives to create an environment in which the brewer can fully concentrate on their craft."
|
||||
}
|
||||
},
|
||||
"concept": {
|
||||
@ -194,15 +212,14 @@
|
||||
"para001": "At suginomori brewery, our mission is to pass down Japanese sake culture to future generations.",
|
||||
"para002": "To achieve this, we reexamine conventional practices of the sake industry and experiment with new endeavors.",
|
||||
"para003": "We aim to go beyond brewing – exploring sake with a free and creative approach, to help spread the beauty of sake to the world.",
|
||||
"subtitle001": "about suginomori brewery",
|
||||
"subtitle001": "about suginomori brewery:",
|
||||
"para004": "In 2021, we restored Suginomori Shuzo, a sake brewery nestled in the historic town of Narai-juku, established in 1793 but closed down in 2012. After upgrading the facilities and optimizing the space to a third of its original size, it was reborn as suginomori brewery.",
|
||||
"subtitle002": "our approach",
|
||||
"para005": "In the process of revitalization, we found that the number of sake breweries, which used to be close to local life, has been decreasing year by year due to the decline in domestic demand for sake, and that abandoned farmland has been growing along with it. We wanted to take these issues seriously.",
|
||||
"para006": "We listen to what farmers have to say and visit their rice fields.",
|
||||
"para007": "We will realize a stable production environment by introducing a new four-season brewing method that allows year-round brewing.",
|
||||
"para008": "These are small steps, but we believe that these efforts will lead to the future.",
|
||||
"para009": "One of our challenges is to become an “open brewery”. Our brewery is adjacent to an old private house on the same site and the restaurant of BYAKU Narai, an accommodation facility that was partially renovated from a former sake brewery. The brewery is separated from the restaurant by a single large glass partition, and we believe that this will provide an opportunity for visitors to experience the sake brewing process up close, which is usually difficult to see, and to develop an interest in sake.",
|
||||
"para010": "Finally, the brand name “narai” was chosen with the hope that our sake will be a catalyst for people around the world to learn about and visit the charming inn town of Naraijuku. We would be happy if you could stop by suginomori brewery when you come to Narai."
|
||||
"para005": "In the process of the renewal, we sought to confront some of the challenges faced by the sake industry in recent years: a decline in domestic demand for sake, the subsequent closing down of many local sake breweries and rise in abandoned farmland, and unstable employment due to sake typically being produced only in the winter months.",
|
||||
"para006": "We listened to the voices of farmers. We visited the rice paddies, and took part in the rice cultivation process. We incorporated a year-round production method called “shiki-jozo,” or “four-seasons brewing,” to ensure a more stable production environment. Although seemingly small steps, we believe that these are some of the building blocks for a more promising future.",
|
||||
"para007": "Additionally, as part of our endeavor, we sought to transform the brewery into what we call an “open brewery.” suginomori brewery is adjoined to the restaurant of BYAKU Narai, an accommodation facility renovated from part of the former Suginomori Shuzo. Visitors can look into the brewery from a large glass window in the restaurant, providing them with a rare up-close view of the sake brewing process. Through opening and showcasing our brewery, we hope to inspire more people to take an interest in sake.",
|
||||
"para008": "Finally, our brand name “narai” is derived from the town of Narai-juku – chosen from our hope to introduce this charming town to the world through our sake. If you ever find yourself in Narai-juku, we would be delighted to welcome you to suginomori brewery.",
|
||||
"para009": "",
|
||||
"para010": ""
|
||||
},
|
||||
"company": {
|
||||
"title": "company",
|
||||
@ -213,7 +230,7 @@
|
||||
},
|
||||
"director": {
|
||||
"label": "ceo",
|
||||
"value": "Kou Sundberg William"
|
||||
"value": "Kou William Sundberg"
|
||||
},
|
||||
"since": {
|
||||
"label": "Founded",
|
||||
@ -231,32 +248,37 @@
|
||||
"irie": {
|
||||
"japanese": "入江将之",
|
||||
"english": "Masayuki Irie",
|
||||
"role": "杜氏"
|
||||
"role": "Master Brewer"
|
||||
},
|
||||
"nishikawa": {
|
||||
"japanese": "西川正貴",
|
||||
"english": "Masataka Nishikawa",
|
||||
"role": "番頭"
|
||||
"role": "Banto (head clerk)"
|
||||
},
|
||||
"sundberg": {
|
||||
"japanese": "サンドバーグ弘",
|
||||
"english": "Kou Sundberg",
|
||||
"role": "蔵元"
|
||||
"english": "Kou William Sundberg",
|
||||
"role": "Chief Executive Officer"
|
||||
},
|
||||
"masa": {
|
||||
"japanese": "小松正則",
|
||||
"english": "Masanori Komatsu",
|
||||
"role": "Business Development"
|
||||
},
|
||||
"yamano": {
|
||||
"japanese": "山野恭稔",
|
||||
"english": "Takatoshi Yamano",
|
||||
"role": "クリエイティブディレクター"
|
||||
"role": "Creative Director"
|
||||
},
|
||||
"yoshida": {
|
||||
"japanese": "吉田有紗",
|
||||
"english": "Alisa Yoshida",
|
||||
"role": "PR・マーケティング"
|
||||
"role": "PR & Marketing"
|
||||
},
|
||||
"ikegaya": {
|
||||
"japanese": "池谷晋也",
|
||||
"english": "Shinya Ikegaya",
|
||||
"role": "営業戦略"
|
||||
"role": "Sales Strategy"
|
||||
},
|
||||
"subtitle003": "movie"
|
||||
},
|
||||
@ -469,57 +491,76 @@
|
||||
"ariaLabel": "Contact our support email"
|
||||
}
|
||||
},
|
||||
"disclosurePage": {
|
||||
"disclosure-page": {
|
||||
"title": "Disclosures",
|
||||
"distributor": {
|
||||
"label": "販売業者",
|
||||
"value": "杉の森酒造株式会社"
|
||||
"label": "Company Name",
|
||||
"value": "suginomori brewery inc."
|
||||
},
|
||||
"representative": {
|
||||
"label": "責任者",
|
||||
"value": "代表取締役 サンドバーグ弘ウィリアム"
|
||||
"label": "Representative",
|
||||
"value": "Kou William Sundberg, Representative Director and President"
|
||||
},
|
||||
"address": {
|
||||
"label": "住所",
|
||||
"one": "〒399-6303",
|
||||
"two": "長野県塩尻市奈良井551-1"
|
||||
"label": "Address",
|
||||
"one": "551-1 Narai, Shiojiri City,",
|
||||
"two": "Nagano, Japan 399-6303"
|
||||
},
|
||||
"phone": {
|
||||
"label": "電話番号",
|
||||
"label": "Phone",
|
||||
"value": "0264-24-0340"
|
||||
},
|
||||
"email": {
|
||||
"label": "メールアドレス",
|
||||
"label": "Email",
|
||||
"value": "info@narai.jp"
|
||||
},
|
||||
"homepage": {
|
||||
"label": "ホームページ",
|
||||
"label": "URL",
|
||||
"value": "https://www.narai.jp/"
|
||||
},
|
||||
"price": {
|
||||
"label": "商品の販売価格",
|
||||
"value": "各商品ページをご参照ください。"
|
||||
},
|
||||
"otherCharges": {
|
||||
"label": "商品以外の必要料金",
|
||||
"value": "配送料:1本500円。2本800円。3本以上のご購入で送料一律1,000円となります。"
|
||||
"label": "Product price",
|
||||
"value": "Please refer to each product page."
|
||||
},
|
||||
"paymentMethod": {
|
||||
"label": "支払方法",
|
||||
"value": "クレジットカード決済"
|
||||
"label": "Payment method",
|
||||
"value": "Credit card"
|
||||
},
|
||||
"paymentPeriod": {
|
||||
"label": "支払時期",
|
||||
"value": "クレジットカード決済:商品注文時にお支払いが確定します。"
|
||||
"label": "Payment timing",
|
||||
"value": "Credit card payment will be confirmed at the time of product order."
|
||||
},
|
||||
"delivery": {
|
||||
"label": "商品の引渡時期",
|
||||
"value": "ご注文確認後、直ちに商品を発送いたします。(通常、商品の発送までに3営業日前後の時間がかかります。天候不順や配送業者の都合、その他何らかの理由により商品の発送が遅延する場合は、メールにてお知らせいたします。)"
|
||||
"label": "Product delivery time",
|
||||
"value": "Your order will typically be shipped within 3 business days upon order confirmation. (In case of delay due to weather, delivery service availability, or any other reason, we will notify you by email)."
|
||||
},
|
||||
"returnsAndExchanges": {
|
||||
"label": "返品・交換",
|
||||
"one": "お客さま都合による返品・交換は対応いたしかねますので予めご了承ください。商品の欠陥による返品・交換は、商品到着後7日以内にご購入者のお名前・住所を送り状に明記の上、下記住所まで着払いにてご返送ください。",
|
||||
"two": "〒399-6303 長野県塩尻市奈良井551-1"
|
||||
"label": "Returns and exchanges",
|
||||
"one": "Please note that we can only accept returns and exchanges due to product defects. Please return the product with your name and address written on the shipping label within 7 days of receiving the product, at your own expense, to the following address:",
|
||||
"two": "551-1 Narai, Shiojiri City, Nagano, Japan 399-6303"
|
||||
},
|
||||
"legal": {
|
||||
"title": "Alcoholic Beverage Sales Disclosure",
|
||||
"001": {
|
||||
"label": "Name and location of where liquor is sold",
|
||||
"value": "suginomori brewery inc."
|
||||
},
|
||||
"002": {
|
||||
"label": "Name of Liquor Sales Manager",
|
||||
"value": "Masataka Nishikawa"
|
||||
},
|
||||
"003": {
|
||||
"label": "Date of attendance of the Liquor Sales Management Training",
|
||||
"value": "September 11, 2020"
|
||||
},
|
||||
"004": {
|
||||
"label": "Attendance Deadline of the next Liquor Sales Management Training",
|
||||
"value": "August 31, 2023"
|
||||
},
|
||||
"005": {
|
||||
"label": "Implementing Agency of the Liquor Sales Management Training",
|
||||
"value": "Japan Voluntary Chain Association"
|
||||
}
|
||||
},
|
||||
"return": "Return Home"
|
||||
}
|
||||
|
@ -23,7 +23,7 @@
|
||||
},
|
||||
"newsletter": {
|
||||
"title": "newsletter",
|
||||
"description": "ニュースレターにご登録いただくと、初回送料無料クーポン、購読者限定の情報やペアリングディナーなどのご案内をお送りさせていただきます。",
|
||||
"description": "ニュースレターにご登録いただくと、購読者限定の情報やペアリングディナーなどのご案内をお送りさせていただきます。",
|
||||
"placeholder": "メールアドレス",
|
||||
"button": "登録する"
|
||||
},
|
||||
@ -32,7 +32,7 @@
|
||||
"about-narai": {
|
||||
"title": "山の水、",
|
||||
"subtitle": "空に一番近い酒",
|
||||
"body": "標高約940mの日本でも有数の空に近い自然豊かな環境で醸造しています。多くの酒蔵が水の性質が安定している井戸水を使用しますが、naraiは標高1,000m以上から流れる天然の山水を使用。信濃川と木曽川の分水嶺付近の湧き水であるこの山水は、日本でも有数な「硬度25以下」の透明感と丸みのある滑らかな舌触りが特徴です。"
|
||||
"body": "標高約940mの日本でも有数の空に近い自然に恵まれた環境で醸造しています。多くの酒蔵が水の性質が安定している井戸水を使用しますが、naraiは標高1,000m以上から流れる天然の山水を使用。信濃川と木曽川の分水嶺付近の湧き水であるこの山水は、日本でも有数な「硬度25以下」の透明感と丸みのある滑らかな舌触りが特徴です。"
|
||||
},
|
||||
"location": {
|
||||
"title": "長野",
|
||||
@ -46,18 +46,19 @@
|
||||
"line002": "角打ち&作業場",
|
||||
"line003": "sagyobar"
|
||||
},
|
||||
"body": "ここは、ボトルの箱詰めや出荷などの酒蔵作業 (sagyo) と、日本酒移動販売車「suginomori wagon」から提供されるSAKEを楽しむこともできる場(bar)が融合した、弊蔵の直営店です。酒蔵から徒歩1分。線路を挟み向かいの倉庫をリノベーションしました。",
|
||||
"body": "ここは、ボトルの箱詰めや出荷などの酒蔵作業 (sagyo) と、日本酒移動販売車「suginomori wagon」から提供されるsakeを楽しむこともできる場(bar)が融合した、弊蔵の直営店です。酒蔵から徒歩1分。線路を挟み向かいの倉庫をリノベーションしました。",
|
||||
"button": "sagyobarについて"
|
||||
},
|
||||
"concept": {
|
||||
"title": "醸造のその先へ",
|
||||
"subtitle": "",
|
||||
"body": "私たちには、日本酒文化を未来に継承したいという信念があります。そのためには、これまでの常識をもう一度見つめ直すことや、新しい試みにも挑戦する。醸造のその先へ、自由な発想でSAKEを探究し、その魅力を伝えていきます。",
|
||||
"body": "私たちには、日本酒文化を未来に継承したいという信念があります。そのためには、これまでの常識をもう一度見つめ直すことや、新しい試みにも挑戦する。醸造のその先へ、自由な発想でsakeを探究し、その魅力を伝えていきます。",
|
||||
"button": "concept"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shops": {
|
||||
"top": "トップ",
|
||||
"title": "取り扱い店",
|
||||
"subtitle": "取り扱い店",
|
||||
"all": "全店",
|
||||
@ -70,12 +71,23 @@
|
||||
},
|
||||
"footer": {
|
||||
"newsletter": {
|
||||
"promo": "初回注文で10%割引。割引を受ける。"
|
||||
"promo": "購読者限定の情報やペアリングディナーなどのご案内をお送りさせていただきます。"
|
||||
},
|
||||
"menu": {
|
||||
"products": "商品",
|
||||
"shops": "取り扱い店",
|
||||
"about": "naraiについて",
|
||||
"bar": "sagyobar",
|
||||
"concept": "コンセプト",
|
||||
"stories": "ストーリー",
|
||||
"company": "会社概要"
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"add": "カートに入れる",
|
||||
"add": "add to cart",
|
||||
"out-of-stock": "品切れ中",
|
||||
"options": "オプション",
|
||||
"quantity-label": "購入数",
|
||||
"title": "ショッピングカード",
|
||||
"subtitle": "ご注文内容の確認",
|
||||
"empty": "買い物袋が空っぽ",
|
||||
@ -110,17 +122,17 @@
|
||||
},
|
||||
"about": {
|
||||
"001": {
|
||||
"title": "山の水",
|
||||
"title": "山の水、",
|
||||
"subtitle": "空に一番近い酒"
|
||||
},
|
||||
"002": {
|
||||
"title": "長野 奈良井宿で醸す",
|
||||
"para001": "私たちは、標高約940mの日本でも有数の空に近い自然豊かな環境で醸造しています。冬は氷点下20度近くまで冷え込み、山の水は凍ります。寒い冬を越えると、新緑に囲まれ、空気が清らかで過ごしやすい季節が続きます。秋には紅葉で山が鮮やかに染まります。日本らしい四季の移ろいを感じられる自然豊かな立地です。",
|
||||
"para002": "この自然豊かな魅力を贅沢に味で表現したい… 通常の酒蔵では大きなチームが分業制で大量生産ができるよう、大型タンク(3,000L〜8,000Lなど)を使用しますが、私たちの蔵では杜氏1人でも丁寧な手作業で、効率的に全工程に関わり、小ロット生産ながらも高品質なSAKE造りができるよう900Lと1,800Lの小型タンクを採用しています。さらに、その時々に感じる季節の移ろいや、自然の美しさを味に反映させるために、年中生産可能な四季醸造方式を取り入れました。"
|
||||
"para001": "私たちは、標高約940mの日本でも有数の空に近い自然豊かな環境で醸造しています。冬は氷点下20度近くまで冷え込み、山の水は凍ります。寒い冬を越えると、新緑に囲まれ、空気が清らかで過ごしやすい季節が続きます。秋には紅葉で山が鮮やかに染まります。日本らしい四季の移ろいを感じられる立地です。",
|
||||
"para002": "この自然豊かな魅力を贅沢に味で表現したい… 通常の酒蔵では大きなチームが分業制で大量生産ができるよう、大型タンク(3,000L〜8,000Lなど)を使用しますが、私たちの蔵では杜氏1人でも丁寧な手作業で、効率的に全工程に関わり、小ロット生産ながらも高品質なsake造りができるよう900Lと1,800Lの小型タンクを採用しています。また、多くの酒蔵が冬季のみ醸造をする中、その時々に感じる季節の移ろいや、自然の美しさを味に反映させるために、年中生産可能な四季醸造方式を取り入れました。"
|
||||
},
|
||||
"003": {
|
||||
"title": "多彩な黒 – 醸造の理念",
|
||||
"para001": "多彩」と「黒」は、一見相反するように聞こえますが、僕の目指す「多彩な黒」は1色ではなく、さまざまな絵の具で丁寧に色を重ね合わせて生まれる色をイメージしています。仕込みごとに山水や米の状態に合わせ、旨味やふくよかさ、辛味や甘味、香り等の要素を経験に基づく手作業で調和させることで、「全体ではまとまりを感じるが、口の中に広がった時に多彩な色を感じる」、そんな1 本を目指して醸造を探究しています。信州の自然を贅沢に表現した「山の水、空に一番近い酒」を是非お楽しみください。",
|
||||
"para001": "「多彩」と「黒」は、一見相反するように聞こえますが、僕の目指す「多彩な黒」は1色ではなく、さまざまな絵の具で丁寧に色を重ね合わせて生まれる色をイメージしています。仕込みごとに山水や米の状態に合わせ、旨味やふくよかさ、辛味や甘味、香り等の要素を経験に基づく手作業で調和させることで、「全体ではまとまりを感じるが、口の中に広がった時に多彩な色を感じる」、そんな1 本を目指して醸造を探究しています。信州の自然を贅沢に表現した「山の水、空に一番近い酒」を是非お楽しみください。",
|
||||
"master001": "Master Brewer",
|
||||
"master002": "Masayuki Irie"
|
||||
},
|
||||
@ -132,15 +144,18 @@
|
||||
"title": "自然を紡ぐ",
|
||||
"subtitle": " – naraiの素材",
|
||||
"water": {
|
||||
"title": "水 – 土台",
|
||||
"title": "水 –",
|
||||
"subtitle": "土台",
|
||||
"body": "多くの酒蔵が水の性質が安定している井戸水を使用しますが、naraiは標高1,000m以上から流れる天然の山水を使用。信濃川と木曽川の分水嶺付近の湧き水であるこの山水は、日本でも有数な「硬度25以下」の透明感と丸みのある滑らかな舌触りが特徴です。"
|
||||
},
|
||||
"rice": {
|
||||
"title": "米 – 幹",
|
||||
"title": "米 –",
|
||||
"subtitle": "幹",
|
||||
"body": "長野県安曇野エリアで、日本アルプスの恵みで農家を営む「ファームいちまる」によって、丁寧に育てられた米を使用しています。地産米にこだわり、杜氏自ら農家に通い、田植えなどを行いながら相談し、米を厳選しています。"
|
||||
},
|
||||
"koji": {
|
||||
"title": "酵母と麹 - 構成要素(発酵)",
|
||||
"title": "酵母と麹 -",
|
||||
"subtitle": "構成要素(発酵)",
|
||||
"body": "酵母は発酵力が強く、香りを上品にまとめることができることが特徴だと考えている長野由来の協会第7号を使用しています。米を糖に変える麹菌は、酵素力価の良いバランスの取れたものを採用しています。"
|
||||
}
|
||||
},
|
||||
@ -159,7 +174,7 @@
|
||||
"para001": "ここは、ボトルの箱詰めや出荷などの酒蔵作業 (sagyo) と、日本酒移動販売車「suginomori wagon」から提供されるsakeを楽しむこともできる場(bar)が融合した、弊蔵の直営店です。",
|
||||
"para002": "リノベーションした倉庫は、元々は先代の頃の杉の森酒蔵の機材や業務備品の保管用の場所として利用されていました。倉庫の建築はそのままに、入り口部分のゲートを刷新し、照明や空調等の設備を新設。什器は酒蔵の作業でも使用する酒瓶を運ぶための外装容器「P箱」を活用しています。このP箱を自在にレイアウトすることで、角打ちのようなスペースとしても活用できます。"
|
||||
},
|
||||
"002": "奈良井宿へ観光で来られたお客様が、隣接する酒蔵で醸造したてのnaraiをお楽しみいただけるだけではなく、音楽イベントや、ワークショップなども企画中。さまざまな利用シーンを演出することで、奈良井宿の新たな観光スポットの1つになることを目指していきます。",
|
||||
"002": "奈良井宿へ観光で来られたお客さまが、隣接する酒蔵で醸造したてのnaraiをお楽しみいただけるだけではなく、音楽イベントや、ワークショップなども企画中。さまざまな利用シーンを演出することで、奈良井宿の新たな観光スポットの1つになることを目指していきます。",
|
||||
"003": {
|
||||
"title": "suginomori wagon",
|
||||
"body": "sagyobarでは、日本酒サーバーが搭載された移動販売車「suginomori wagon」を収容しており、naraiの飲み比べセットや、地域限定メニュー、オリジナルグッズなどの販売をしています。sagyobarでの活用はもちろんのこと、移動可能な販売空間として、長野県内や、全国のさまざまなイベントに出店しnaraiをお届けします。"
|
||||
@ -186,7 +201,8 @@
|
||||
},
|
||||
"clerk": {
|
||||
"title": "番頭:西川正貴",
|
||||
"body": "製造管理、販売管理、酒税管理、総務、営業事務に携わる。「何かあれば西川に」が合言葉の頼れる存在。2006年に京都府の松本酒造に入社し、長年番頭を務めたのち、2021年より杉の森酒造に着任。製造者が最も集中できる環境づくりを心掛け、日々業務に取り組んでいる。"
|
||||
"001": "広島出身。製造管理、販売管理、酒税管理、総務、営業事務に携わる。",
|
||||
"002": "「何かあれば西川に」が合言葉の頼れる存在。2006年に京都府の松本酒造に入社し、長年番頭を務めたのち、2021年より杉の森酒造に着任。製造者が最も集中できる環境づくりを心掛け、日々業務に取り組んでいる。"
|
||||
}
|
||||
},
|
||||
"concept": {
|
||||
@ -196,11 +212,10 @@
|
||||
"para003": "醸造のその先へ、自由な発想でsakeを探究し、その魅力を伝えていきます。",
|
||||
"subtitle001": "suginomori brewery (当蔵) について",
|
||||
"para004": "⻑野県に位置する日本最長の宿場町「奈良井宿」の歴史的な街並みの中で、1793年に創業し、2012年頃より休眠状態だった老舗酒蔵「杉の森酒造」を、2021年に製造面積を約1/3のサイズに最適化し、設備を一新する形で「suginomori brewery」として再生しました。",
|
||||
"subtitle002": "私たちが想うこと—",
|
||||
"para005": "再生にあたって、近年では日本酒の国内需要低下に伴い、地域の暮らしに密接だった酒蔵が年々減少し、それに伴い耕作放棄地が増えている課題や、一般的な酒蔵では冬季のみ酒造りを行うため働き手の仕事が安定しないことがわかり、私たちの酒蔵ではこれらの課題を真摯に受け止めたいと考えました。",
|
||||
"para006": "農家さんの言葉に耳を傾け、田んぼに足を運ぶ。",
|
||||
"para006": "農家さんの言葉に耳を傾け、田んぼに足を運び、米作りの段階から一緒に携わっていく。",
|
||||
"para007": "年中仕込みが可能な四季醸造方式を新たに導入することで安定した製造環境を実現する。",
|
||||
"para008": "・・・小さなことの積み重ねですが、これらの取り組みが未来に繋がると信じています。",
|
||||
"para008": "・・・このような小さなことの積み重ねですが、これらの取り組みが未来に繋がると信じています。",
|
||||
"para009": "またこれらに関連し、私たちが挑戦したことの1つが、「開かれた酒蔵」としての在り方です。弊蔵は、同敷地内の古民家や、旧酒蔵の一部を改修した宿泊施設(BYAKU Narai)のレストランに隣接しています。大きなガラスの仕切り1枚で仕切られており、普段はなかなか見ることの出来ない酒造りを間近で感じられることは、日本酒に興味を持って頂くきっかけになると信じています。",
|
||||
"para010": "最後に、ブランド名の【narai】は、「私たちのsakeをきっかけに、奈良井宿という魅力的な宿場町を世界の人に知ってもらい、足を運んで頂きたい」という想いを込めて名付けました。奈良井に来られる際には、suginomori breweryに立ち寄って頂けたら嬉しいです。"
|
||||
},
|
||||
@ -243,6 +258,11 @@
|
||||
"english": "Kou Sundberg",
|
||||
"role": "蔵元"
|
||||
},
|
||||
"masa": {
|
||||
"japanese": "小松正宜",
|
||||
"english": "Masanori Komatsu",
|
||||
"role": "事業開発"
|
||||
},
|
||||
"yamano": {
|
||||
"japanese": "山野恭稔",
|
||||
"english": "Takatoshi Yamano",
|
||||
@ -469,7 +489,7 @@
|
||||
"ariaLabel": "Contact our support email"
|
||||
}
|
||||
},
|
||||
"disclosurePage": {
|
||||
"disclosure-page": {
|
||||
"title": "Disclosures",
|
||||
"distributor": {
|
||||
"label": "販売業者",
|
||||
@ -500,10 +520,6 @@
|
||||
"label": "商品の販売価格",
|
||||
"value": "各商品ページをご参照ください。"
|
||||
},
|
||||
"otherCharges": {
|
||||
"label": "商品以外の必要料金",
|
||||
"value": "配送料:1本500円。2本800円。3本以上のご購入で送料一律1,000円となります。"
|
||||
},
|
||||
"paymentMethod": {
|
||||
"label": "支払方法",
|
||||
"value": "クレジットカード決済"
|
||||
@ -521,6 +537,29 @@
|
||||
"one": "お客さま都合による返品・交換は対応いたしかねますので予めご了承ください。商品の欠陥による返品・交換は、商品到着後7日以内にご購入者のお名前・住所を送り状に明記の上、下記住所まで着払いにてご返送ください。",
|
||||
"two": "〒399-6303 長野県塩尻市奈良井551-1"
|
||||
},
|
||||
"legal": {
|
||||
"title": "酒類販売管理者標識",
|
||||
"001": {
|
||||
"label": "販売場の名称及び所在地",
|
||||
"value": "杉の森酒造株式会社"
|
||||
},
|
||||
"002": {
|
||||
"label": "酒類販売管理者の氏名",
|
||||
"value": "西川正貴"
|
||||
},
|
||||
"003": {
|
||||
"label": "酒類販売管理研修受講年月日",
|
||||
"value": "令和2年9月11日"
|
||||
},
|
||||
"004": {
|
||||
"label": "次回研修の受講期限",
|
||||
"value": "令和5年8月31日"
|
||||
},
|
||||
"005": {
|
||||
"label": "研修実施団体名",
|
||||
"value": "一般社団法人 日本ボランタリーチェーン協会"
|
||||
}
|
||||
},
|
||||
"return": "Return Home"
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,8 @@
|
||||
"react": "latest",
|
||||
"react-dom": "latest",
|
||||
"react-intersection-observer": "^9.5.2",
|
||||
"react-player": "^2.12.0"
|
||||
"react-player": "^2.12.0",
|
||||
"sharp": "^0.32.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
|
Before Width: | Height: | Size: 345 KiB After Width: | Height: | Size: 264 KiB |
Before Width: | Height: | Size: 353 KiB After Width: | Height: | Size: 283 KiB |
Before Width: | Height: | Size: 346 KiB After Width: | Height: | Size: 306 KiB |
Before Width: | Height: | Size: 877 KiB After Width: | Height: | Size: 484 KiB |
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 169 KiB After Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 860 KiB After Width: | Height: | Size: 659 KiB |
Before Width: | Height: | Size: 473 KiB After Width: | Height: | Size: 406 KiB |
Before Width: | Height: | Size: 550 KiB After Width: | Height: | Size: 522 KiB |
Before Width: | Height: | Size: 507 KiB After Width: | Height: | Size: 410 KiB |
Before Width: | Height: | Size: 741 KiB After Width: | Height: | Size: 405 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 43 KiB |