-
{t('home.previews.about-narai.title')}
-
{t('home.previews.about-narai.subtitle')}
+
{t('home.previews.about-narai.title')}
+
{t('home.previews.about-narai.subtitle')}
-
+
{t('home.previews.about-narai.body')}
diff --git a/components/layout/concept-preview.tsx b/components/layout/concept-preview.tsx
new file mode 100644
index 000000000..5d38bdbe2
--- /dev/null
+++ b/components/layout/concept-preview.tsx
@@ -0,0 +1,25 @@
+'use client';
+import { useTranslations } from 'next-intl';
+import Link from 'next/link';
+
+export default function ConceptPreview() {
+ const t = useTranslations('Index');
+
+ return (
+
+
+
{t('home.previews.concept.title')}
+
{t('home.previews.concept.subtitle')}
+
+
+
{t('home.previews.concept.body')}
+
+ {t('home.previews.concept.button')}
+
+
+
+ );
+}
diff --git a/components/layout/location-preview.tsx b/components/layout/location-preview.tsx
new file mode 100644
index 000000000..72884299c
--- /dev/null
+++ b/components/layout/location-preview.tsx
@@ -0,0 +1,25 @@
+'use client';
+import { useTranslations } from 'next-intl';
+import Link from 'next/link';
+
+export default function LocationPreview() {
+ const t = useTranslations('Index');
+
+ return (
+
+
+
{t('home.previews.location.title')}
+
{t('home.previews.location.subtitle')}
+
+
+
{t('home.previews.location.body')}
+
+ {t('home.previews.location.button')}
+
+
+
+ );
+}
diff --git a/components/layout/sagyobar-preview.tsx b/components/layout/sagyobar-preview.tsx
new file mode 100644
index 000000000..2a146e70a
--- /dev/null
+++ b/components/layout/sagyobar-preview.tsx
@@ -0,0 +1,25 @@
+'use client';
+import { useTranslations } from 'next-intl';
+import Link from 'next/link';
+
+export default function SagyobarPreview() {
+ const t = useTranslations('Index');
+
+ return (
+
+
+
{t('home.previews.bar.title')}
+
{t('home.previews.bar.subtitle')}
+
+
+
{t('home.previews.bar.body')}
+
+ {t('home.previews.bar.button')}
+
+
+
+ );
+}
diff --git a/components/layout/stories.tsx b/components/layout/stories.tsx
new file mode 100644
index 000000000..e2bd95d84
--- /dev/null
+++ b/components/layout/stories.tsx
@@ -0,0 +1,68 @@
+import clsx from 'clsx';
+import { getBlog } from 'lib/shopify';
+import Image from 'next/image';
+import Link from 'next/link';
+import { SupportedLocale } from './navbar/language-control';
+
+export default async function Stories({
+ locale,
+ handle,
+ articles
+}: {
+ locale?: SupportedLocale;
+ handle: string;
+ articles?: number;
+}) {
+ const blog = await getBlog({
+ handle: 'headless',
+ articles: articles || 3,
+ language: locale?.toUpperCase()
+ });
+ console.debug({ blog });
+
+ if (!blog) return null;
+
+ return (
+
+
+
stories
+
+ {blog?.articles?.map((article) => (
+
+
+ {!!article?.image?.url && (
+
+ )}
+
+
{article?.title}
+
{article?.excerpt}
+
+ ))}
+
+
+
+ more stories
+
+
+
+
+ );
+}
diff --git a/lib/shopify/index.ts b/lib/shopify/index.ts
index 107a7797e..271013d11 100644
--- a/lib/shopify/index.ts
+++ b/lib/shopify/index.ts
@@ -9,6 +9,7 @@ import {
editCartItemsMutation,
removeFromCartMutation
} from './mutations/cart';
+import { getBlogQuery } from './queries/blog';
import { getCartQuery } from './queries/cart';
import {
getCollectionProductsQuery,
@@ -23,6 +24,7 @@ import {
getProductsQuery
} from './queries/product';
import {
+ Blog,
Cart,
Collection,
Connection,
@@ -31,6 +33,8 @@ import {
Page,
Product,
ShopifyAddToCartOperation,
+ ShopifyBlog,
+ ShopifyBlogOperation,
ShopifyCart,
ShopifyCartOperation,
ShopifyCollection,
@@ -167,6 +171,19 @@ const reshapeImages = (images: Connection
, productTitle: string) => {
});
};
+const reshapeBlog = (blog: ShopifyBlog) => {
+ if (!blog) {
+ return undefined;
+ }
+
+ const { articles, ...rest } = blog;
+
+ return {
+ ...rest,
+ articles: removeEdgesAndNodes(articles)
+ };
+};
+
const reshapeProduct = (product: ShopifyProduct, filterHiddenProducts: boolean = true) => {
if (!product || (filterHiddenProducts && product.tags.includes(HIDDEN_PRODUCT_TAG))) {
return undefined;
@@ -397,6 +414,25 @@ export async function getPages({
return removeEdgesAndNodes(res.body.data.pages);
}
+export async function getBlog({
+ handle,
+ articles,
+ language,
+ country
+}: {
+ handle: string;
+ articles?: number;
+ language?: string;
+ country?: string;
+}): Promise {
+ const res = await shopifyFetch({
+ query: getBlogQuery,
+ variables: { handle, articles, language, country }
+ });
+
+ return reshapeBlog(res.body.data.blogByHandle);
+}
+
export async function getProduct({
handle,
language,
diff --git a/lib/shopify/queries/blog.ts b/lib/shopify/queries/blog.ts
new file mode 100644
index 000000000..4c631161e
--- /dev/null
+++ b/lib/shopify/queries/blog.ts
@@ -0,0 +1,61 @@
+import seoFragment from '../fragments/seo';
+
+const blogFragment = /* GraphQL */ `
+ fragment blog on Blog {
+ ... on Blog {
+ id
+ title
+ handle
+ articles(first: $articles) {
+ edges {
+ node {
+ id
+ title
+ handle
+ excerpt
+ content
+ contentHtml
+ image {
+ url
+ altText
+ width
+ height
+ }
+ seo {
+ ...seo
+ }
+ publishedAt
+ }
+ }
+ }
+ seo {
+ ...seo
+ }
+ }
+ }
+ ${seoFragment}
+`;
+
+export const getBlogQuery = /* GraphQL */ `
+ query getBlog($handle: String!, $articles: Int, $country: CountryCode, $language: LanguageCode)
+ @inContext(country: $country, language: $language) {
+ blogByHandle(handle: $handle) {
+ ...blog
+ }
+ }
+ ${blogFragment}
+`;
+
+export const getBlogsQuery = /* GraphQL */ `
+ query getBlogs($country: CountryCode, $language: LanguageCode)
+ @inContext(country: $country, language: $language) {
+ blogs(first: 100) {
+ edges {
+ node {
+ ...blog
+ }
+ }
+ }
+ }
+ ${blogFragment}
+`;
diff --git a/lib/shopify/types.ts b/lib/shopify/types.ts
index 2a7479067..af407c17a 100644
--- a/lib/shopify/types.ts
+++ b/lib/shopify/types.ts
@@ -61,6 +61,22 @@ export type Page = {
updatedAt: string;
};
+export type Blog = Omit & {
+ articles: BlogArticle[];
+};
+
+export type BlogArticle = {
+ id: string;
+ title: string;
+ handle: string;
+ content: string;
+ contentHtml: string;
+ excerpt: string;
+ publishedAt: string;
+ image?: Image;
+ seo?: SEO;
+};
+
export type Product = Omit & {
variants: ProductVariant[];
images: Image[];
@@ -88,6 +104,18 @@ export type SEO = {
description: string;
};
+export type ShopifyBlog = {
+ id: string;
+ title: string;
+ handle: string;
+ content: string;
+ contentHtml: string;
+ excerpt: string;
+ articles: Connection;
+ seo?: SEO;
+ image?: Image;
+};
+
export type ShopifyCart = {
id: string;
checkoutUrl: string;
@@ -248,6 +276,11 @@ export type ShopifyPagesOperation = {
};
};
+export type ShopifyBlogOperation = {
+ data: { blogByHandle: ShopifyBlog };
+ variables: { handle: string; articles?: number; language?: string; country?: string };
+};
+
export type ShopifyProductOperation = {
data: { product: ShopifyProduct };
variables: {
diff --git a/messages/en.json b/messages/en.json
index 3eee3f7e7..756a98e9f 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -30,6 +30,24 @@
"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."
+ },
+ "location": {
+ "title": "brewed in Narai-juku, Nagano",
+ "subtitle": "",
+ "body": "Our brewery is nestled in the historic townscape of Narai-juku, a well-preserved post town in Nagano stretching for about 1km – the longest of its kind in Japan. 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.",
+ "button": "about narai"
+ },
+ "bar": {
+ "title": "sagyobar : a brewery-operated bar & workspace",
+ "subtitle": "",
+ "body": "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. It is a renovated warehouse located a one-minute walk from the brewery, just across the railway tracks.",
+ "button": "about sagyobar"
+ },
+ "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.",
+ "button": "concept"
}
}
},
diff --git a/messages/ja.json b/messages/ja.json
index 5325a5312..36cdda55d 100644
--- a/messages/ja.json
+++ b/messages/ja.json
@@ -30,6 +30,24 @@
"title": "山の水、",
"subtitle": "空に一番近い酒",
"body": "標高約940mの日本でも有数の空に近い自然豊かな環境で醸造しています。多くの酒蔵が水の性質が安定している井戸水を使用しますが、naraiは標高1,000m以上から流れる天然の山水を使用。信濃川と木曽川の分水嶺付近の湧き水であるこの山水は、日本でも有数な「硬度25以下」の透明感と丸みのある滑らかな舌触りが特徴です。"
+ },
+ "location": {
+ "title": "長野",
+ "subtitle": "奈良井宿で醸す",
+ "body": "⻑野県に位置する日本最長の宿場町「奈良井宿」の歴史的な街並みの中で醸造しています。奈良井宿の冬は氷点下20度近くまで冷え込み、山の水は凍ります。寒い冬を越えると、新緑に囲まれ、空気が清らかで過ごしやすい季節が続きます。秋には紅葉で山が鮮やかに染まります。日本らしい四季の移ろいを感じられる自然豊かな立地です。",
+ "button": "naraiについて"
+ },
+ "bar": {
+ "title": "酒蔵直営の角打ち&作業場",
+ "subtitle": "sagyobar",
+ "body": "ここは、ボトルの箱詰めや出荷などの酒蔵作業 (sagyo) と、日本酒移動販売車「suginomori wagon」から提供されるSAKEを楽しむこともできる場(bar)が融合した、弊蔵の直営店です。酒蔵から徒歩1分。線路を挟み向かいの倉庫をリノベーションしました。",
+ "button": "sagyobarについて"
+ },
+ "concept": {
+ "title": "醸造のその先へ",
+ "subtitle": "",
+ "body": "私たちには、日本酒文化を未来に継承したいという信念があります。そのためには、これまでの常識をもう一度見つめ直すことや、新しい試みにも挑戦する。醸造のその先へ、自由な発想でSAKEを探究し、その魅力を伝えていきます。",
+ "button": "concept"
}
}
},