Skip to content

Commit b2155ef

Browse files
totocalcioubugeeei
andauthored
feat: add store page (#926)
* feat: add store page * fix: responsive * chore: cspell * fix: type * fix: last line * feat: トップページにストアセクション追加 * fix: textlint * feat: Store セクションの表示 * chore: unicode * chore: ogDescription * chore * chore * chore * chore * chore --------- Co-authored-by: ubugeeei <[email protected]>
1 parent f6892ed commit b2155ef

27 files changed

+624
-7
lines changed

app/constant.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const HOME_HEADING_ID = {
1111
volunteer: "volunteer",
1212
access: "access",
1313
studentSupport: "student-support",
14+
store: "store",
1415

1516
// NOTE: Be careful as it is hardcoded in MDC
1617
contact: "contact-form",

app/layouts/default.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ const menuItems = computed<MenuItemProps[]>(() =>
5252
routeName: localeRoute({ name: "event" }).name,
5353
disabled: !__FEATURE_EVENT__,
5454
},
55+
{
56+
id: HOME_HEADING_ID.store,
57+
label: "Store",
58+
routeName: localeRoute({ name: "store" }).name,
59+
},
5560
{
5661
id: HOME_HEADING_ID.ticket,
5762
label: "Ticket",
@@ -100,6 +105,7 @@ const WIDE_ROUTE_NAMES: RoutesNamesList[] = [
100105
"sponsors-sponsorId",
101106
"event",
102107
"related-events",
108+
"store",
103109
];
104110
105111
const isWidenContent = computed(() =>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<script setup lang="ts">
2+
import { HOME_HEADING_ID } from "~/constant";
3+
import { useI18n, useWithBase } from "#imports";
4+
import { VFSection, JaCheckTheItems, EnCheckTheItems } from "#components";
5+
6+
const withBase = useWithBase();
7+
const { locale, t } = useI18n();
8+
</script>
9+
10+
<template>
11+
<VFSection
12+
:id="HOME_HEADING_ID.store"
13+
:title="t('store.panel.title')"
14+
:cover-image="{
15+
src: withBase('/images/top/cover/grab-your-gear.png'),
16+
alt: t('store.coverImageAlt'),
17+
}"
18+
>
19+
<component :is="locale === 'ja' ? JaCheckTheItems : EnCheckTheItems" />
20+
<div class="button-container">
21+
<VFButton link="https://vuejs-jp.stores.jp">
22+
{{ t('store.preOrder') }}
23+
</VFButton>
24+
</div>
25+
</VFSection>
26+
</template>
27+
28+
<style scoped>
29+
@import "~/assets/styles/custom-media-query.css";
30+
31+
.button-container {
32+
text-align: center;
33+
34+
margin-top: 32px;
35+
@media (--mobile) {
36+
margin-top: 24px;
37+
}
38+
}
39+
</style>

app/pages/index.vue

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
<script setup lang="ts">
2-
import SectionVolunteer from "./_components/SectionVolunteer.vue";
2+
import SectionGetYourTicket from "./_components/SectionGetYourTicket.vue";
33
import SectionSpeakers from "./_components/SectionSpeaker.vue";
4+
import SectionGranYourGear from "./_components/SectionGranYourGear.vue";
45
import SectionStudentSupport from "./_components/SectionStudentSupport.vue";
5-
import SectionMessage from "./_components/SectionMessage.vue";
6-
import SectionSponsorWanted from "./_components/SectionSponsorWanted.vue";
6+
import SectionVolunteer from "./_components/SectionVolunteer.vue";
77
import SectionSponsors from "./_components/SectionSponsors.vue";
8-
import SectionGetYourTicket from "./_components/SectionGetYourTicket.vue";
9-
import SectionContact from "./_components/SectionContact.vue";
8+
import SectionSponsorWanted from "./_components/SectionSponsorWanted.vue";
109
import SectionAccess from "./_components/SectionAccess.vue";
10+
import SectionMessage from "./_components/SectionMessage.vue";
11+
import SectionContact from "./_components/SectionContact.vue";
1112
1213
import {
1314
defineRouteRules,
@@ -30,6 +31,7 @@ useSeoMeta({ title: "" });
3031
<div class="section-container">
3132
<SectionGetYourTicket />
3233
<SectionSpeakers />
34+
<SectionGranYourGear />
3335
<SectionStudentSupport />
3436
<SectionVolunteer />
3537
<SectionSponsors />
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<script setup lang="ts">
2+
import type { Goods } from "~~/i18n/goods";
3+
import { useI18n } from "#imports";
4+
5+
interface Props {
6+
item: Goods;
7+
}
8+
9+
defineProps<Props>();
10+
11+
const { t } = useI18n();
12+
13+
const formatPrice = (price: number) => {
14+
return `¥${price.toLocaleString()}`;
15+
};
16+
</script>
17+
18+
<template>
19+
<li class="store-item-card">
20+
<div class="item-image">
21+
<img :src="item.src" :alt="item.name" loading="lazy" />
22+
</div>
23+
<div class="item-content">
24+
<div class="item-name">
25+
{{ item.name }}
26+
</div>
27+
<div class="item-price">
28+
{{ formatPrice(item.price) }}
29+
</div>
30+
<div class="item-description">
31+
<p>{{ item.description }}</p>
32+
<br>
33+
<div class="item-specs">
34+
<p v-if="item.specs.color" class="spec-item">
35+
{{ t('store.color') }}{{ item.specs.color }}
36+
</p>
37+
<p v-if="item.specs.material" class="spec-item">
38+
{{ t('store.material') }}{{ item.specs.material }}
39+
</p>
40+
<p v-if="item.specs.size" class="spec-item">
41+
{{ t('store.size') }}{{ item.specs.size }}
42+
</p>
43+
</div>
44+
</div>
45+
</div>
46+
</li>
47+
</template>
48+
49+
<style scoped>
50+
@import "~/assets/styles/custom-media-query.css";
51+
52+
.store-item-card {
53+
display: grid;
54+
grid-template-rows: auto 1fr;
55+
row-gap: 1rem;
56+
line-height: 1.5;
57+
}
58+
59+
.item-content {
60+
display: grid;
61+
grid-template-rows: auto auto 1fr;
62+
row-gap: 0.25rem;
63+
}
64+
65+
.item-image img {
66+
width: 100%;
67+
height: 100%;
68+
aspect-ratio: 1 / 1;
69+
object-fit: contain;
70+
}
71+
72+
.item-name {
73+
font-size: 0.875rem;
74+
75+
@media (--mobile) {
76+
font-size: 0.75rem;
77+
}
78+
}
79+
80+
.item-price {
81+
font-size: 1.125rem;
82+
font-weight: 700;
83+
84+
@media (--mobile) {
85+
font-size: 1rem;
86+
}
87+
}
88+
89+
.item-description {
90+
font-size: 1rem;
91+
white-space: pre-wrap;
92+
93+
@media (--mobile) {
94+
font-size: 0.875rem;
95+
}
96+
}
97+
98+
.spec-item {
99+
margin: 0;
100+
}
101+
</style>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script setup lang="ts">
2+
import { useI18n } from "#imports";
3+
4+
const { t } = useI18n();
5+
</script>
6+
7+
<template>
8+
<VFButton link="https://vuejs-jp.stores.jp" external class="vf-button">
9+
{{ t('store.preOrder') }}
10+
</VFButton>
11+
</template>
12+
13+
<style scoped>
14+
.vf-button {
15+
color: var(--color-white);
16+
17+
&:hover {
18+
color: var(--color-white);
19+
}
20+
}
21+
</style>

app/pages/store/index.vue

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<script setup lang="ts">
2+
import { GOODS as enGoods } from "../../../i18n/en/goods";
3+
import { GOODS as jaGoods } from "../../../i18n/ja/goods";
4+
import StorePreOrderButton from "./_components/StorePreOrderButton.vue";
5+
import StoreItemCard from "./_components/StoreItemCard.vue";
6+
import {
7+
computed,
8+
useI18n,
9+
definePageMeta,
10+
useRuntimeConfig,
11+
// NOTE: import useHead to avoid `useHead is not defined` error
12+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
13+
useHead,
14+
useSeoMeta,
15+
defineOgImage,
16+
} from "#imports";
17+
import { VFSection, JaStore, EnStore } from "#components";
18+
19+
definePageMeta({ prerender: true });
20+
const { t, locale } = useI18n();
21+
22+
const runtimeConfig = useRuntimeConfig();
23+
24+
const goods = computed(() => locale.value === "en" ? enGoods : jaGoods);
25+
const indent = computed(() => locale.value === "en" ? "0.7em" : "0.25em");
26+
27+
defineOgImage({
28+
component: "root",
29+
url: `${runtimeConfig.public.siteUrl}images/og/store.png`,
30+
});
31+
32+
useSeoMeta({
33+
title: t("store.title"),
34+
ogTitle: t("store.title"),
35+
description: t("store.description"),
36+
ogDescription: t("store.description"),
37+
});
38+
</script>
39+
40+
<template>
41+
<div id="pages-store">
42+
<h1>{{ $t('store.title') }}</h1>
43+
44+
<VFSection :title="t('store.panel.title')">
45+
<component :is="locale === 'ja' ? JaStore : EnStore">
46+
<template #button>
47+
<div class="store-button-container">
48+
<StorePreOrderButton />
49+
</div>
50+
</template>
51+
<template #goods>
52+
<ul class="store-item-list">
53+
<StoreItemCard
54+
v-for="item in goods"
55+
:key="item.id"
56+
:item="item"
57+
/>
58+
</ul>
59+
</template>
60+
</component>
61+
</VFSection>
62+
</div>
63+
</template>
64+
65+
<style scoped>
66+
@import "~/assets/styles/custom-media-query.css";
67+
68+
#pages-store{
69+
display: grid;
70+
row-gap: 1.5rem;
71+
72+
@media (--mobile) {
73+
row-gap: 1rem;
74+
}
75+
76+
h1 {
77+
font-family: "ClashDisplay-Semibold";
78+
font-size: 3rem;
79+
padding: 7.5rem 0;
80+
margin: 0;
81+
82+
@media (--mobile) {
83+
padding: 2.5rem 0.75rem;
84+
}
85+
}
86+
87+
.store-button-container {
88+
text-align: center;
89+
margin-top: 1.5rem;
90+
margin-bottom: 2rem;
91+
92+
@media (--mobile) {
93+
margin-top: 1rem;
94+
margin-bottom: 1.5rem;
95+
}
96+
}
97+
98+
.store-item-list {
99+
display: grid;
100+
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
101+
gap: 1.5rem;
102+
margin-bottom: 2.5rem;
103+
104+
@media (--mobile) {
105+
row-gap: 2rem;
106+
}
107+
}
108+
109+
:deep(.divider) {
110+
border: 0;
111+
border-top: 1px solid var(--color-divider);
112+
margin: 2rem 0;
113+
114+
@media (--mobile) {
115+
margin: 1.5rem 0;
116+
}
117+
}
118+
119+
:deep(.store-note) {
120+
text-indent: -1.25em;
121+
margin-left: 1.25em;
122+
123+
span {
124+
margin-right: v-bind(indent);
125+
}
126+
}
127+
}
128+
</style>

cspell.config.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ words:
1111
- arounds
1212
- authjs
1313
- autodocs
14+
- barai
1415
- bengo
1516
- bsky
1617
- ceroan
@@ -108,7 +109,9 @@ words:
108109
- posx
109110
- presigner
110111
- primevue
112+
- QUIC
111113
- Rahul
114+
- Rakuten
112115
- Rinchoku
113116
- RIZAP
114117
- RNBO

i18n/en/check-the-items.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The Vue Fes Store is back this year, where you can purchase Vue Fes Japan exclusive original goods.
2+
This time, we have completely redesigned the goods design in conjunction with the site and logo rebranding. Get your original goods and help make Vue Fes Japan even more exciting!

0 commit comments

Comments
 (0)