Update homepage positions and form notifications
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import { startTransition, useState } from 'react'
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { FormSuccessDialog } from '@/components/site/form-success-dialog'
|
||||
|
||||
type DistributorFormState = {
|
||||
companyName: string
|
||||
@@ -55,6 +56,35 @@ type PartnershipFormIntro = {
|
||||
title?: string | null
|
||||
}
|
||||
|
||||
type SuccessDialogState = {
|
||||
message: string
|
||||
title: string
|
||||
}
|
||||
|
||||
function getPartnershipSuccessDialog(type: 'distributor' | 'oem', locale: Locale): SuccessDialogState {
|
||||
if (type === 'oem') {
|
||||
return locale === 'zh'
|
||||
? {
|
||||
message: 'OEM 合作申请已提交,我们会尽快查看并联系您。',
|
||||
title: 'OEM 合作申请已提交',
|
||||
}
|
||||
: {
|
||||
message: 'Your OEM inquiry has been submitted successfully. We will review it and contact you soon.',
|
||||
title: 'OEM inquiry submitted',
|
||||
}
|
||||
}
|
||||
|
||||
return locale === 'zh'
|
||||
? {
|
||||
message: '经销商申请已提交,我们会尽快查看并联系您。',
|
||||
title: '经销商申请已提交',
|
||||
}
|
||||
: {
|
||||
message: 'Your distributor request has been submitted successfully. We will review it and contact you soon.',
|
||||
title: 'Distributor request submitted',
|
||||
}
|
||||
}
|
||||
|
||||
export function DealerPartnershipForms({
|
||||
intro,
|
||||
locale,
|
||||
@@ -68,17 +98,17 @@ export function DealerPartnershipForms({
|
||||
const [oemForm, setOemForm] = useState<OemFormState>(INITIAL_OEM_FORM)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [isPending, setIsPending] = useState(false)
|
||||
const [success, setSuccess] = useState<string | null>(null)
|
||||
const [successDialog, setSuccessDialog] = useState<SuccessDialogState | null>(null)
|
||||
const [startedAt] = useState(() => Date.now())
|
||||
|
||||
async function submitForm(endpoint: string, payload: object) {
|
||||
async function submitForm(endpoint: string, payload: object, type: 'distributor' | 'oem') {
|
||||
setError(null)
|
||||
setSuccess(null)
|
||||
setSuccessDialog(null)
|
||||
setIsPending(true)
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
body: JSON.stringify({ ...payload, startedAt }),
|
||||
body: JSON.stringify({ ...payload, locale, startedAt }),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
method: 'POST',
|
||||
})
|
||||
@@ -93,13 +123,10 @@ export function DealerPartnershipForms({
|
||||
}
|
||||
|
||||
startTransition(() => {
|
||||
setSuccess(
|
||||
data.message ||
|
||||
(locale === 'zh'
|
||||
? '申请已提交,我们会尽快联系您。'
|
||||
: 'Your request has been submitted successfully.'),
|
||||
)
|
||||
setSuccessDialog(getPartnershipSuccessDialog(type, locale))
|
||||
})
|
||||
|
||||
return true
|
||||
} catch (submissionError) {
|
||||
setError(
|
||||
submissionError instanceof Error
|
||||
@@ -108,6 +135,7 @@ export function DealerPartnershipForms({
|
||||
? '提交失败,请稍后再试。'
|
||||
: 'Submission failed. Please try again.',
|
||||
)
|
||||
return false
|
||||
} finally {
|
||||
setIsPending(false)
|
||||
}
|
||||
@@ -115,18 +143,29 @@ export function DealerPartnershipForms({
|
||||
|
||||
async function handleDistributorSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault()
|
||||
await submitForm('/api/sales/distributor', distributorForm)
|
||||
setDistributorForm(INITIAL_DISTRIBUTOR_FORM)
|
||||
if (await submitForm('/api/sales/distributor', distributorForm, 'distributor')) {
|
||||
setDistributorForm(INITIAL_DISTRIBUTOR_FORM)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleOemSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault()
|
||||
await submitForm('/api/sales/oem', oemForm)
|
||||
setOemForm(INITIAL_OEM_FORM)
|
||||
if (await submitForm('/api/sales/oem', oemForm, 'oem')) {
|
||||
setOemForm(INITIAL_OEM_FORM)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="rounded-[32px] border border-black/10 bg-white p-6 shadow-[0_24px_80px_rgba(17,24,39,0.05)] md:p-8">
|
||||
{successDialog ? (
|
||||
<FormSuccessDialog
|
||||
locale={locale}
|
||||
message={successDialog.message}
|
||||
onClose={() => setSuccessDialog(null)}
|
||||
title={successDialog.title}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<div className="grid gap-8 lg:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)]">
|
||||
<div>
|
||||
<div className="text-sm uppercase tracking-[0.18em] text-text-muted">
|
||||
@@ -249,7 +288,7 @@ export function DealerPartnershipForms({
|
||||
setDistributorForm((current) => ({ ...current, message: value }))
|
||||
}
|
||||
/>
|
||||
<SubmitState error={error} isPending={isPending} locale={locale} success={success} />
|
||||
<SubmitState error={error} isPending={isPending} locale={locale} />
|
||||
</form>
|
||||
) : (
|
||||
<form className="space-y-5" onSubmit={handleOemSubmit}>
|
||||
@@ -307,7 +346,7 @@ export function DealerPartnershipForms({
|
||||
value={oemForm.message}
|
||||
onChange={(value) => setOemForm((current) => ({ ...current, message: value }))}
|
||||
/>
|
||||
<SubmitState error={error} isPending={isPending} locale={locale} success={success} />
|
||||
<SubmitState error={error} isPending={isPending} locale={locale} />
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
@@ -320,12 +359,10 @@ function SubmitState({
|
||||
error,
|
||||
isPending,
|
||||
locale,
|
||||
success,
|
||||
}: {
|
||||
error: string | null
|
||||
isPending: boolean
|
||||
locale: Locale
|
||||
success: string | null
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
@@ -335,12 +372,6 @@ function SubmitState({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{success ? (
|
||||
<div className="rounded-2xl border border-[#3A7D44]/25 bg-[#F2FAF4] px-4 py-3 text-sm text-[#24552C]">
|
||||
{success}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Metadata } from 'next'
|
||||
import type { Payload, Where } from 'payload'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { setRequestLocale } from 'next-intl/server'
|
||||
import { preinit, preinitModule } from 'react-dom'
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { formatChinaDate } from '@/lib/china-time'
|
||||
@@ -10,6 +11,7 @@ import { getPayloadClient } from '@/lib/payload'
|
||||
import { ACCESSIBLE_PRODUCT_STATUSES, isVisibleProductStatus } from '@/lib/product-status'
|
||||
import { getProductSeriesDefault } from '@/lib/product-series-defaults'
|
||||
import { loadProductTemplateHtml } from '@/lib/product-templates'
|
||||
import type { LoadedProductTemplate } from '@/lib/product-templates'
|
||||
import { renderRichText } from '@/lib/render-rich-text'
|
||||
import { buildAbsoluteUrl, buildLocaleAlternates, extractRichTextPlainText, trimDescription } from '@/lib/seo'
|
||||
import { normalizeUploadUrl } from '@/lib/upload-url'
|
||||
@@ -17,7 +19,7 @@ import { ProductContentTabs } from './product-content-tabs'
|
||||
import { ProductGallerySection } from './product-gallery-section'
|
||||
import { ProductTemplateMount } from './product-template-mount'
|
||||
import { ProductVideosSection } from './product-videos-section'
|
||||
import type { Download, Media, News, NewsCategory, Product, ProductCategory, ProductReview } from '@/payload-types'
|
||||
import type { Media, News, NewsCategory, Product, ProductCategory, ProductReview } from '@/payload-types'
|
||||
|
||||
export const revalidate = 300
|
||||
|
||||
@@ -73,6 +75,26 @@ function getSeriesKeyFromSlug(slug: string) {
|
||||
return 'accessories'
|
||||
}
|
||||
|
||||
function preinitProductTemplateAssets(templateData: LoadedProductTemplate | null) {
|
||||
if (!templateData) return
|
||||
|
||||
for (const href of templateData.styleHrefs) {
|
||||
preinit(href, {
|
||||
as: 'style',
|
||||
crossOrigin: 'anonymous',
|
||||
fetchPriority: 'high',
|
||||
precedence: 'product-template',
|
||||
})
|
||||
}
|
||||
|
||||
for (const src of templateData.scriptSrcs) {
|
||||
preinitModule(src, {
|
||||
as: 'script',
|
||||
crossOrigin: 'anonymous',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function getAccessibleProductIdsBySlug(payload: Payload, slug: string, locale: Locale) {
|
||||
const products = await fetchAllPayloadDocs<Product>((pagination) =>
|
||||
payload.find({
|
||||
@@ -380,10 +402,10 @@ async function getProductPageDataUncached(slug: string, locale: Locale) {
|
||||
if (!product) return { downloads: [], news: [], overviewMedia: [], product: null, reviews: [], videos: [] }
|
||||
|
||||
const overviewUploadIds = collectRichTextUploadIds((product.overview || product.summary) as RichTextLike)
|
||||
const overviewMedia =
|
||||
const overviewMediaPromise: Promise<Media[]> =
|
||||
overviewUploadIds.length > 0
|
||||
? (
|
||||
await payload.find({
|
||||
? payload
|
||||
.find({
|
||||
collection: 'media',
|
||||
depth: 0,
|
||||
limit: overviewUploadIds.length,
|
||||
@@ -394,69 +416,73 @@ async function getProductPageDataUncached(slug: string, locale: Locale) {
|
||||
},
|
||||
},
|
||||
})
|
||||
).docs
|
||||
: []
|
||||
.then((result) => result.docs)
|
||||
: Promise.resolve([])
|
||||
|
||||
const relatedProductIds = await getAccessibleProductIdsBySlug(payload, slug, locale)
|
||||
const downloads = await fetchAllPayloadDocs<Download>((pagination) =>
|
||||
payload.find({
|
||||
collection: 'downloads',
|
||||
depth: 2,
|
||||
locale,
|
||||
sort: '-releaseDate',
|
||||
where: {
|
||||
and: [
|
||||
{ docLocale: { equals: locale } },
|
||||
{ status: { equals: 'published' } },
|
||||
{ 'visibility.showInDownloadsCenter': { equals: true } },
|
||||
getDownloadProductRelationWhere(relatedProductIds.length > 0 ? relatedProductIds : [product.id]),
|
||||
],
|
||||
const relatedProductIdsPromise = getAccessibleProductIdsBySlug(payload, slug, locale)
|
||||
const videosPromise = payload.find({
|
||||
collection: 'productVideos',
|
||||
depth: 1,
|
||||
limit: 300,
|
||||
locale,
|
||||
sort: 'sortOrder,-publishedAt,-updatedAt',
|
||||
where: {
|
||||
and: [
|
||||
{ docLocale: { equals: locale } },
|
||||
{ product: { equals: product.id } },
|
||||
{ status: { equals: 'published' } },
|
||||
],
|
||||
},
|
||||
})
|
||||
const reviewsPromise = payload.find({
|
||||
collection: 'productReviews',
|
||||
depth: 1,
|
||||
limit: 300,
|
||||
locale,
|
||||
sort: 'sortOrder,-publishedAt,-updatedAt',
|
||||
where: {
|
||||
and: [
|
||||
{ docLocale: { equals: locale } },
|
||||
{ product: { equals: product.id } },
|
||||
{ status: { equals: 'published' } },
|
||||
],
|
||||
},
|
||||
})
|
||||
const newsPromise = payload.find({
|
||||
collection: 'news',
|
||||
depth: 1,
|
||||
limit: 100,
|
||||
locale,
|
||||
sort: '-publishedAt,-updatedAt',
|
||||
where: {
|
||||
docLocale: {
|
||||
equals: locale,
|
||||
},
|
||||
...pagination,
|
||||
}) as Promise<{ docs: Download[]; hasNextPage?: boolean | null }>,
|
||||
)
|
||||
},
|
||||
})
|
||||
const relatedProductIds = await relatedProductIdsPromise
|
||||
const downloadsPromise = payload.find({
|
||||
collection: 'downloads',
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
locale,
|
||||
pagination: false,
|
||||
where: {
|
||||
and: [
|
||||
{ docLocale: { equals: locale } },
|
||||
{ status: { equals: 'published' } },
|
||||
{ 'visibility.showInDownloadsCenter': { equals: true } },
|
||||
getDownloadProductRelationWhere(relatedProductIds.length > 0 ? relatedProductIds : [product.id]),
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const [videos, reviews, news] = await Promise.all([
|
||||
payload.find({
|
||||
collection: 'productVideos',
|
||||
depth: 1,
|
||||
limit: 300,
|
||||
locale,
|
||||
sort: 'sortOrder,-publishedAt,-updatedAt',
|
||||
where: {
|
||||
and: [
|
||||
{ docLocale: { equals: locale } },
|
||||
{ product: { equals: product.id } },
|
||||
{ status: { equals: 'published' } },
|
||||
],
|
||||
},
|
||||
}),
|
||||
payload.find({
|
||||
collection: 'productReviews',
|
||||
depth: 1,
|
||||
limit: 300,
|
||||
locale,
|
||||
sort: 'sortOrder,-publishedAt,-updatedAt',
|
||||
where: {
|
||||
and: [
|
||||
{ docLocale: { equals: locale } },
|
||||
{ product: { equals: product.id } },
|
||||
{ status: { equals: 'published' } },
|
||||
],
|
||||
},
|
||||
}),
|
||||
payload.find({
|
||||
collection: 'news',
|
||||
depth: 1,
|
||||
limit: 100,
|
||||
locale,
|
||||
sort: '-publishedAt,-updatedAt',
|
||||
where: {
|
||||
docLocale: {
|
||||
equals: locale,
|
||||
},
|
||||
},
|
||||
}),
|
||||
const [overviewMedia, downloads, videos, reviews, news] = await Promise.all([
|
||||
overviewMediaPromise,
|
||||
downloadsPromise,
|
||||
videosPromise,
|
||||
reviewsPromise,
|
||||
newsPromise,
|
||||
])
|
||||
|
||||
const relatedNews = news.docs.filter((article) =>
|
||||
@@ -465,7 +491,7 @@ async function getProductPageDataUncached(slug: string, locale: Locale) {
|
||||
),
|
||||
)
|
||||
|
||||
return { downloads, news: relatedNews, overviewMedia, product, reviews: reviews.docs, videos: videos.docs }
|
||||
return { downloads: downloads.docs, news: relatedNews, overviewMedia, product, reviews: reviews.docs, videos: videos.docs }
|
||||
}
|
||||
|
||||
const getProductPageData = cacheCmsQuery<[string, Locale], Awaited<ReturnType<typeof getProductPageDataUncached>>>(
|
||||
@@ -692,6 +718,7 @@ export default async function ProductDetailPage({
|
||||
)
|
||||
const templateData =
|
||||
templateId && templateId !== 'default' ? await loadProductTemplateHtml(templateId, locale) : null
|
||||
preinitProductTemplateAssets(templateData)
|
||||
const renderedVideos = videos.map((video) => ({
|
||||
descriptionHtml:
|
||||
video.description &&
|
||||
|
||||
@@ -12,6 +12,48 @@ type ProductTemplateMountProps = {
|
||||
const MAX_SCRIPT_ATTEMPTS = 3
|
||||
const VERIFY_DELAYS = [1200, 2200, 3600] as const
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__productTemplateScriptRuns?: Record<string, number | undefined>
|
||||
}
|
||||
}
|
||||
|
||||
function getTemplateScriptRunIndex(templateId: string, src: string) {
|
||||
const runs = window.__productTemplateScriptRuns || {}
|
||||
window.__productTemplateScriptRuns = runs
|
||||
|
||||
const url = new URL(src, window.location.origin)
|
||||
const key = `${templateId}:${url.origin}${url.pathname}`
|
||||
const current = runs[key] ?? 0
|
||||
runs[key] = current + 1
|
||||
|
||||
return current
|
||||
}
|
||||
|
||||
function toSameOriginScriptSrc(src: string, attempt: number, runIndex: number) {
|
||||
const url = new URL(src, window.location.origin)
|
||||
const isSameOrigin = url.origin === window.location.origin
|
||||
|
||||
if (attempt > 0 || runIndex > 0) {
|
||||
url.searchParams.set('templateRun', `${runIndex}`)
|
||||
url.searchParams.set('attempt', `${attempt}`)
|
||||
}
|
||||
|
||||
return isSameOrigin ? `${url.pathname}${url.search}${url.hash}` : url.toString()
|
||||
}
|
||||
|
||||
function hasStylesheet(href: string) {
|
||||
const target = new URL(href, window.location.origin).href
|
||||
|
||||
return Array.from(document.head.querySelectorAll<HTMLLinkElement>('link[rel="stylesheet"]')).some((link) => {
|
||||
try {
|
||||
return new URL(link.href, window.location.origin).href === target
|
||||
} catch {
|
||||
return link.getAttribute('href') === href
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function markImagePriority(image: HTMLImageElement, isPriority: boolean) {
|
||||
image.loading = isPriority ? 'eager' : 'lazy'
|
||||
image.fetchPriority = isPriority ? 'high' : 'low'
|
||||
@@ -71,6 +113,9 @@ export function ProductTemplateMount({
|
||||
let imageObserver: MutationObserver | null = null
|
||||
let pendingScripts = 0
|
||||
let disposed = false
|
||||
const scriptRunIndexes = new Map(
|
||||
scriptSrcs.map((src) => [src, getTemplateScriptRunIndex(templateId, src)]),
|
||||
)
|
||||
|
||||
markTemplateMediaLazy(host)
|
||||
|
||||
@@ -89,7 +134,7 @@ export function ProductTemplateMount({
|
||||
|
||||
for (const href of styleHrefs) {
|
||||
const selector = `link[data-product-template-style="${href}"]`
|
||||
if (document.head.querySelector(selector)) continue
|
||||
if (document.head.querySelector(selector) || hasStylesheet(href)) continue
|
||||
|
||||
const link = document.createElement('link')
|
||||
link.rel = 'stylesheet'
|
||||
@@ -144,14 +189,11 @@ export function ProductTemplateMount({
|
||||
|
||||
for (const src of scriptSrcs) {
|
||||
const script = document.createElement('script')
|
||||
const url = new URL(src, window.location.origin)
|
||||
url.searchParams.set('templateMount', mountId)
|
||||
url.searchParams.set('attempt', `${attempt}`)
|
||||
script.src =
|
||||
url.origin === window.location.origin ? `${url.pathname}${url.search}${url.hash}` : url.toString()
|
||||
const runIndex = scriptRunIndexes.get(src) ?? 0
|
||||
script.src = toSameOriginScriptSrc(src, attempt, runIndex)
|
||||
script.type = 'module'
|
||||
script.crossOrigin = 'anonymous'
|
||||
script.dataset.productTemplateScript = `${templateId}:${src}:${attempt}`
|
||||
script.dataset.productTemplateScript = `${templateId}:${src}:${runIndex}:${attempt}:${mountId}`
|
||||
script.onload = () => {
|
||||
pendingScripts = Math.max(0, pendingScripts - 1)
|
||||
if (pendingScripts === 0) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { startTransition, useState } from 'react'
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { getChinaTodayInputDate } from '@/lib/china-time'
|
||||
import { FormSuccessDialog } from '@/components/site/form-success-dialog'
|
||||
|
||||
type ProductOption = {
|
||||
model: string
|
||||
@@ -20,6 +21,23 @@ type FormState = {
|
||||
website: string
|
||||
}
|
||||
|
||||
type SuccessDialogState = {
|
||||
message: string
|
||||
title: string
|
||||
}
|
||||
|
||||
function getSupportSuccessDialog(locale: Locale): SuccessDialogState {
|
||||
return locale === 'zh'
|
||||
? {
|
||||
message: '售后支持请求已提交,后台会继续跟进处理。',
|
||||
title: '支持请求已提交',
|
||||
}
|
||||
: {
|
||||
message: 'Your support request has been submitted successfully. Our team will continue the follow-up process.',
|
||||
title: 'Support request submitted',
|
||||
}
|
||||
}
|
||||
|
||||
function getInitialState(): FormState {
|
||||
return {
|
||||
country: '',
|
||||
@@ -44,18 +62,18 @@ export function SupportContactForm({
|
||||
const [form, setForm] = useState<FormState>(() => getInitialState())
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [isPending, setIsPending] = useState(false)
|
||||
const [success, setSuccess] = useState<string | null>(null)
|
||||
const [successDialog, setSuccessDialog] = useState<SuccessDialogState | null>(null)
|
||||
const [startedAt] = useState(() => Date.now())
|
||||
|
||||
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault()
|
||||
setError(null)
|
||||
setSuccess(null)
|
||||
setSuccessDialog(null)
|
||||
setIsPending(true)
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/support/contact', {
|
||||
body: JSON.stringify({ ...form, startedAt }),
|
||||
body: JSON.stringify({ ...form, locale, startedAt }),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
method: 'POST',
|
||||
})
|
||||
@@ -71,12 +89,7 @@ export function SupportContactForm({
|
||||
|
||||
startTransition(() => {
|
||||
setForm(getInitialState())
|
||||
setSuccess(
|
||||
data.message ||
|
||||
(locale === 'zh'
|
||||
? '提交成功,支持请求已经进入后台。'
|
||||
: 'Submitted successfully. Your support request has been saved.'),
|
||||
)
|
||||
setSuccessDialog(getSupportSuccessDialog(locale))
|
||||
})
|
||||
} catch (submissionError) {
|
||||
setError(
|
||||
@@ -97,6 +110,15 @@ export function SupportContactForm({
|
||||
|
||||
return (
|
||||
<form className="space-y-5" onSubmit={handleSubmit}>
|
||||
{successDialog ? (
|
||||
<FormSuccessDialog
|
||||
locale={locale}
|
||||
message={successDialog.message}
|
||||
onClose={() => setSuccessDialog(null)}
|
||||
title={successDialog.title}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<div className="hidden" aria-hidden="true">
|
||||
<Field
|
||||
label="Website"
|
||||
@@ -164,12 +186,6 @@ export function SupportContactForm({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{success ? (
|
||||
<div className="rounded-2xl border border-[#3A7D44]/25 bg-[#F2FAF4] px-4 py-3 text-sm text-[#24552C]">
|
||||
{success}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { checkFormSubmissionGuard } from '@/lib/form-submission-guard'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import {
|
||||
getSalesNotificationRecipient,
|
||||
normalizeNotificationLocale,
|
||||
sendSubmissionNotification,
|
||||
} from '@/lib/smtp-notifications'
|
||||
import type { DistributorRequest } from '@/payload-types'
|
||||
@@ -13,6 +14,7 @@ type SubmissionPayload = {
|
||||
country?: string
|
||||
distributionExperience?: string
|
||||
email?: string
|
||||
locale?: string
|
||||
message?: string
|
||||
phone?: string
|
||||
publicWebsite?: string
|
||||
@@ -63,6 +65,10 @@ export async function POST(request: Request) {
|
||||
const message = normalizeText(body.message)
|
||||
const website = normalizeText(body.website)
|
||||
const startedAt = typeof body.startedAt === 'number' ? body.startedAt : 0
|
||||
const notificationLocale = normalizeNotificationLocale(
|
||||
body.locale,
|
||||
request.headers.get('referer'),
|
||||
)
|
||||
|
||||
if (website) {
|
||||
return NextResponse.json({ message: 'Ignored.' }, { status: 200 })
|
||||
@@ -113,6 +119,10 @@ export async function POST(request: Request) {
|
||||
adminPath: `/admin/collections/distributorRequests/${createdRequest.id}`,
|
||||
fields: [
|
||||
{ label: 'Request ID', value: createdRequest.id },
|
||||
{
|
||||
label: 'Form Language',
|
||||
value: notificationLocale === 'zh' ? 'Chinese (zh)' : 'English (en)',
|
||||
},
|
||||
{ label: 'Company Name', value: companyName },
|
||||
{ label: 'Contact Name', value: contactName },
|
||||
{ label: 'Email', value: email },
|
||||
@@ -125,7 +135,7 @@ export async function POST(request: Request) {
|
||||
replyTo: email,
|
||||
subject: `[Eversolo] New distributor request - ${companyName}`,
|
||||
title: 'New distributor request',
|
||||
to: getSalesNotificationRecipient(),
|
||||
to: getSalesNotificationRecipient(notificationLocale),
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Distributor notification email failed', error)
|
||||
|
||||
@@ -3,6 +3,7 @@ import { checkFormSubmissionGuard } from '@/lib/form-submission-guard'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import {
|
||||
getSalesNotificationRecipient,
|
||||
normalizeNotificationLocale,
|
||||
sendSubmissionNotification,
|
||||
} from '@/lib/smtp-notifications'
|
||||
import type { OemRequest } from '@/payload-types'
|
||||
@@ -12,6 +13,7 @@ type SubmissionPayload = {
|
||||
contactName?: string
|
||||
country?: string
|
||||
email?: string
|
||||
locale?: string
|
||||
message?: string
|
||||
phone?: string
|
||||
projectType?: string
|
||||
@@ -61,6 +63,10 @@ export async function POST(request: Request) {
|
||||
const message = normalizeText(body.message)
|
||||
const website = normalizeText(body.website)
|
||||
const startedAt = typeof body.startedAt === 'number' ? body.startedAt : 0
|
||||
const notificationLocale = normalizeNotificationLocale(
|
||||
body.locale,
|
||||
request.headers.get('referer'),
|
||||
)
|
||||
|
||||
if (website) {
|
||||
return NextResponse.json({ message: 'Ignored.' }, { status: 200 })
|
||||
@@ -115,6 +121,10 @@ export async function POST(request: Request) {
|
||||
adminPath: `/admin/collections/oemRequests/${createdRequest.id}`,
|
||||
fields: [
|
||||
{ label: 'Request ID', value: createdRequest.id },
|
||||
{
|
||||
label: 'Form Language',
|
||||
value: notificationLocale === 'zh' ? 'Chinese (zh)' : 'English (en)',
|
||||
},
|
||||
{ label: 'Company Name', value: companyName },
|
||||
{ label: 'Contact Name', value: contactName },
|
||||
{ label: 'Email', value: email },
|
||||
@@ -126,7 +136,7 @@ export async function POST(request: Request) {
|
||||
replyTo: email,
|
||||
subject: `[Eversolo] New OEM inquiry - ${companyName}`,
|
||||
title: 'New OEM inquiry',
|
||||
to: getSalesNotificationRecipient(),
|
||||
to: getSalesNotificationRecipient(notificationLocale),
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('OEM notification email failed', error)
|
||||
|
||||
@@ -3,6 +3,7 @@ import { checkFormSubmissionGuard } from '@/lib/form-submission-guard'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import {
|
||||
getAfterServiceNotificationRecipient,
|
||||
normalizeNotificationLocale,
|
||||
sendSubmissionNotification,
|
||||
} from '@/lib/smtp-notifications'
|
||||
import type { AfterServiceRequest } from '@/payload-types'
|
||||
@@ -12,6 +13,7 @@ type SubmissionPayload = {
|
||||
details?: string
|
||||
email?: string
|
||||
issueSummary?: string
|
||||
locale?: string
|
||||
name?: string
|
||||
productModel?: string
|
||||
purchaseDate?: string
|
||||
@@ -65,6 +67,10 @@ export async function POST(request: Request) {
|
||||
const details = normalizeText(body.details)
|
||||
const website = normalizeText(body.website)
|
||||
const startedAt = typeof body.startedAt === 'number' ? body.startedAt : 0
|
||||
const notificationLocale = normalizeNotificationLocale(
|
||||
body.locale,
|
||||
request.headers.get('referer'),
|
||||
)
|
||||
|
||||
if (website) {
|
||||
return NextResponse.json({ message: 'Ignored.' }, { status: 200 })
|
||||
@@ -118,6 +124,10 @@ export async function POST(request: Request) {
|
||||
adminPath: `/admin/collections/afterServiceRequests/${createdRequest.id}`,
|
||||
fields: [
|
||||
{ label: 'Request ID', value: createdRequest.id },
|
||||
{
|
||||
label: 'Form Language',
|
||||
value: notificationLocale === 'zh' ? 'Chinese (zh)' : 'English (en)',
|
||||
},
|
||||
{ label: 'Name', value: name },
|
||||
{ label: 'Email', value: email },
|
||||
{ label: 'Country', value: country },
|
||||
@@ -130,7 +140,7 @@ export async function POST(request: Request) {
|
||||
replyTo: email,
|
||||
subject: `[Eversolo] New after-service request - ${name}`,
|
||||
title: 'New after-service request',
|
||||
to: getAfterServiceNotificationRecipient(),
|
||||
to: getAfterServiceNotificationRecipient(notificationLocale),
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('After-service notification email failed', error)
|
||||
|
||||
Reference in New Issue
Block a user