chore: baseline import
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import configPromise from '@payload-config'
|
||||
import { RootPage, generatePageMetadata } from '@payloadcms/next/views'
|
||||
// @ts-expect-error generated by Payload admin import map generator
|
||||
import { importMap } from '../importMap.js'
|
||||
import { AdminHome } from '../_components/admin-home'
|
||||
|
||||
type AdminPageProps = {
|
||||
params: Promise<{ segments?: string[] }>
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params, searchParams }: AdminPageProps) {
|
||||
return generatePageMetadata({
|
||||
config: configPromise,
|
||||
params: params as Promise<{ [key: string]: string | string[] }>,
|
||||
searchParams: searchParams as Promise<{ [key: string]: string | string[] }>,
|
||||
})
|
||||
}
|
||||
|
||||
export default async function PayloadAdminPage({ params, searchParams }: AdminPageProps) {
|
||||
const normalizedParams = params.then(async (value) => {
|
||||
const segments = Array.isArray(value.segments) ? value.segments.filter(Boolean) : []
|
||||
|
||||
return { segments }
|
||||
})
|
||||
|
||||
const { segments } = await normalizedParams
|
||||
const resolvedSearchParams = await searchParams
|
||||
|
||||
if (segments.length === 0) {
|
||||
return <AdminHome cacheCleared={resolvedSearchParams.cache === 'cleared'} />
|
||||
}
|
||||
|
||||
return RootPage({
|
||||
config: configPromise,
|
||||
importMap,
|
||||
params: Promise.resolve({ segments }),
|
||||
searchParams: Promise.resolve(resolvedSearchParams as { [key: string]: string | string[] }),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
'use server'
|
||||
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
const CACHE_PATHS = [
|
||||
'/en',
|
||||
'/zh',
|
||||
'/en/about',
|
||||
'/zh/about',
|
||||
'/en/dealers',
|
||||
'/zh/dealers',
|
||||
'/en/downloads',
|
||||
'/zh/downloads',
|
||||
'/en/news',
|
||||
'/zh/news',
|
||||
'/en/products',
|
||||
'/zh/products',
|
||||
'/en/support',
|
||||
'/zh/support',
|
||||
'/en/support/contact',
|
||||
'/zh/support/contact',
|
||||
'/en/support/faq',
|
||||
'/zh/support/faq',
|
||||
'/en/support/warranty',
|
||||
'/zh/support/warranty',
|
||||
] as const
|
||||
|
||||
function normalizeReturnPath(value: FormDataEntryValue | null) {
|
||||
if (typeof value !== 'string') return '/admin'
|
||||
if (!value.startsWith('/admin')) return '/admin'
|
||||
return value
|
||||
}
|
||||
|
||||
export async function clearFrontendCache(formData: FormData) {
|
||||
const returnTo = normalizeReturnPath(formData.get('returnTo'))
|
||||
|
||||
for (const path of CACHE_PATHS) {
|
||||
revalidatePath(path)
|
||||
}
|
||||
|
||||
redirect(`${returnTo}${returnTo.includes('?') ? '&' : '?'}cache=cleared`)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
'use client'
|
||||
|
||||
import { usePathname, useSearchParams } from 'next/navigation'
|
||||
import { clearFrontendCache } from './admin-cache-actions'
|
||||
|
||||
export function AdminCacheButton() {
|
||||
const pathname = usePathname()
|
||||
const searchParams = useSearchParams()
|
||||
const cacheCleared = searchParams.get('cache') === 'cleared'
|
||||
const returnTo = pathname || '/admin'
|
||||
|
||||
return (
|
||||
<div className="admin-cache-header-slot" aria-live="polite">
|
||||
<form action={clearFrontendCache}>
|
||||
<input type="hidden" name="returnTo" value={returnTo} />
|
||||
<button
|
||||
className="admin-cache-header-slot__button"
|
||||
type="submit"
|
||||
title={cacheCleared ? 'Frontend cache cleared.' : 'Clear frontend cache'}
|
||||
>
|
||||
{cacheCleared ? 'Cache cleared' : 'Clear cache'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import Link from 'next/link'
|
||||
import { getPayload } from 'payload'
|
||||
import configPromise from '@payload-config'
|
||||
import { clearFrontendCache } from './admin-cache-actions'
|
||||
|
||||
const QUICK_LINKS = [
|
||||
{
|
||||
description: 'Manage hero modules, featured products, support panels, and homepage news surfaces.',
|
||||
href: '/admin/globals/homepage',
|
||||
title: 'Homepage',
|
||||
},
|
||||
{
|
||||
description: 'Open core product records, templates, editor-driven pages, downloads, reviews, and videos.',
|
||||
href: '/admin/collections/products',
|
||||
title: 'Products',
|
||||
},
|
||||
{
|
||||
description: 'Review firmware, manuals, online-upgrade entries, and release-note content.',
|
||||
href: '/admin/collections/downloads',
|
||||
title: 'Downloads',
|
||||
},
|
||||
{
|
||||
description: 'Keep support landing copy, FAQ content, contact instructions, and warranty policy aligned.',
|
||||
href: '/admin/globals/supportPage',
|
||||
title: 'Support',
|
||||
},
|
||||
{
|
||||
description: 'Track incoming after-sales, distributor, and OEM requests in one operations lane.',
|
||||
href: '/admin/collections/afterServiceRequests',
|
||||
title: 'Operations',
|
||||
},
|
||||
{
|
||||
description: 'Publish news, maintain redirects, and keep site-wide branding settings consistent.',
|
||||
href: '/admin/collections/news',
|
||||
title: 'Publishing',
|
||||
},
|
||||
] as const
|
||||
|
||||
const WORKSTREAMS = [
|
||||
'Template-backed products: keep local template assignments stable and manage hero/card media from the product record.',
|
||||
'Editor-driven products: use the imported legacy HTML as the rebuild reference, then move final content into overview and structured fields.',
|
||||
'Downloads: maintain release notes, package links, online-upgrade guidance, and product visibility together.',
|
||||
'Social proof: attach videos and reviews directly to each product so the frontend tabs stay complete.',
|
||||
] as const
|
||||
|
||||
function formatCount(value: number) {
|
||||
return new Intl.NumberFormat('en-US').format(value)
|
||||
}
|
||||
|
||||
async function getOverviewCounts() {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
|
||||
const [
|
||||
products,
|
||||
downloads,
|
||||
productVideos,
|
||||
productReviews,
|
||||
news,
|
||||
faqs,
|
||||
dealers,
|
||||
afterServiceRequests,
|
||||
distributorRequests,
|
||||
oemRequests,
|
||||
] = await Promise.all([
|
||||
payload.count({ collection: 'products' }),
|
||||
payload.count({ collection: 'downloads' }),
|
||||
payload.count({ collection: 'productVideos' }),
|
||||
payload.count({ collection: 'productReviews' }),
|
||||
payload.count({ collection: 'news' }),
|
||||
payload.count({ collection: 'faqs' }),
|
||||
payload.count({ collection: 'dealers' }),
|
||||
payload.count({ collection: 'afterServiceRequests' }),
|
||||
payload.count({ collection: 'distributorRequests' }),
|
||||
payload.count({ collection: 'oemRequests' }),
|
||||
])
|
||||
|
||||
return [
|
||||
{ eyebrow: 'Catalog', href: '/admin/collections/products', title: 'Products', value: products.totalDocs },
|
||||
{ eyebrow: 'Catalog', href: '/admin/collections/downloads', title: 'Downloads', value: downloads.totalDocs },
|
||||
{ eyebrow: 'Social Proof', href: '/admin/collections/productVideos', title: 'Product Videos', value: productVideos.totalDocs },
|
||||
{ eyebrow: 'Social Proof', href: '/admin/collections/productReviews', title: 'Product Reviews', value: productReviews.totalDocs },
|
||||
{ eyebrow: 'Publishing', href: '/admin/collections/news', title: 'News', value: news.totalDocs },
|
||||
{ eyebrow: 'Support', href: '/admin/collections/faqs', title: 'FAQs', value: faqs.totalDocs },
|
||||
{ eyebrow: 'Sales', href: '/admin/collections/dealers', title: 'Dealers', value: dealers.totalDocs },
|
||||
{
|
||||
eyebrow: 'Operations',
|
||||
href: '/admin/collections/afterServiceRequests',
|
||||
title: 'Requests',
|
||||
value: afterServiceRequests.totalDocs + distributorRequests.totalDocs + oemRequests.totalDocs,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export async function AdminHome({ cacheCleared = false }: { cacheCleared?: boolean }) {
|
||||
const stats = await getOverviewCounts()
|
||||
|
||||
return (
|
||||
<div className="studio-home">
|
||||
<section className="studio-home__hero">
|
||||
<div>
|
||||
<p className="studio-home__eyebrow">Eversolo Content Studio</p>
|
||||
<h1 className="studio-home__title">A cleaner control room for products, publishing, and support.</h1>
|
||||
<p className="studio-home__lede">
|
||||
This workspace keeps template-backed products, CMS-built pages, firmware updates, reviews,
|
||||
videos, and support operations in one place.
|
||||
</p>
|
||||
</div>
|
||||
<div className="studio-home__actions">
|
||||
<Link className="studio-home__primary" href="/admin/collections/products">
|
||||
Open products
|
||||
</Link>
|
||||
<Link className="studio-home__secondary" href="/admin/globals/homepage">
|
||||
Edit homepage
|
||||
</Link>
|
||||
<form action={clearFrontendCache}>
|
||||
<input type="hidden" name="returnTo" value="/admin" />
|
||||
<button className="studio-home__secondary studio-home__secondary--warm" type="submit">
|
||||
Clear frontend cache
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{cacheCleared ? (
|
||||
<section className="studio-home__notice" aria-live="polite">
|
||||
Frontend cache cleared. Refresh the storefront to confirm the latest content, ordering, and homepage feeds.
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
<section className="studio-home__stats">
|
||||
{stats.map((stat) => (
|
||||
<Link key={stat.title} href={stat.href} className="studio-home__stat-card">
|
||||
<span className="studio-home__stat-eyebrow">{stat.eyebrow}</span>
|
||||
<strong className="studio-home__stat-value">{formatCount(stat.value)}</strong>
|
||||
<span className="studio-home__stat-title">{stat.title}</span>
|
||||
</Link>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<section className="studio-home__grid">
|
||||
<div className="studio-home__panel">
|
||||
<div className="studio-home__panel-head">
|
||||
<h2>Priority lanes</h2>
|
||||
<p>Jump into the surfaces that drive the storefront most often.</p>
|
||||
</div>
|
||||
<div className="studio-home__link-list">
|
||||
{QUICK_LINKS.map((item) => (
|
||||
<Link key={item.href} className="studio-home__link-card" href={item.href}>
|
||||
<strong>{item.title}</strong>
|
||||
<span>{item.description}</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="studio-home__panel">
|
||||
<div className="studio-home__panel-head">
|
||||
<h2>Editorial guidance</h2>
|
||||
<p>Keep the admin team working the same way across templates, downloads, and support.</p>
|
||||
</div>
|
||||
<ul className="studio-home__checklist">
|
||||
{WORKSTREAMS.map((item) => (
|
||||
<li key={item}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { RscEntryLexicalCell as RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
|
||||
import { RscEntryLexicalField as RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
|
||||
import { LexicalDiffComponent as LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
|
||||
import { FixedToolbarFeatureClient as FixedToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { InlineToolbarFeatureClient as InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { HorizontalRuleFeatureClient as HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { UploadFeatureClient as UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { BlockquoteFeatureClient as BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { RelationshipFeatureClient as RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { LinkFeatureClient as LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { ChecklistFeatureClient as ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { OrderedListFeatureClient as OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { UnorderedListFeatureClient as UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { IndentFeatureClient as IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { AlignFeatureClient as AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { HeadingFeatureClient as HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { ParagraphFeatureClient as ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { InlineCodeFeatureClient as InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { SuperscriptFeatureClient as SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { SubscriptFeatureClient as SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { StrikethroughFeatureClient as StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { BoldFeatureClient as BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc'
|
||||
|
||||
/** @type import('payload').ImportMap */
|
||||
export const importMap = {
|
||||
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell": RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e,
|
||||
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalField": RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e,
|
||||
"@payloadcms/richtext-lexical/rsc#LexicalDiffComponent": LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e,
|
||||
"@payloadcms/richtext-lexical/client#FixedToolbarFeatureClient": FixedToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient": InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient": HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#UploadFeatureClient": UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#BlockquoteFeatureClient": BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#RelationshipFeatureClient": RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#LinkFeatureClient": LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#ChecklistFeatureClient": ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#OrderedListFeatureClient": OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#UnorderedListFeatureClient": UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#IndentFeatureClient": IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#AlignFeatureClient": AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#HeadingFeatureClient": HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#ParagraphFeatureClient": ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#InlineCodeFeatureClient": InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#SuperscriptFeatureClient": SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#SubscriptFeatureClient": SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#StrikethroughFeatureClient": StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/next/rsc#CollectionCards": CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1
|
||||
}
|
||||
Reference in New Issue
Block a user