Merge pull request 'fix: agenda, qrcode, carousel' (#17) from fix/toan-13072026-fe into develop

Reviewed-on: UKSOURCE/ipv6#17
This commit is contained in:
2026-07-13 22:55:54 +00:00
10 changed files with 121 additions and 18 deletions
+50 -6
View File
@@ -5,6 +5,7 @@ type AgendaItem = {
time: string; time: string;
title: string; title: string;
description: string; description: string;
avatar?: string;
}; };
type Props = { type Props = {
@@ -53,6 +54,25 @@ const renderFormattedText = (text: string) => {
}; };
export default function AgendaSection({ data }: Props) { export default function AgendaSection({ data }: Props) {
const base = process.env.NEXT_PUBLIC_BASE_PATH || "";
const renderAvatar = (avatarUrl: string, sizeClass: string) => (
<div className={`${sizeClass} rounded-full overflow-hidden border border-primary/20 bg-white/5 flex items-center justify-center backdrop-blur-sm shadow-inner shrink-0`}>
{avatarUrl.includes("placeholder") ? (
<svg className="w-1/2 h-1/2 text-primary/60" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z" />
</svg>
) : (
// eslint-disable-next-line @next/next/no-img-element
<img
src={`${base}${avatarUrl}`}
alt="Speaker"
className="w-full h-full object-cover"
/>
)}
</div>
);
return ( return (
<section className="py-16 lg:py-24 max-w-hd mx-auto px-[var(--spacing-gutter)]" id={data.id}> <section className="py-16 lg:py-24 max-w-hd mx-auto px-[var(--spacing-gutter)]" id={data.id}>
{/* Section Header */} {/* Section Header */}
@@ -108,12 +128,24 @@ export default function AgendaSection({ data }: Props) {
{item.time} {item.time}
</span> </span>
</div> </div>
<h3 className="font-[var(--font-display-lg)] text-base lg:text-lg text-on-surface mb-3 leading-snug pr-8"> <h3 className="font-[var(--font-display-lg)] text-base lg:text-lg text-on-surface mb-3 leading-snug text-balance pr-8">
{renderFormattedText(item.title)} {renderFormattedText(item.title)}
</h3> </h3>
{!item.description && item.avatar && (
<div className="flex lg:justify-end justify-start mb-3">
{renderAvatar(item.avatar, "w-10 h-10 lg:w-12 lg:h-12")}
</div>
)}
{item.description && ( {item.description && (
<div className="text-xs lg:text-sm text-on-surface-variant/75 leading-relaxed border-t border-primary/10 pt-3 mt-3"> <div className="text-xs lg:text-sm text-on-surface-variant/75 leading-relaxed border-t border-primary/10 pt-3 mt-3 flex flex-row lg:flex-row-reverse items-center gap-4">
{renderFormattedText(item.description)} {item.avatar && (
<div className="shrink-0">
{renderAvatar(item.avatar, "w-10 h-10 lg:w-12 lg:h-12")}
</div>
)}
<div className="flex-1 lg:text-right">
{renderFormattedText(item.description)}
</div>
</div> </div>
)} )}
</div> </div>
@@ -147,12 +179,24 @@ export default function AgendaSection({ data }: Props) {
{item.time} {item.time}
</span> </span>
</div> </div>
<h3 className="font-[var(--font-display-lg)] text-base lg:text-lg text-on-surface mb-3 leading-snug pr-8"> <h3 className="font-[var(--font-display-lg)] text-base lg:text-lg text-on-surface mb-3 leading-snug text-balance pr-8">
{renderFormattedText(item.title)} {renderFormattedText(item.title)}
</h3> </h3>
{!item.description && item.avatar && (
<div className="flex justify-start mb-3">
{renderAvatar(item.avatar, "w-10 h-10 lg:w-12 lg:h-12")}
</div>
)}
{item.description && ( {item.description && (
<div className="text-xs lg:text-sm text-on-surface-variant/75 leading-relaxed border-t border-primary/10 pt-3 mt-3"> <div className="text-xs lg:text-sm text-on-surface-variant/75 leading-relaxed border-t border-primary/10 pt-3 mt-3 flex flex-row items-center gap-4">
{renderFormattedText(item.description)} {item.avatar && (
<div className="shrink-0">
{renderAvatar(item.avatar, "w-10 h-10 lg:w-12 lg:h-12")}
</div>
)}
<div className="flex-1">
{renderFormattedText(item.description)}
</div>
</div> </div>
)} )}
</div> </div>
+1
View File
@@ -2,6 +2,7 @@
// Tên file chính xác trong public/assets/img/carousel/ // Tên file chính xác trong public/assets/img/carousel/
const ROW_1 = [ const ROW_1 = [
{ name: "IEAS", file: "ieas.png" },
{ name: "NVIDIA", file: "Nvidia_logo.svg.png" }, { name: "NVIDIA", file: "Nvidia_logo.svg.png" },
{ name: "Maxis", file: "Maxis-logo.png" }, { name: "Maxis", file: "Maxis-logo.png" },
{ name: "Singtel", file: "Singtel_logo.svg.png" }, { name: "Singtel", file: "Singtel_logo.svg.png" },
+9 -5
View File
@@ -1,7 +1,7 @@
{ {
"hero": { "hero": {
"badgeIcon": "calendar_today", "badgeIcon": "calendar_today",
"badgeText": "27 August 2026 | Ho Chi Minh City, Vietnam", "badgeText": "27 August 2026 | Hanoi, Vietnam",
"title": "IPv6 - Enhanced for AI & Data Centre Summit 2026", "title": "IPv6 - Enhanced for AI & Data Centre Summit 2026",
"subtitle": "Unlocking the Potential of Next-Gen Infrastructure. Join global leaders in redefining ASEAN's digital connectivity for the age of AI.", "subtitle": "Unlocking the Potential of Next-Gen Infrastructure. Join global leaders in redefining ASEAN's digital connectivity for the age of AI.",
"primaryCta": { "label": "Register", "href": "/registration" }, "primaryCta": { "label": "Register", "href": "/registration" },
@@ -153,7 +153,8 @@
"order": "03", "order": "03",
"time": "9.15 am 9.25 am", "time": "9.15 am 9.25 am",
"title": "Welcome Address by **Prof Emeritus Dr Sureswaran Ramadass**", "title": "Welcome Address by **Prof Emeritus Dr Sureswaran Ramadass**",
"description": "*Chairman, IPv6 Forum Malaysia*\n*Chairman, APAC IPv6 Council*" "description": "*Chairman, IPv6 Forum Malaysia*\n*Chairman, APAC IPv6 Council*",
"avatar": "/assets/img/speakers/speaker.png"
}, },
{ {
"order": "04", "order": "04",
@@ -177,19 +178,22 @@
"order": "07", "order": "07",
"time": "10.00 am 10.40 am", "time": "10.00 am 10.40 am",
"title": "Keynote Address: **\"IPv6 Enhanced, SRv6 & Net5.5G for AI-Native Data Centres\"**", "title": "Keynote Address: **\"IPv6 Enhanced, SRv6 & Net5.5G for AI-Native Data Centres\"**",
"description": "Speaker: Dr. Will Liu, Huawei, China" "description": "Speaker: Dr. Will Liu, Huawei, China",
"avatar": "/assets/img/speakers/placeholder.png"
}, },
{ {
"order": "08", "order": "08",
"time": "10.40 am 11.10 am", "time": "10.40 am 11.10 am",
"title": "**AI, Cybersecurity & Smart Digital Ecosystems**", "title": "**AI, Cybersecurity & Smart Digital Ecosystems**",
"description": "Speaker: Prof. Sureswaran Ramadass, Malaysia\nChairman, IPv6 Forum Malaysia" "description": "Speaker: Prof. Sureswaran Ramadass, Malaysia\nChairman, IPv6 Forum Malaysia",
"avatar": "/assets/img/speakers/speaker.png"
}, },
{ {
"order": "09", "order": "09",
"time": "11.10 am 11.50 am", "time": "11.10 am 11.50 am",
"title": "**Building AI-Ready Data Centre Infrastructure in ASEAN**", "title": "**Building AI-Ready Data Centre Infrastructure in ASEAN**",
"description": "Speaker: Mr. Bayu Hanantasena, Strategic Advisor to CEO, Indosat, Indonesia" "description": "Speaker: Mr. Bayu Hanantasena, Strategic Advisor to CEO, Indosat, Indonesia",
"avatar": "/assets/img/speakers/placeholder.png"
}, },
{ {
"order": "10", "order": "10",
+9 -5
View File
@@ -1,7 +1,7 @@
{ {
"hero": { "hero": {
"badgeIcon": "calendar_today", "badgeIcon": "calendar_today",
"badgeText": "27 Tháng 8/2026 | TP. Hồ Chí Minh, Việt Nam", "badgeText": "27 Tháng 8/2026 | Hà Nội, Việt Nam",
"title": "IPv6 - Enhanced for AI & Data Centre Summit 2026", "title": "IPv6 - Enhanced for AI & Data Centre Summit 2026",
"subtitle": "Khám phá tiềm năng Hạ tầng thế hệ mới. Chung sức định hình kỷ nguyên kết nối số tại ASEAN cùng doanh nghiệp hàng đầu quốc tế.", "subtitle": "Khám phá tiềm năng Hạ tầng thế hệ mới. Chung sức định hình kỷ nguyên kết nối số tại ASEAN cùng doanh nghiệp hàng đầu quốc tế.",
"primaryCta": { "label": "Đăng Ký", "href": "/registration" }, "primaryCta": { "label": "Đăng Ký", "href": "/registration" },
@@ -153,7 +153,8 @@
"order": "03", "order": "03",
"time": "9.15 am 9.25 am", "time": "9.15 am 9.25 am",
"title": "Phát biểu chào mừng bởi **Prof Emeritus Dr Sureswaran Ramadass**", "title": "Phát biểu chào mừng bởi **Prof Emeritus Dr Sureswaran Ramadass**",
"description": "*Chairman, IPv6 Forum Malaysia*\n*Chairman, APAC IPv6 Council*" "description": "*Chairman, IPv6 Forum Malaysia*\n*Chairman, APAC IPv6 Council*",
"avatar": "/assets/img/speakers/speaker.png"
}, },
{ {
"order": "04", "order": "04",
@@ -177,19 +178,22 @@
"order": "07", "order": "07",
"time": "10.00 am 10.40 am", "time": "10.00 am 10.40 am",
"title": "Bài phát biểu Keynote: **\"Tối ưu hóa IPv6, SRv6 & Net5.5G cho Trung tâm Dữ liệu tích hợp AI\"**", "title": "Bài phát biểu Keynote: **\"Tối ưu hóa IPv6, SRv6 & Net5.5G cho Trung tâm Dữ liệu tích hợp AI\"**",
"description": "Speaker: Dr. Will Liu, Huawei, China" "description": "Speaker: Dr. Will Liu, Huawei, China",
"avatar": "/assets/img/speakers/placeholder.png"
}, },
{ {
"order": "08", "order": "08",
"time": "10.40 am 11.10 am", "time": "10.40 am 11.10 am",
"title": "**AI, An ninh mạng & Hệ sinh thái số thông minh**", "title": "**AI, An ninh mạng & Hệ sinh thái số thông minh**",
"description": "Speaker: Prof. Sureswaran Ramadass, Malaysia\nChairman, IPv6 Forum Malaysia" "description": "Speaker: Prof. Sureswaran Ramadass, Malaysia\nChairman, IPv6 Forum Malaysia",
"avatar": "/assets/img/speakers/speaker.png"
}, },
{ {
"order": "09", "order": "09",
"time": "11.10 am 11.50 am", "time": "11.10 am 11.50 am",
"title": "**Xây dựng hạ tầng Trung tâm dữ liệu sẵn sàng cho AI tại ASEAN**", "title": "**Xây dựng hạ tầng Trung tâm dữ liệu sẵn sàng cho AI tại ASEAN**",
"description": "Speaker: Mr. Bayu Hanantasena, Strategic Advisor to CEO, Indosat, Indonesia" "description": "Speaker: Mr. Bayu Hanantasena, Strategic Advisor to CEO, Indosat, Indonesia",
"avatar": "/assets/img/speakers/placeholder.png"
}, },
{ {
"order": "10", "order": "10",
+1 -1
View File
@@ -2,7 +2,7 @@
"hero": { "hero": {
"eyebrow": "OPPORTUNITY AWAITS", "eyebrow": "OPPORTUNITY AWAITS",
"title": "Become a Sponsor", "title": "Become a Sponsor",
"subtitle": "IPv6 - Enhanced AI & Data Centre Summit 2026 — 27 August 2026, Ho Chi Minh City", "subtitle": "IPv6 - Enhanced AI & Data Centre Summit 2026 — 27 August 2026, Hanoi",
"description": "Join industry leaders at the forefront of the technological evolution. We invite innovative companies to co-sponsor an elite gathering focusing on AI, IPv6, Cloud Computing, Cybersecurity, Blockchain, and Data Centres. Align your brand with the architects of tomorrow's infrastructure.", "description": "Join industry leaders at the forefront of the technological evolution. We invite innovative companies to co-sponsor an elite gathering focusing on AI, IPv6, Cloud Computing, Cybersecurity, Blockchain, and Data Centres. Align your brand with the architects of tomorrow's infrastructure.",
"primaryCta": { "label": "Download Prospectus", "href": "#" }, "primaryCta": { "label": "Download Prospectus", "href": "#" },
"secondaryCta": { "label": "Speak to an Agent", "href": "#contact" } "secondaryCta": { "label": "Speak to an Agent", "href": "#contact" }
+1 -1
View File
@@ -2,7 +2,7 @@
"hero": { "hero": {
"eyebrow": "CƠ HỘI ĐANG CHỜ", "eyebrow": "CƠ HỘI ĐANG CHỜ",
"title": "Trở Thành Nhà Tài Trợ", "title": "Trở Thành Nhà Tài Trợ",
"subtitle": "Hội Nghị IPv6 cho AI & Trung Tâm Dữ Liệu 2026 — 27 Tháng 8 năm 2026, TP. Hồ Chí Minh", "subtitle": "Hội Nghị IPv6 cho AI & Trung Tâm Dữ Liệu 2026 — 27 Tháng 8 năm 2026, Hà Nội",
"description": "Cùng các nhà lãnh đạo ngành ở tuyến đầu tiến hóa công nghệ. Chúng tôi mời các công ty đổi mới đồng tài trợ cho sự kiện đẳng cấp tập trung vào AI, IPv6, Điện Toán Đám Mây, An Ninh Mạng, Blockchain và Trung Tâm Dữ Liệu. Kết nối thương hiệu của bạn với những kiến trúc sư của hạ tầng ngày mai.", "description": "Cùng các nhà lãnh đạo ngành ở tuyến đầu tiến hóa công nghệ. Chúng tôi mời các công ty đổi mới đồng tài trợ cho sự kiện đẳng cấp tập trung vào AI, IPv6, Điện Toán Đám Mây, An Ninh Mạng, Blockchain và Trung Tâm Dữ Liệu. Kết nối thương hiệu của bạn với những kiến trúc sư của hạ tầng ngày mai.",
"primaryCta": { "label": "Tải Hồ Sơ Tài Trợ", "href": "#" }, "primaryCta": { "label": "Tải Hồ Sơ Tài Trợ", "href": "#" },
"secondaryCta": { "label": "Liên Hệ Chuyên Viên", "href": "#contact" } "secondaryCta": { "label": "Liên Hệ Chuyên Viên", "href": "#contact" }
Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

+50
View File
@@ -0,0 +1,50 @@
import fs from 'fs';
import path from 'path';
import axios from 'axios';
async function downloadQR(url: string, outputPath: string) {
const qrApiUrl = `https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${encodeURIComponent(url)}`;
const response = await axios({
url: qrApiUrl,
method: 'GET',
responseType: 'stream',
});
return new Promise<void>((resolve, reject) => {
const writer = fs.createWriteStream(outputPath);
response.data.pipe(writer);
writer.on('finish', () => resolve());
writer.on('error', (err) => reject(err));
});
}
async function main() {
const localUrl = 'http://localhost:3000/ipv6/feedback';
const prodUrl = 'https://techvanguard.vn/ipv6/feedback';
const publicDir = path.join(__dirname, '../public/assets/img');
if (!fs.existsSync(publicDir)) {
fs.mkdirSync(publicDir, { recursive: true });
}
const localPath = path.join(publicDir, 'feedback-qr-local.png');
const prodPath = path.join(publicDir, 'feedback-qr-prod.png');
console.log('Downloading QR code for local URL:', localUrl);
await downloadQR(localUrl, localPath);
console.log('Saved local QR to:', localPath);
console.log('Downloading QR code for prod URL:', prodUrl);
await downloadQR(prodUrl, prodPath);
console.log('Saved prod QR to:', prodPath);
// Copy to artifact directory
const artifactDir = 'C:\\Users\\Admin\\.gemini\\antigravity-ide\\brain\\c96f6734-1739-4dd2-b415-68dac04243f7';
if (fs.existsSync(artifactDir)) {
fs.copyFileSync(localPath, path.join(artifactDir, 'feedback-qr-local.png'));
fs.copyFileSync(prodPath, path.join(artifactDir, 'feedback-qr-prod.png'));
console.log('Copied QR codes to artifact directory.');
}
}
main().catch(console.error);