diff --git a/src/app/(frontend)/[locale]/media-assets/[category]/[subcategory]/page.tsx b/src/app/(frontend)/[locale]/media-assets/[category]/[subcategory]/page.tsx new file mode 100644 index 0000000..2667907 --- /dev/null +++ b/src/app/(frontend)/[locale]/media-assets/[category]/[subcategory]/page.tsx @@ -0,0 +1,121 @@ +import type { Metadata } from 'next' +import Link from 'next/link' +import { setRequestLocale } from 'next-intl/server' +import { notFound } from 'next/navigation' +import type { Locale } from '@/i18n/config' +import { getMediaAssetDetailPage } from '@/lib/media-assets' +import { buildLocaleAlternates, trimDescription } from '@/lib/seo' +import { MediaAssetGallery } from '../../media-asset-gallery' +import { MediaAssetBreadcrumbs, MediaAssetEmptyState } from '../../media-assets-shared' + +export const revalidate = 300 + +function DownloadIcon({ className = '' }: { className?: string }) { + return ( + + ) +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ category: string; locale: Locale; subcategory: string }> +}): Promise { + const { category, locale, subcategory } = await params + const pageData = await getMediaAssetDetailPage(locale, category, subcategory) + + if (!pageData) { + return { + title: locale === 'zh' ? '媒体资料' : 'Media Assets', + } + } + + return { + alternates: buildLocaleAlternates(`/media-assets/${category}/${subcategory}`, locale), + description: trimDescription(pageData.subcategory.description) || + (locale === 'zh' ? '下载 Eversolo 媒体资料。' : 'Download Eversolo media assets.'), + title: pageData.subcategory.name, + } +} + +export default async function MediaAssetDetailPage({ + params, +}: { + params: Promise<{ category: string; locale: Locale; subcategory: string }> +}) { + const { category, locale, subcategory } = await params + setRequestLocale(locale) + + const pageData = await getMediaAssetDetailPage(locale, category, subcategory) + if (!pageData) notFound() + + const archiveMeta = [pageData.archiveFormat, pageData.archiveFileSize].filter(Boolean).join(' / ') + + return ( +
+ + +
+
+
+ {locale === 'zh' ? '媒体资料包' : 'Media Asset Kit'} +
+

+ {pageData.subcategory.name} +

+

+ {pageData.subcategory.description || + (locale === 'zh' + ? '点击缩略图预览原始资源,或通过标题悬停菜单查看格式、大小并下载。' + : 'Open thumbnails to preview the original file, or hover titles to inspect format, size, and download quickly.')} +

+
+ + {pageData.archiveDownloadUrl ? ( +
+ {archiveMeta ? ( + + {archiveMeta} + + ) : null} + + + {locale === 'zh' ? '下载压缩包' : 'Download All'} + +
+ ) : null} +
+ +
+ {pageData.assets.length > 0 ? ( + + ) : ( + + {locale === 'zh' + ? '这个资料包还没有添加三级资源。请在后台为该二级分类添加图片、PDF 或视频条目。' + : 'This kit does not have downloadable assets yet. Add image, PDF, or video entries to this secondary category in the admin.'} +
+ + {locale === 'zh' ? '返回上级分类' : 'Back to category'} + +
+
+ )} +
+
+ ) +} diff --git a/src/app/(frontend)/[locale]/media-assets/[category]/page.tsx b/src/app/(frontend)/[locale]/media-assets/[category]/page.tsx new file mode 100644 index 0000000..b8b879e --- /dev/null +++ b/src/app/(frontend)/[locale]/media-assets/[category]/page.tsx @@ -0,0 +1,82 @@ +import type { Metadata } from 'next' +import { setRequestLocale } from 'next-intl/server' +import { notFound } from 'next/navigation' +import type { Locale } from '@/i18n/config' +import { getMediaAssetCategoryPage } from '@/lib/media-assets' +import { buildLocaleAlternates, trimDescription } from '@/lib/seo' +import { + MediaAssetBreadcrumbs, + MediaAssetCategoryGrid, + MediaAssetEmptyState, + MediaAssetPageHeader, +} from '../media-assets-shared' + +export const revalidate = 300 + +export async function generateMetadata({ + params, +}: { + params: Promise<{ category: string; locale: Locale }> +}): Promise { + const { category, locale } = await params + const pageData = await getMediaAssetCategoryPage(locale, category) + + if (!pageData) { + return { + title: locale === 'zh' ? '媒体资料' : 'Media Assets', + } + } + + return { + alternates: buildLocaleAlternates(`/media-assets/${category}`, locale), + description: trimDescription(pageData.category.description) || + (locale === 'zh' ? '浏览 Eversolo 媒体资料分类。' : 'Browse Eversolo media asset categories.'), + title: pageData.category.name, + } +} + +export default async function MediaAssetCategoryPage({ + params, +}: { + params: Promise<{ category: string; locale: Locale }> +}) { + const { category, locale } = await params + setRequestLocale(locale) + + const pageData = await getMediaAssetCategoryPage(locale, category) + if (!pageData) notFound() + + return ( +
+ + + + +
+ {pageData.children.length > 0 ? ( + + ) : ( + + {locale === 'zh' + ? '这个一级分类下还没有二级资料包。请在后台创建一个带上级分类的媒体资料包。' + : 'This category does not have secondary kits yet. Add a media asset kit with this parent category in the admin.'} + + )} +
+
+ ) +} diff --git a/src/app/(frontend)/[locale]/media-assets/media-asset-gallery.tsx b/src/app/(frontend)/[locale]/media-assets/media-asset-gallery.tsx new file mode 100644 index 0000000..2a2c962 --- /dev/null +++ b/src/app/(frontend)/[locale]/media-assets/media-asset-gallery.tsx @@ -0,0 +1,259 @@ +'use client' + +import { useEffect, useMemo, useState } from 'react' +import type { Locale } from '@/i18n/config' +import type { MediaAssetFileItem } from '@/lib/media-assets' + +function DownloadGlyph({ className = '' }: { className?: string }) { + return ( + + ) +} + +function CloseGlyph({ className = '' }: { className?: string }) { + return ( + + ) +} + +function getVideoEmbedUrl(url: string) { + try { + const parsed = new URL(url) + const hostname = parsed.hostname.replace(/^www\./, '') + + if (hostname === 'youtube.com' || hostname === 'm.youtube.com') { + const id = parsed.searchParams.get('v') + return id ? `https://www.youtube.com/embed/${id}?controls=1&rel=0` : null + } + + if (hostname === 'youtu.be') { + const id = parsed.pathname.split('/').filter(Boolean)[0] + return id ? `https://www.youtube.com/embed/${id}?controls=1&rel=0` : null + } + + if (hostname === 'vimeo.com') { + const id = parsed.pathname.split('/').filter(Boolean)[0] + return id ? `https://player.vimeo.com/video/${id}` : null + } + + if (hostname === 'player.vimeo.com' || parsed.pathname.includes('/embed/')) return url + } catch { + return null + } + + return null +} + +function PreviewFrame({ item }: { item: MediaAssetFileItem }) { + if (item.type === 'image') { + return ( + {item.title} + ) + } + + if (item.type === 'pdf') { + return ( +