Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions components/ArticleList.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,39 @@ import Link from '@/components/Link'
import ArticleThumbnail from './ArticleThumbnail'

const ArticleList = ({ slug, title, summary, tags, image }) => {
const visibleTags = (tags || []).slice(0, 3)

return (
<div className="group h-full">
<Link href={`/blog/${slug}`}>
<div className="relative flex h-full flex-col overflow-hidden rounded-2xl bg-white shadow-lg transition-all hover:-translate-y-2 hover:shadow-2xl dark:bg-gray-800">
<div className="relative overflow-hidden rounded-t-2xl pb-60">
<Link href={`/blog/${slug}`} className="block h-full">
<article className="surface-panel relative flex h-full flex-col overflow-hidden transition-all duration-300 hover:-translate-y-1">
<div className="relative overflow-hidden rounded-t-2xl pb-[58%]">
<ArticleThumbnail slug={slug} title={title} image={image} />
<div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent opacity-0 transition-opacity group-hover:opacity-100"></div>
<div className="from-slate-950/30 absolute inset-0 bg-gradient-to-t to-transparent opacity-0 transition-opacity group-hover:opacity-100" />
</div>

<div className="flex flex-1 flex-col p-6">
<div className="flex flex-1 flex-col p-5 sm:p-6">
<div className="mb-4 flex flex-wrap gap-2">
{tags.slice(0, 3).map((tag) => (
{visibleTags.map((tag) => (
<span
key={tag}
className="text-xs inline-block rounded-full bg-orange-100 px-3 py-1 font-medium text-orange-800 dark:bg-orange-900/30 dark:text-orange-300"
className="text-xs inline-flex items-center rounded-full border border-orange-200 bg-orange-50 px-2.5 py-1 font-medium text-orange-700 dark:border-orange-700/60 dark:bg-orange-900/30 dark:text-orange-300"
>
{tag}
</span>
))}
</div>

<h2 className="mb-3 text-xl font-bold text-gray-900 transition-colors group-hover:text-orange-600 dark:text-white dark:group-hover:text-orange-400">
<h2 className="mb-3 text-xl font-bold leading-tight text-slate-900 transition-colors group-hover:text-orange-600 dark:text-slate-100 dark:group-hover:text-orange-300">
{title}
</h2>

<p className="line-clamp-3 flex-1 leading-relaxed text-gray-600 dark:text-gray-300">
<p className="line-clamp-3 flex-1 text-sm leading-7 text-slate-600 dark:text-slate-300">
{summary}
</p>

<div className="mt-4 flex items-center font-medium text-orange-500 group-hover:text-orange-600 dark:text-orange-400 dark:group-hover:text-orange-300">
<span className="text-sm">Đọc thêm</span>
<div className="mt-5 inline-flex items-center text-sm font-semibold text-orange-600 dark:text-orange-300">
Đọc thêm
<svg
className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-1"
fill="none"
Expand All @@ -48,7 +50,7 @@ const ArticleList = ({ slug, title, summary, tags, image }) => {
</svg>
</div>
</div>
</div>
</article>
</Link>
</div>
)
Expand Down
7 changes: 4 additions & 3 deletions components/ArticleThumbnail.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import Link from '@/components/Link'
import Image from 'next/image'

const ArticleThumbnail = ({ slug, title, image }) => {
const ArticleThumbnail = ({ title, image }) => {
const thumbnail = image || '/static/images/default-ogp.png'

return (
<Image
alt={title}
className="absolute inset-0 h-full w-full transform object-cover opacity-90 transition duration-700 group-hover:opacity-100 dark:opacity-80"
src={image}
src={thumbnail}
layout="fill"
decoding="async"
sizes="50vw"
Expand Down
56 changes: 25 additions & 31 deletions components/BottomNav.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,32 @@ const BottomNav = () => {
const route = `/${router.pathname.split('/')[1]}`

return (
<div className="sm:hidden">
<nav className="pb-safe fixed bottom-0 z-50 w-full border-t bg-zinc-100 dark:border-zinc-800 dark:bg-zinc-900">
<div className="relative mx-auto flex h-16 max-w-md items-center justify-around px-6">
<div className="md:hidden">
<nav className="pb-safe fixed bottom-0 z-50 w-full border-t border-slate-200/80 bg-white/95 backdrop-blur-lg dark:border-slate-700/70 dark:bg-slate-900/95">
<div className="relative mx-auto flex h-16 max-w-md items-center justify-around px-5">
{links.map(({ href, label, icon, isHighlighted }) => (
<Link alt={label} key={label} href={href}>
{isHighlighted ? (
<button className="relative -top-4 flex h-14 w-14 transform flex-col items-center justify-center rounded-full bg-gradient-to-r from-orange-500 to-orange-600 text-white shadow-lg transition-transform hover:scale-105">
<div className="flex flex-col items-center">
{icon}
<span className="mt-1 text-[10px] font-medium">{label}</span>
</div>
</button>
) : (
<button
className={`flex h-full w-full flex-col items-center justify-center space-y-1 ${
route === href
? 'text-orange-500 dark:text-orange-400'
: 'text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-50'
}`}
>
{icon}
<span
className={`text-[13px] ${
route === href
? 'text-orange-500 dark:text-orange-400'
: 'text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-50'
}`}
>
{label}
</span>
</button>
)}
<Link
key={label}
href={href}
aria-label={label}
className={
isHighlighted
? 'shadow-orange-500/35 relative -top-4 flex h-14 w-14 flex-col items-center justify-center rounded-full bg-gradient-to-r from-orange-500 to-orange-600 text-white shadow-lg'
: `flex h-full min-w-[52px] flex-col items-center justify-center gap-1 ${
(href === '/' ? router.pathname === '/' : route === href)
? 'text-orange-500 dark:text-orange-300'
: 'text-slate-600 dark:text-slate-400'
}`
}
>
{icon}
<span
className={`text-[11px] font-medium ${
isHighlighted ? 'text-white' : 'tracking-wide'
}`}
>
{label}
</span>
</Link>
))}
</div>
Expand Down
90 changes: 55 additions & 35 deletions components/Card.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,61 @@
import Image from './Image'
import Link from './Link'

const Card = ({ title, description, imgSrc, href, showMore = true, isFree }) => (
<div className="w-full p-4 md:w-1/2">
<Link alt={`Link tới ${title}`} href={href} className="block h-full">
<div className="hover:scale-105flex flex h-full h-full flex-col flex-col overflow-hidden overflow-hidden rounded-lg rounded-lg bg-gray-100 transition duration-500 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700">
{imgSrc && (
<div>
<Image
alt={title}
src={imgSrc}
className="h-64 w-full object-cover object-center"
width={544}
height={306}
loading="lazy"
/>
const Card = ({ title, description, imgSrc, href, showMore = true, isFree }) => {
const badge =
isFree === undefined ? null : isFree ? (
<span className="text-xs inline-flex w-fit items-center rounded-full border border-emerald-200 bg-emerald-50 px-2.5 py-1 font-semibold tracking-wide text-emerald-700 dark:border-emerald-700/60 dark:bg-emerald-900/30 dark:text-emerald-300">
MIỄN PHÍ
</span>
) : (
<span className="text-xs inline-flex w-fit items-center rounded-full border border-orange-200 bg-orange-50 px-2.5 py-1 font-semibold tracking-wide text-orange-700 dark:border-orange-700/60 dark:bg-orange-900/30 dark:text-orange-300">
PREMIUM
</span>
)

return (
<div className="h-full">
<Link alt={`Link tới ${title}`} href={href} className="group block h-full">
<article className="surface-panel flex h-full flex-col overflow-hidden transition-all duration-300 hover:-translate-y-1">
{imgSrc && (
<div className="aspect-[16/10] overflow-hidden border-b border-slate-200 dark:border-slate-700">
<Image
alt={title}
src={imgSrc}
className="h-full w-full object-cover object-center transition-transform duration-500 group-hover:scale-105"
width={544}
height={306}
loading="lazy"
/>
</div>
)}

<div className="flex flex-1 flex-col p-5 sm:p-6">
{badge && <div className="mb-3">{badge}</div>}
<h2 className="text-xl font-bold leading-tight text-slate-900 transition-colors group-hover:text-orange-600 dark:text-slate-100 dark:group-hover:text-orange-300">
{title}
</h2>
<p className="mt-3 flex-1 text-sm leading-7 text-slate-600 dark:text-slate-300">
{description}
</p>
{showMore && (
<span className="mt-5 inline-flex items-center text-sm font-semibold text-orange-600 dark:text-orange-300">
Xem chi tiết
<svg className="ml-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 7l5 5m0 0l-5 5m5-5H6"
/>
</svg>
</span>
)}
</div>
)}
<div className="flex-grow p-6">
<h2 className="mb-3 text-xl font-bold leading-8 tracking-tight sm:text-2xl">
{isFree !== undefined &&
(isFree ? (
<span className="mb-2 inline-block rounded bg-green-100 px-2.5 py-0.5 text-sm font-medium text-green-800 dark:bg-green-900 dark:text-green-300">
MIỄN PHÍ
</span>
) : (
<span className="mb-2 inline-block rounded px-2.5 py-0.5 text-sm font-medium text-orange-800 dark:bg-orange-900 dark:text-orange-300">
💎 PREMIUM
</span>
))}
<div>{title}</div>
</h2>
<p className="prose mb-3 max-w-none text-gray-500 dark:text-gray-400">{description}</p>
</div>
</div>
</Link>
</div>
)
</article>
</Link>
</div>
)
}

export default Card
44 changes: 31 additions & 13 deletions components/CourseRegistrationForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { FaUser, FaEnvelope, FaPhone, FaGraduationCap, FaSpinner, FaTag } from '

export default function CourseRegistrationForm({
courseTitle = 'AWS SAA-C03',
courseLabel,
theme = 'orange',
onSuccess,
}) {
Expand Down Expand Up @@ -177,6 +178,8 @@ export default function CourseRegistrationForm({
button:
'bg-gradient-to-r from-orange-600 to-orange-500 hover:from-orange-700 hover:to-orange-600',
link: 'text-orange-600',
courseTag:
'border-orange-200 bg-orange-50 text-orange-700 dark:border-orange-500/40 dark:bg-orange-500/10 dark:text-orange-200',
},
purple: {
bg: 'bg-gradient-to-br from-purple-50 to-purple-100 dark:from-purple-900/10 dark:to-purple-800/10',
Expand All @@ -186,6 +189,8 @@ export default function CourseRegistrationForm({
button:
'bg-gradient-to-r from-purple-600 to-pink-500 hover:from-purple-700 hover:to-pink-600',
link: 'text-purple-600',
courseTag:
'border-purple-200 bg-purple-50 text-purple-700 dark:border-purple-500/40 dark:bg-purple-500/10 dark:text-purple-200',
},
blue: {
bg: 'bg-gradient-to-br from-blue-50 to-blue-100 dark:from-blue-900/10 dark:to-blue-800/10',
Expand All @@ -194,10 +199,13 @@ export default function CourseRegistrationForm({
hover: 'hover:bg-blue-50 dark:hover:bg-blue-900/20',
button: 'bg-gradient-to-r from-blue-600 to-blue-500 hover:from-blue-700 hover:to-blue-600',
link: 'text-blue-600',
courseTag:
'border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-500/40 dark:bg-blue-500/10 dark:text-blue-200',
},
}

const currentTheme = themeColors[theme]
const currentTheme = themeColors[theme] || themeColors.orange
const selectedCourseLabel = courseLabel || courseTitle

return (
<section id="registration-form" className={`py-20 ${currentTheme.bg}`}>
Expand All @@ -209,6 +217,12 @@ export default function CourseRegistrationForm({
<p className="text-xl text-gray-600 dark:text-gray-300">
Điền thông tin để nhận tư vấn chi tiết và ưu đãi đặc biệt
</p>
<div
className={`mt-4 inline-flex flex-wrap items-center justify-center gap-2 rounded-full border px-4 py-2 text-sm font-semibold ${currentTheme.courseTag}`}
>
<span>Khóa đang chọn:</span>
<span>{selectedCourseLabel}</span>
</div>
</div>

<div className="rounded-2xl bg-white p-8 shadow-xl dark:bg-gray-900">
Expand All @@ -227,6 +241,7 @@ export default function CourseRegistrationForm({
value={formData.name}
onChange={handleChange}
required
autoComplete="name"
className={`w-full rounded-lg border border-gray-300 bg-white py-3 pl-10 pr-4 text-gray-900 focus:outline-none focus:ring-2 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 ${currentTheme.focus}`}
placeholder="Nhập họ và tên"
/>
Expand All @@ -246,6 +261,7 @@ export default function CourseRegistrationForm({
value={formData.email}
onChange={handleChange}
required
autoComplete="email"
className={`w-full rounded-lg border border-gray-300 bg-white py-3 pl-10 pr-4 text-gray-900 focus:outline-none focus:ring-2 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 ${currentTheme.focus}`}
placeholder="email@example.com"
/>
Expand All @@ -265,6 +281,8 @@ export default function CourseRegistrationForm({
value={formData.phone}
onChange={handleChange}
required
autoComplete="tel"
inputMode="tel"
className={`w-full rounded-lg border border-gray-300 bg-white py-3 pl-10 pr-4 text-gray-900 focus:outline-none focus:ring-2 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 ${currentTheme.focus}`}
placeholder="0901234567"
/>
Expand Down Expand Up @@ -293,40 +311,40 @@ export default function CourseRegistrationForm({
</div>

{/* Alumni Status */}
<div
<label
htmlFor="isAlumni"
className={`flex cursor-pointer items-center rounded-lg border border-gray-200 p-4 transition dark:border-gray-600 ${currentTheme.hover}`}
onClick={() => setFormData({ ...formData, isAlumni: !formData.isAlumni })}
>
<input
id="isAlumni"
type="checkbox"
name="isAlumni"
checked={formData.isAlumni}
onChange={handleChange}
className={`mr-3 h-5 w-5 rounded border-gray-300 ${currentTheme.checkbox}`}
/>
<label className="cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-300">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
Cựu học viên của VNTechies
</label>
</div>
</span>
</label>
Comment on lines +314 to +329

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The checkbox inputs are wrapped in label elements but also contain nested label/span elements. This creates invalid HTML as labels cannot be nested. Remove the inner label tags and keep only the span elements, or restructure to avoid nesting labels.

Copilot uses AI. Check for mistakes.

{/* Group Registration */}
<div
<label
htmlFor="groupRegistration"
className={`flex cursor-pointer items-center rounded-lg border border-gray-200 p-4 transition dark:border-gray-600 ${currentTheme.hover}`}
onClick={() =>
setFormData({ ...formData, groupRegistration: !formData.groupRegistration })
}
>
<input
id="groupRegistration"
type="checkbox"
name="groupRegistration"
checked={formData.groupRegistration}
onChange={handleChange}
className={`mr-3 h-5 w-5 rounded border-gray-300 ${currentTheme.checkbox}`}
/>
<label className="cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-300">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
Đăng ký theo nhóm (2+ người)
</label>
</div>
</span>
</label>

{/* Coupon Code */}
<div>
Expand Down
Loading