Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions app/components/shared/CpSponsorFooter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<script setup lang="ts">
import type { Sponsor } from '#shared/types/sponsor'
import { useI18n } from 'vue-i18n'
import { SPONSOR_LEVELS } from '#shared/types/sponsor'

const { locale, t } = useI18n()

const { data } = useFetch('/api/sponsor', { default: () => [] as Sponsor[] })

const sponsorGroups = computed(() => {
const grouped = Object.groupBy(data.value || [], (sponsor) => sponsor.level)
return SPONSOR_LEVELS
.filter((level) => grouped[level]?.length)
.map((level) => ({
level,
sponsors: grouped[level]!,
}))
})

const hasSponsor = computed(() => !!data.value?.length)
</script>

<template>
<section
v-if="hasSponsor"
class="px-8 pb-8 pt-16 bg-gray-50"
>
<div class="mx-auto max-w-[1100px]">
<div
v-for="{ level, sponsors } in sponsorGroups"
:key="level"
class="mb-8 last:mb-0"
>
<h3 class="text-sm text-gray-600 tracking-wider font-bold mb-4 text-center uppercase">
{{ t(`level.${level}`) }}
</h3>
<div
class="gap-4 grid grid-cols-2 lg:grid-cols-5 md:grid-cols-4"
>
<NuxtLink
v-for="sponsor in sponsors"
:key="sponsor.id"
class="p-3 rounded-lg bg-white flex shadow-sm transition items-center justify-center hover:shadow-md hover:scale-105"
external
rel="noreferrer"
target="_blank"
:title="sponsor.name[locale]"
:to="sponsor.link"
>
<NuxtImg
:alt="sponsor.name[locale]"
class="h-20 max-h-full max-w-full object-contain md:h-24"
:src="sponsor.image"
/>
</NuxtLink>
</div>
</div>
</div>
</section>
</template>

<i18n lang="yaml">
zh:
title: 贊助夥伴
level:
titanium: 鈦金級
diamond: 鑽石級
gold: 黃金級
silver: 白銀級
bronze: 青銅級
friend: 好朋友級
community: 社群夥伴
en:
title: Sponsors
level:
titanium: Titanium
diamond: Diamond
gold: Gold
silver: Silver
bronze: Bronze
friend: Friend
community: Community Partner
</i18n>
2 changes: 2 additions & 0 deletions app/layouts/default.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import CpFooter from '~/components/shared/CpFooter.vue'
import CpNavbar from '~/components/shared/CpNavbar.vue'
import CpSponsorFooter from '~/components/shared/CpSponsorFooter.vue'
</script>

<template>
Expand All @@ -10,6 +11,7 @@ import CpNavbar from '~/components/shared/CpNavbar.vue'
<slot />
</main>

<CpSponsorFooter />
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Including CpSponsorFooter in the default layout means every page render/prerender will trigger its data fetch. Since /api/sponsor ultimately hits Google Sheets (fetchSheet('sponsorList')), this can noticeably slow nuxt generate and increase the chance of rate limiting. Consider adding caching (e.g., Nitro cachedEventHandler/storage) or making the footer fetch lazy/client-side so it doesn’t block all routes during prerender.

Suggested change
<CpSponsorFooter />
<ClientOnly>
<CpSponsorFooter />
</ClientOnly>

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SSG 的部分應該不會一直請求(?)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useFetch 在 SSG 下 已經有去重和快取機制,而且 /api/sponsor 應該已經是個 cache function,應該不用擔心,頂多是每個頁面都會重複 CpSponsorFooter 的渲染結果而已。ClientOnly 反而會導致 Sponsor 的區塊延遲出現,可能不太理想。

<CpFooter />
</div>
</template>