Commit f818e2b5 authored by Lê Bảo Hồng Đức's avatar Lê Bảo Hồng Đức

Merge branch 'feat/video-newsletter' into 'develop-news'

fix

See merge request !62
parents 5257ce93 adfed5ce
import Axios, { AxiosError, AxiosHeaders, AxiosRequestConfig, InternalAxiosRequestConfig } from "axios";
import links from "@/links";
import {
ensureValidAdminAccessToken,
refreshAdminAccessToken,
......@@ -10,7 +11,7 @@ interface RetriableAxiosRequestConfig extends InternalAxiosRequestConfig {
const createAxiosInstance = () => {
const instance = Axios.create({
baseURL: `${process.env.NEXT_PUBLIC_BACKEND_HOST}/api/v1.0`,
baseURL: links.apiEndpoint,
withCredentials: true,
});
......
import { NewsItem } from "@/api/types/news";
import BASE_URL from "@/links";
import { resolveUploadUrl } from "@/links";
import dayjs from "dayjs";
import AppEditorContent from "@/components/shared/editor-content";
import Link from "next/link";
......@@ -12,7 +12,7 @@ function CardNews({ news }: { news: NewsItem }) {
className="flex flex-row gap-2 mb-2 sm:gap-3 sm:mb-3"
>
<ImageNext
src={`${BASE_URL.imageEndpoint}${news.thumbnail}`}
src={resolveUploadUrl(news.thumbnail)}
alt={news.title}
className="aspect-3/2 object-cover"
width={130}
......
import { EventItem } from '@/api/types/event'
import BASE_URL from '@/links'
import { resolveUploadUrl } from '@/links'
import dayjs from 'dayjs';
import AppEditorContent from '@/components/shared/editor-content';
import Link from "next/link";
......@@ -12,7 +12,7 @@ function CardEvent({ event }: { event: EventItem }) {
className='flex flex-row gap-2 mb-2 sm:gap-3 sm:mb-3 p-2 sm:p-3 border border-gray-200 bg-white rounded-md'
>
<ImageNext
src={`${BASE_URL.imageEndpoint}${event.image}`}
src={resolveUploadUrl(event.image)}
alt={event.name}
className='aspect-3/2 object-cover'
width={130}
......@@ -31,4 +31,4 @@ function CardEvent({ event }: { event: EventItem }) {
);
}
export default CardEvent;
\ No newline at end of file
export default CardEvent;
import { NewsItem } from "@/api/types/news";
import BASE_URL from "@/links";
import { resolveUploadUrl } from "@/links";
import dayjs from "dayjs";
import AppEditorContent from "@/components/shared/editor-content";
import Link from "next/link";
......@@ -12,7 +12,7 @@ function CardNews({ news }: { news: NewsItem }) {
className="flex flex-row gap-2 mb-2 sm:gap-3 sm:mb-3"
>
<ImageNext
src={`${BASE_URL.imageEndpoint}${news.thumbnail}`}
src={resolveUploadUrl(news.thumbnail)}
alt={news.title}
className="aspect-3/2 object-cover"
width={130}
......
import { NewsItem } from "@/api/types/news";
import BASE_URL from "@/links";
import { resolveUploadUrl } from "@/links";
import dayjs from "dayjs";
import Link from "next/link";
import ImageNext from "@/components/shared/image-next";
......@@ -11,7 +11,7 @@ function CardNews({ news }: { news: NewsItem }) {
className="flex flex-row gap-2 mb-2 sm:gap-3 sm:mb-3"
>
<ImageNext
src={`${BASE_URL.imageEndpoint}${news.thumbnail}`}
src={resolveUploadUrl(news.thumbnail)}
alt={news.title}
className="aspect-3/2 object-cover"
width={130}
......
......@@ -3,7 +3,7 @@
import * as React from "react";
import { useQuery } from "@tanstack/react-query";
import { useCustomClient } from "@/api/mutator/custom-client";
import Links from "@/links";
import Links, { resolveUploadUrl } from "@/links";
type RawHomeCategory = {
id?: string | null;
......@@ -153,12 +153,8 @@ const resolveAssetUrl = (value?: string | null) => {
const trimmed = value?.trim();
if (!trimmed) return "/thumbnail.png";
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return trimmed;
if (trimmed.startsWith("/")) {
return `${Links.imageEndpoint.replace(/\/+$/, "")}${trimmed}`;
}
return `${Links.imageEndpoint}${trimmed.replace(/^\/+/, "")}`;
return resolveUploadUrl(trimmed);
};
const sortByPublishedDesc = (items: HomePostItem[]) =>
......
......@@ -6,7 +6,7 @@ import { notFound, useParams } from "next/navigation";
import dayjs from "dayjs";
import parse from "html-react-parser";
import BASE_URL from "@/links";
import { resolveUploadUrl } from "@/links";
import { useGetEvents } from "@/api/endpoints/event";
import { EventApiResponse } from "@/api/types/event";
......@@ -58,7 +58,7 @@ export default function EventDetailPage() {
{eventsDetail?.responseData?.rows[0].image ? (
<div className="w-full h-52 relative ">
<EventImage
src={`${BASE_URL.imageEndpoint}${eventsDetail?.responseData?.rows[0].image}`}
src={resolveUploadUrl(eventsDetail?.responseData?.rows[0].image)}
alt={eventsDetail?.responseData?.rows[0]?.name || "image"}
/>
</div>
......
import type { Category } from "@/api/models/category";
import { useCustomClient } from "@/api/mutator/custom-client";
import Links from "@/links";
import Links, { resolveUploadUrl } from "@/links";
import { getCategoryFallbackResponse } from "@/mockdata/categories";
import type {
DynamicCategoryMenuItem,
......@@ -293,10 +293,8 @@ export function resolveDynamicPostImage(thumbnail?: DynamicPostThumbnail) {
const value = thumbnail?.path ?? thumbnail?.original ?? thumbnail?.url ?? "";
if (!value) return "/thumbnail.png";
if (value.startsWith("http://") || value.startsWith("https://")) return value;
if (value.startsWith("/")) return `${Links.imageEndpoint.replace(/\/+$/, "")}${value}`;
return `${Links.imageEndpoint}${value.replace(/^\/+/, "")}`;
return resolveUploadUrl(value);
}
export function stripHtml(value?: string | null) {
......
......@@ -19,6 +19,7 @@ import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import logo from "@/assets/VCCI-HCM-logo-VN-2025.png";
import links from "@/links";
import { loginAdmin } from "@/lib/auth/admin-auth";
import useAuthStore from "@/store/useAuthStore";
......@@ -85,7 +86,7 @@ function getAuthErrorMessage(error: unknown, fallback: string) {
}
async function postAuthJson<TResponse, TBody>(path: string, body: TBody) {
const response = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_HOST}/api/v1.0${path}`, {
const response = await fetch(`${links.apiEndpoint}${path}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
......
import { EventItem } from '@/api/types/event';
import Links from '@links/index'
import { resolveUploadUrl } from '@links/index'
import dayjs from 'dayjs';
// Helper: remove <img> tags and extract plain text from HTML
......@@ -27,7 +27,7 @@ const CardEvents = ({ event, link }: { event: EventItem, link: string }) => {
className="flex flex-col hover:no-underline sm:flex-row gap-2 mb-6 bg-white rounded-lg shadow-sm p-4 border items-start min-w-0"
>
<img
src={`${Links.imageEndpoint}${event.image}`}
src={resolveUploadUrl(event.image)}
alt={event.name}
className="w-full sm:w-56 md:w-64 h-40 md:h-36 object-cover shrink-0"
onError={(e) => {
......@@ -48,4 +48,4 @@ const CardEvents = ({ event, link }: { event: EventItem, link: string }) => {
)
}
export default CardEvents;
\ No newline at end of file
export default CardEvents;
import dayjs from "dayjs";
import Links from "@links/index";
import { resolveUploadUrl } from "@links/index";
import { NewsItem } from "@/api/types/news";
const stripImagesAndHtml = (html?: string) => {
......@@ -21,9 +21,7 @@ const stripImagesAndHtml = (html?: string) => {
const resolveThumbnail = (thumbnail?: string) => {
if (!thumbnail) return "/img-error.png";
if (thumbnail.startsWith("http://") || thumbnail.startsWith("https://")) return thumbnail;
if (thumbnail.startsWith("/")) return `${Links.imageEndpoint.replace(/\/+$/, "")}${thumbnail}`;
return `${Links.imageEndpoint}${thumbnail}`;
return resolveUploadUrl(thumbnail);
};
const CardNews = ({ news, link }: { news: NewsItem; link: string }) => {
......
"use client";
import { useCustomClient } from "@/api/mutator/custom-client";
import { resolveUploadUrl } from "@/links";
import { categoryFallbackRows } from "@/mockdata/categories";
export type CmsHeaderCategoryType = "category" | "page" | "news";
......@@ -350,7 +351,7 @@ const transformPost = (
id: post.thumbnail.id,
name: post.thumbnail.original ?? post.thumbnail.path ?? "thumbnail",
alt: post.thumbnail.original ?? post.thumbnail.path ?? "thumbnail",
url: post.thumbnail.path ?? "",
url: resolveUploadUrl(post.thumbnail.path),
}
: null,
is_hidden: Boolean(post.is_hidden),
......
"use client";
import { useCustomClient } from "@/api/mutator/custom-client";
import Links from "@/links";
import { resolveUploadUrl } from "@/links";
import type { AdminMediaItem } from "@/mockdata/admin-news";
export type CmsFileItem = {
......@@ -44,10 +44,7 @@ export const resolveCmsFileUrl = (path?: string | null) => {
const value = path?.trim();
if (!value) return "/img-error.png";
if (value.startsWith("http://") || value.startsWith("https://")) return value;
if (value.startsWith("/")) return `${Links.imageEndpoint.replace(/\/+$/, "")}${value}`;
return `${Links.imageEndpoint}${value.replace(/^\/+/, "")}`;
return resolveUploadUrl(value);
};
export const toAdminMediaItem = (item: CmsFileItem): AdminMediaItem => ({
......
......@@ -5,8 +5,9 @@ import useAuthStore, {
type AuthenticatedAdminSession,
type AuthenticatedAdminUser,
} from "@/store/useAuthStore";
import links from "@/links";
const AUTH_BASE_URL = `${process.env.NEXT_PUBLIC_BACKEND_HOST}/api/v1.0/auth`;
const AUTH_BASE_URL = `${links.apiEndpoint}/auth`;
const SESSION_EXPIRED_MESSAGE = "Phiên đăng nhập đã hết hạn. Vui lòng đăng nhập lại.";
interface AuthEnvelope<T> {
......
......@@ -2,6 +2,7 @@ declare const links: {
analyticsGoogle: string
apiEndpoint: string
imageEndpoint: string
resolveUploadUrl: (value?: string | null) => string
backendHost: string
backendProtocol: string
backendPathname: string
......
const DEFAULT_BACKEND_ORIGIN = "https://vietprodev.duckdns.org/gateway/vcci-news-backend";
const normalizeOrigin = (value?: string | null) => value?.trim().replace(/\/+$/, "") || "";
const readOrigin = (key: "NEXT_PUBLIC_BACKEND_HOST" | "NEXT_PUBLIC_FRONTEND_HOST") => {
const envOrigin = normalizeOrigin(process.env[key]);
if (envOrigin) return envOrigin;
if (key === "NEXT_PUBLIC_BACKEND_HOST" && process.env.NODE_ENV === "production") {
return DEFAULT_BACKEND_ORIGIN;
}
if (typeof window !== "undefined" && key === "NEXT_PUBLIC_FRONTEND_HOST") {
return normalizeOrigin(window.location.origin);
......@@ -33,11 +38,27 @@ const frontendOrigin = readOrigin("NEXT_PUBLIC_FRONTEND_HOST");
const backendUrl = toUrl(backendOrigin);
const frontendUrl = toUrl(frontendOrigin);
const uploadsEndpoint = backendOrigin ? `${backendOrigin}/uploads/` : "/uploads/";
export const resolveUploadUrl = (value?: string | null) => {
const trimmed = value?.trim();
if (!trimmed) return "";
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return trimmed;
const cleanPath = trimmed.replace(/^\/+/, "").replace(/^api\/uploads\//, "uploads/");
if (cleanPath.startsWith("uploads/")) {
return backendOrigin ? `${backendOrigin}/${cleanPath}` : `/${cleanPath}`;
}
return `${uploadsEndpoint}${cleanPath}`;
};
const links = {
analyticsGoogle: "G-C9TEK9BS4C",
apiEndpoint: backendOrigin ? `${backendOrigin}/api/v1.0` : "/api/v1.0",
imageEndpoint: backendOrigin ? `${backendOrigin}/` : "/",
imageEndpoint: uploadsEndpoint,
resolveUploadUrl,
backendHost: backendUrl?.hostname || "",
backendProtocol: backendUrl?.protocol.replace(":", "") || "",
backendPathname: backendUrl?.pathname.replace(/\/+$/, "") || "/",
......
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