Commit 3102cf75 authored by Phạm Quang Bảo's avatar Phạm Quang Bảo

fix: caption image and register

parent d5228e26
......@@ -2,7 +2,6 @@ export interface NewsItem {
id: string
title: string
thumbnail: string
external_link: string
description: string
release_at: string
is_active: boolean
......
......@@ -23,7 +23,7 @@ type RawHomeThumbnail = {
type RawHomePost = {
id?: string | null;
title?: string | null;
external_link?: string | null;
slug?: string | null;
summary?: string | null;
content?: string | null;
release_at?: string | null;
......@@ -400,7 +400,7 @@ async function fetchHomePostsFromApi() {
const thumbnailPath = item.thumbnail?.path ?? item.thumbnail?.original ?? null;
const title = String(item.title ?? "").trim();
const externalLink = buildPostLink(
item.external_link || (title ? `/${title}` : undefined),
item.slug ? `/${item.slug}` : (title ? `/${title}` : undefined),
item.id ? String(item.id) : "",
"#",
);
......@@ -626,7 +626,7 @@ export function useEventCalendarPosts(currentMonth: Date) {
const thumbnailPath = item.thumbnail?.path ?? item.thumbnail?.original ?? null;
const title = String(item.title ?? "").trim();
const externalLink = buildPostLink(
item.external_link || (title ? `/${title}` : undefined),
item.slug ? `/${item.slug}` : (title ? `/${title}` : undefined),
item.id ? String(item.id) : "",
"#",
);
......
......@@ -2,7 +2,7 @@ import type { Metadata } from "next";
import links from "@links/index";
import {
fetchDynamicPostById,
fetchDynamicPostByExternalLink,
fetchDynamicPostBySlug,
getDynamicPostSeoImage,
getDynamicPostExcerpt,
stripHtml,
......@@ -66,7 +66,7 @@ export async function generateMetadata({
try {
post = postId
? await fetchDynamicPostById(postId)
: await fetchDynamicPostByExternalLink(path);
: await fetchDynamicPostBySlug(path);
} catch {
post = null;
}
......
......@@ -151,7 +151,7 @@ export default function ArticlePage({ category, allCategories }: ArticlePageProp
className="border-b border-[#eceff3] pb-8 last:border-b-0"
>
<Link
href={buildDynamicPostHref(item.external_link, item.id, category.id)}
href={buildDynamicPostHref(item.slug, item.id, category.id)}
className="group grid items-center gap-5 sm:grid-cols-[250px_minmax(0,1fr)]"
>
<div className="relative overflow-hidden rounded-md bg-[#edf1f5] aspect-[25/15] sm:aspect-[5/3]">
......
......@@ -103,7 +103,7 @@ export default function CatalogPage({ category, allCategories }: CatalogPageProp
return (
<Link
key={item.id}
href={buildDynamicPostHref(item.external_link, item.id, category.id)}
href={buildDynamicPostHref(item.slug, item.id, category.id)}
className="group block"
>
<div className="overflow-hidden bg-white shadow-[0_10px_24px_rgba(17,24,39,0.08)]">
......
......@@ -39,6 +39,11 @@ function StructuredImageSection({ section }: { section: DynamicPostContentSectio
height={800}
className="h-auto w-full object-contain"
/>
{item.caption ? (
<figcaption className="mt-2 text-center text-sm text-gray-600">
{item.caption}
</figcaption>
) : null}
</figure>
);
})}
......
......@@ -46,6 +46,7 @@ type RawPostUser = {
type RawPostSectionImage = {
position?: number | null;
caption?: string | null;
image?: {
id?: string | null;
name?: string | null;
......@@ -60,7 +61,6 @@ type RawPostItem = {
id?: string | null;
title?: string | null;
slug?: string | null;
external_link?: string | null;
content?: string | null;
summary?: string | null;
release_at?: string | null;
......@@ -126,11 +126,13 @@ const normalizePath = (value?: string | null) => {
};
export const buildDynamicPostHref = (
path?: string | null,
slug?: string | null,
_id?: string | null,
_categoryId?: string | null,
) => {
return normalizePath(path);
const trimmed = (slug ?? "").trim();
if (!trimmed) return "#";
return normalizePath(trimmed);
};
const getSlugFromPath = (value?: string | null) => {
......@@ -188,6 +190,7 @@ const mapPostContentSections = (item: RawPostItem): DynamicPostContentSection[]
typeof item?.position === "number"
? item.position
: imageIndex + 1,
caption: item?.caption ?? null,
image: item?.image
? {
id: String(item.image.id ?? ""),
......@@ -228,7 +231,6 @@ export const mapPost = (item: RawPostItem): DynamicPostItem => ({
id: String(item.id ?? ""),
title: String(item.title ?? "").trim(),
slug: String(item.slug ?? "").trim(),
external_link: normalizePath(item.external_link),
content: String(item.content ?? ""),
summary: String(item.summary ?? ""),
release_at: item.release_at ?? null,
......@@ -360,26 +362,16 @@ export async function fetchDynamicPostById(id: string) {
return post.id && post.title ? post : null;
}
export async function fetchDynamicPostByExternalLink(path: string) {
export async function fetchDynamicPostBySlug(path: string) {
const normalizedPath = normalizePath(path);
const slug = getSlugFromPath(normalizedPath);
if (slug) {
const slugResult = await fetchDynamicPostList({
page: 1,
pageSize: 1,
filters: buildVisibleNewsFilters([`slug==${slug}`]),
});
if (slugResult.rows[0]) {
return slugResult.rows[0];
}
}
if (!slug) return null;
const result = await fetchDynamicPostList({
page: 1,
pageSize: 1,
filters: buildVisibleNewsFilters([`external_link==${normalizedPath}`]),
filters: buildVisibleNewsFilters([`slug==${slug}`]),
});
return result.rows[0] ?? null;
......@@ -729,7 +721,7 @@ export function useDynamicPostDetail(postId: string, routePath: string, options:
queryFn: () =>
postId
? fetchDynamicPostById(postId)
: fetchDynamicPostByExternalLink(routePath),
: fetchDynamicPostBySlug(routePath),
enabled: options.enabled !== false,
staleTime: options.staleTime ?? 60 * 1000,
});
......
......@@ -80,7 +80,7 @@ function renderSummary(summary?: string) {
type TinVcciApiRow = {
id?: string | null;
title?: string | null;
external_link?: string | null;
slug?: string | null;
published_at?: string | null;
release_at?: string | null;
created_at?: string | null;
......@@ -119,7 +119,7 @@ export default function AboutVcciHcmPage({
((response?.responseData?.rows ?? []) as unknown as TinVcciApiRow[]).map((item) => ({
id: String(item.id ?? ""),
title: String(item.title ?? "").trim(),
externalLink: buildDynamicPostHref(item.external_link?.trim() || "#", item.id ? String(item.id) : ""),
externalLink: buildDynamicPostHref(item.slug?.trim() || "#", item.id ? String(item.id) : ""),
publishedAt: String(item.published_at ?? item.release_at ?? item.created_at ?? ""),
thumbnailUrl:
links.resolveImageUrl(
......
......@@ -115,7 +115,7 @@ export default function MemberRegistrationPage({ post }: MemberRegistrationPageP
<div className="flex justify-center pt-4">
<a
href={`${links.externalApiOrigin}/dang-ky`}
href={`https://vccihcm.vn/dang-ky`}
target="_blank"
rel="noreferrer"
className="inline-flex min-w-[220px] items-center justify-center rounded-[4px] bg-[#2450b5] px-6 py-4 text-[18px] font-semibold text-white transition-colors hover:bg-[#173f9f]"
......
......@@ -15,10 +15,13 @@ type LegalTradeRouterProps = {
category: DynamicCategoryRouteItem;
};
function resolveVariant(post: DynamicPostItem, category: DynamicCategoryRouteItem) {
const url = category.url || post.external_link;
function resolveVariant(
post: DynamicPostItem,
category: DynamicCategoryRouteItem,
fullPath: string,
) {
const url = category.url || fullPath;
const slug = category.slug || post.slug;
const externalLink = post.external_link;
if (
slug === "phap-che" ||
......@@ -30,7 +33,7 @@ function resolveVariant(post: DynamicPostItem, category: DynamicCategoryRouteIte
if (
slug === "giay-chung-nhan-gcn-va-chung-tu-thuong-mai-cttm" ||
url ===
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/giay-chung-nhan-gcn-va-chung-tu-thuong-mai-cttm"
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/giay-chung-nhan-gcn-va-chung-tu-thuong-mai-cttm"
) {
return "certificate-trade-document" as const;
}
......@@ -39,7 +42,7 @@ function resolveVariant(post: DynamicPostItem, category: DynamicCategoryRouteIte
slug === "quy-trinh-tiep-nhan-ho-so-cap-gcn-va-xac-nhan-cttm" ||
slug === "thu-tuc-cap-co" ||
url ===
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/quy-trinh-tiep-nhan-ho-so-cap-gcn-va-xac-nhan-cttm" ||
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/quy-trinh-tiep-nhan-ho-so-cap-gcn-va-xac-nhan-cttm" ||
url === "/xuat-xu-hang-hoa/thu-tuc-cap-co"
) {
return "procedure" as const;
......@@ -51,10 +54,10 @@ function resolveVariant(post: DynamicPostItem, category: DynamicCategoryRouteIte
if (
slug === "bieu-mau-gcn-va-noi-dung-khai-bao-gcn-cttm" ||
externalLink ===
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/bieu-mau-gcn-va-noi-dung-khai-bao-gcn-cttm" ||
fullPath ===
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/bieu-mau-gcn-va-noi-dung-khai-bao-gcn-cttm" ||
url ===
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/bieu-mau-gcn-va-noi-dung-khai-bao-gcn-cttm"
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/bieu-mau-gcn-va-noi-dung-khai-bao-gcn-cttm"
) {
return "forms" as const;
}
......@@ -62,10 +65,10 @@ function resolveVariant(post: DynamicPostItem, category: DynamicCategoryRouteIte
if (
slug === "phi-cap-gcn-va-xac-nhan-cttm" ||
slug === "phi-va-le-phi-cap-co" ||
externalLink ===
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/phi-cap-gcn-va-xac-nhan-cttm" ||
fullPath ===
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/phi-cap-gcn-va-xac-nhan-cttm" ||
url ===
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/phi-cap-gcn-va-xac-nhan-cttm" ||
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/phi-cap-gcn-va-xac-nhan-cttm" ||
url === "/xuat-xu-hang-hoa/phi-va-le-phi-cap-co"
) {
return "fees" as const;
......@@ -75,14 +78,14 @@ function resolveVariant(post: DynamicPostItem, category: DynamicCategoryRouteIte
slug === "diem-cap-va-cap-gcn-va-xac-nhan-cttm" ||
slug === "diem-cap-va-thoi-gian-cap-co" ||
slug === "diem-cap-va-thoi-gian-cap-gcn-va-xac-nhan-cttm" ||
externalLink ===
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/diem-cap-va-cap-gcn-va-xac-nhan-cttm" ||
externalLink ===
"/phap-che-va-xac-nhan-chung-tu-tm/diem-cap-va-thoi-gian-cap-gcn-va-xac-nhan-cttm" ||
fullPath ===
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/diem-cap-va-cap-gcn-va-xac-nhan-cttm" ||
fullPath ===
"/phap-che-va-xac-nhan-chung-tu-tm/diem-cap-va-thoi-gian-cap-gcn-va-xac-nhan-cttm" ||
url ===
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/diem-cap-va-cap-gcn-va-xac-nhan-cttm" ||
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/diem-cap-va-cap-gcn-va-xac-nhan-cttm" ||
url ===
"/phap-che-va-xac-nhan-chung-tu-tm/diem-cap-va-thoi-gian-cap-gcn-va-xac-nhan-cttm" ||
"/phap-che-va-xac-nhan-chung-tu-tm/diem-cap-va-thoi-gian-cap-gcn-va-xac-nhan-cttm" ||
url === "/xuat-xu-hang-hoa/diem-cap-va-thoi-gian-cap-co"
) {
return "locations" as const;
......@@ -91,10 +94,10 @@ function resolveVariant(post: DynamicPostItem, category: DynamicCategoryRouteIte
if (
slug === "thong-tin-lien-he" ||
slug === "thong-tin-lien-he-co" ||
externalLink ===
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/thong-tin-lien-he" ||
fullPath ===
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/thong-tin-lien-he" ||
url ===
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/thong-tin-lien-he" ||
"/phap-che-cap-giay-chung-nhan-va-xac-nhan-chung-tu-thuong-mai/thong-tin-lien-he" ||
url === "/xuat-xu-hang-hoa/thong-tin-lien-he-co"
) {
return "contact" as const;
......@@ -107,17 +110,18 @@ export default function LegalTradePages({ post, category }: LegalTradeRouterProp
const params = useParams();
const pathSegments = Array.isArray(params.slug) ? params.slug : [params.slug];
const currentSlug = pathSegments.at(-1) ?? "";
const fullPath = `/${pathSegments.filter(Boolean).join("/")}`;
const variant = resolveVariant(
{
...post,
slug: currentSlug || post.slug,
external_link: `/${pathSegments.filter(Boolean).join("/")}` || post.external_link,
},
{
...category,
slug: currentSlug || category.slug,
url: `/${pathSegments.filter(Boolean).join("/")}` || category.url,
url: fullPath || category.url,
},
fullPath,
);
if (variant === "phap-che") {
......
......@@ -49,6 +49,7 @@ export type DynamicPostContentSection = {
image_columns: number;
images: Array<{
position: number;
caption: string | null;
image: {
id: string;
name: string;
......@@ -64,7 +65,6 @@ export type DynamicPostItem = {
id: string;
title: string;
slug: string;
external_link: string;
content: string;
summary: string;
release_at: string | null;
......
......@@ -50,7 +50,7 @@ function SearchResultItem({ item, index }: { item: DynamicPostItem; index: numbe
return (
<article className="border-b border-[#eceff3] pb-8 last:border-b-0">
<Link
href={buildDynamicPostHref(item.external_link, item.id)}
href={buildDynamicPostHref(item.slug, item.id)}
className="group grid gap-5 sm:grid-cols-[250px_minmax(0,1fr)]"
>
<div className="overflow-hidden rounded-md bg-[#edf1f5]">
......
......@@ -52,7 +52,6 @@ function formatFieldName(field: string): string {
slug: "Slug",
published_at: "Ngày xuất bản",
expired_at: "Ngày hết hạn",
external_link: "Liên kết ngoài",
};
return fieldMap[field] || field;
}
......
......@@ -87,6 +87,7 @@ function Footer() {
const [emailError, setEmailError] = useState("");
const [checkError, setCheckError] = useState(false);
const [message, setMessage] = useState("");
const [messageType, setMessageType] = useState<"success" | "error">("success");
const [submitting, setSubmitting] = useState(false);
const { data: siteInformationResponse } =
......@@ -156,6 +157,7 @@ function Footer() {
let hasError = false;
setMessage("");
setMessageType("success");
if (!trimmedEmail) {
setEmailError("Thông tin bắt buộc");
......@@ -182,13 +184,15 @@ function Footer() {
await postApiV10NewsletterSubscription({ email: trimmedEmail.trim() });
setEmail("");
setAccepted(false);
setMessageType("success");
setMessage("Đăng ký nhận thông tin thành công.");
} catch (error) {
setMessage(
const errorMessage =
error instanceof Error
? error.message
: "Không thể đăng ký nhận thông tin.",
);
: "Không thể đăng ký nhận thông tin.";
setMessageType("error");
setMessage(errorMessage);
} finally {
setSubmitting(false);
}
......@@ -251,7 +255,17 @@ function Footer() {
) : null}
{message ? (
<p className="mt-2 text-[12px] text-[#b8d8ff]">{message}</p>
<div
className={`mt-3 flex items-center gap-2 rounded-[6px] px-4 py-3 text-[13px] font-medium ${messageType === "success"
? "bg-[#1a8754] text-white"
: "bg-[#c0392b] text-white"
}`}
>
<span className="text-base leading-none">
{messageType === "success" ? "✓" : "✕"}
</span>
<span>{message}</span>
</div>
) : null}
</div>
</div>
......@@ -346,7 +360,7 @@ function Footer() {
href="https://vcci-hcm.org.vn/"
target="_blank"
rel="noreferrer"
className="font-semibold text-white transition-colors hover:text-[#f7b500]"
className="font-medium text-white transition-colors hover:text-[#f7b500]"
>
VCCI-HCM
</a>
......@@ -355,7 +369,7 @@ function Footer() {
href="https://meu.com.vn/"
target="_blank"
rel="noreferrer"
className="font-semibold text-white transition-colors hover:text-[#f7b500]"
className="font-medium text-white transition-colors hover:text-[#f7b500]"
>
MEU Solutions
</a>
......
......@@ -171,7 +171,6 @@ interface CmsCategoryNode extends CmsCategoryItem {
interface CmsRawPostItem {
id?: string;
title?: string;
external_link?: string | null;
content?: string | null;
release_at?: string | null;
is_active?: boolean | null;
......@@ -803,7 +802,6 @@ export async function createCmsNewsItem(input: {
slug: input.slug,
summary: input.summary,
type: input.type === "baiviettrang" ? "page" : "news",
external_link: input.slug ? `/${input.slug}` : "/",
content: input.summary || "",
category_ids: input.category_ids,
thumbnail_id: input.thumbnail_id ?? null,
......@@ -864,7 +862,6 @@ export async function updateCmsNewsItem(
slug: input.slug,
summary: input.summary,
type: input.type === "baiviettrang" ? "page" : "news",
external_link: input.slug ? `/${input.slug}` : "/",
content: input.summary || "",
category_ids: input.category_ids,
thumbnail_id: input.thumbnail_id ?? null,
......
......@@ -2,7 +2,6 @@ export interface NewsDetailItem {
id: string
title: string
thumbnail: string
external_link: string
description: string
release_at: string
is_active: boolean
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment